diff options
author | Kevin <Kevin@54d463be-8e91-2dee-dedb-b68131a5f0ec> | 2008-04-12 08:43:29 +0000 |
---|---|---|
committer | Kevin <Kevin@54d463be-8e91-2dee-dedb-b68131a5f0ec> | 2008-04-12 08:43:29 +0000 |
commit | 81472b872dfc7f8e22361c7e2fdde2d8f03cf3ce (patch) | |
tree | ae9bfb837066520d81487fa0019fe55b0689b145 /src/char | |
parent | 023b3ec3a06128305ff20d5fbe2c2d315d66736e (diff) | |
download | hercules-81472b872dfc7f8e22361c7e2fdde2d8f03cf3ce.tar.gz hercules-81472b872dfc7f8e22361c7e2fdde2d8f03cf3ce.tar.bz2 hercules-81472b872dfc7f8e22361c7e2fdde2d8f03cf3ce.tar.xz hercules-81472b872dfc7f8e22361c7e2fdde2d8f03cf3ce.zip |
TXT code has been re uploaded. Please note this does not change the fact that TXT support has been dropped. There is no guarantee the code base will be managed and it will in the near future be REMOVED. If there is in ANY way TXT support in the future, it will be abstracted through a sql interface so only the sql code has to be maintained.
git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@12572 54d463be-8e91-2dee-dedb-b68131a5f0ec
Diffstat (limited to 'src/char')
-rw-r--r-- | src/char/Makefile.in | 47 | ||||
-rw-r--r-- | src/char/char.c | 4290 | ||||
-rw-r--r-- | src/char/char.h | 54 | ||||
-rw-r--r-- | src/char/int_guild.c | 1579 | ||||
-rw-r--r-- | src/char/int_guild.h | 26 | ||||
-rw-r--r-- | src/char/int_homun.c | 365 | ||||
-rw-r--r-- | src/char/int_homun.h | 15 | ||||
-rw-r--r-- | src/char/int_party.c | 732 | ||||
-rw-r--r-- | src/char/int_party.h | 20 | ||||
-rw-r--r-- | src/char/int_pet.c | 380 | ||||
-rw-r--r-- | src/char/int_pet.h | 21 | ||||
-rw-r--r-- | src/char/int_status.c | 183 | ||||
-rw-r--r-- | src/char/int_status.h | 23 | ||||
-rw-r--r-- | src/char/int_storage.c | 481 | ||||
-rw-r--r-- | src/char/int_storage.h | 25 | ||||
-rw-r--r-- | src/char/inter.c | 711 | ||||
-rw-r--r-- | src/char/inter.h | 30 |
17 files changed, 8982 insertions, 0 deletions
diff --git a/src/char/Makefile.in b/src/char/Makefile.in new file mode 100644 index 000000000..7835daa7f --- /dev/null +++ b/src/char/Makefile.in @@ -0,0 +1,47 @@ +
+COMMON_OBJ = ../common/obj_all/core.o ../common/obj_all/socket.o ../common/obj_all/timer.o \
+ ../common/obj_all/db.o ../common/obj_all/plugins.o ../common/obj_all/lock.o \
+ ../common/obj_all/malloc.o ../common/obj_all/showmsg.o ../common/obj_all/utils.o \
+ ../common/obj_all/strlib.o ../common/obj_all/grfio.o \
+ ../common/obj_all/mapindex.o ../common/obj_all/ers.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/grfio.h \
+ ../common/mapindex.h ../common/ers.h
+
+CHAR_OBJ = obj_txt/char.o obj_txt/inter.o obj_txt/int_party.o obj_txt/int_guild.o \
+ obj_txt/int_storage.o obj_txt/int_status.o obj_txt/int_pet.o obj_txt/int_homun.o
+CHAR_H = char.h inter.h int_party.h int_guild.h int_storage.h int_status.h int_pet.h int_homun.h
+
+@SET_MAKE@
+
+#####################################################################
+.PHONY : all char-server clean help
+
+all: char-server
+
+char-server: obj_txt $(CHAR_OBJ) $(COMMON_OBJ)
+ @CC@ @LDFLAGS@ -o ../../char-server@EXEEXT@ $(CHAR_OBJ) $(COMMON_OBJ) @LIBS@
+
+clean:
+ rm -rf *.o obj_txt ../../char-server@EXEEXT@
+
+help:
+ @echo "possible targets are 'char-server' 'all' 'clean' 'help'"
+ @echo "'char-server' - char server (TXT version)"
+ @echo "'all' - builds all above targets"
+ @echo "'clean' - cleans builds and objects"
+ @echo "'help' - outputs this message"
+
+#####################################################################
+
+obj_txt:
+ -mkdir obj_txt
+
+obj_txt/%.o: %.c $(CHAR_H) $(COMMON_H)
+ @CC@ @CFLAGS@ $(CUSTOM_CFLAGS) -DTXT_ONLY @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+
+# missing common object files
+../common/obj_all/%.o:
+ @$(MAKE) -C ../common txt
diff --git a/src/char/char.c b/src/char/char.c new file mode 100644 index 000000000..13da079ab --- /dev/null +++ b/src/char/char.c @@ -0,0 +1,4290 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/malloc.h" +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/lock.h" +#include "../common/malloc.h" +#include "../common/mapindex.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "../common/version.h" +#include "inter.h" +#include "int_guild.h" +#include "int_homun.h" +#include "int_pet.h" +#include "int_party.h" +#include "int_storage.h" +#include "int_status.h" +#include "char.h" + +#include <sys/types.h> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <time.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +// private declarations +#define CHAR_CONF_NAME "conf/char_athena.conf" +#define LAN_CONF_NAME "conf/subnet_athena.conf" + +char char_txt[1024] = "save/athena.txt"; +char friends_txt[1024] = "save/friends.txt"; +char hotkeys_txt[1024] = "save/hotkeys.txt"; +char char_log_filename[1024] = "log/char.log"; + +int save_log = 1; // show loading/saving messages + +#ifndef TXT_SQL_CONVERT +char db_path[1024] = "db"; + +struct mmo_map_server { + int fd; + uint32 ip; + uint16 port; + int users; + unsigned short map[MAX_MAP_PER_SERVER]; +} server[MAX_MAP_SERVERS]; + +int login_fd=-1, char_fd=-1; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[NAME_LENGTH] = "Server"; +char login_ip_str[128]; +uint32 login_ip = 0; +uint16 login_port = 6900; +char char_ip_str[128]; +uint32 char_ip = 0; +char bind_ip_str[128]; +uint32 bind_ip = INADDR_ANY; +uint16 char_port = 6121; +int char_maintenance = 0; +bool char_new = true; +int char_new_display = 0; + +int email_creation = 0; // disabled by default + +bool name_ignoring_case = false; // 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 unknown_char_name[1024] = "Unknown"; // Name to use when the requested name cannot be determined +#define TRIM_CHARS "\032\t\x0A\x0D " //The following characters are trimmed regardless because they cause confusion and problems on the servers. [Skotlex] +char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] +bool char_rename = true; + +int log_char = 1; // loggin char or not [devil] +int log_inter = 1; // loggin inter or not [devil] + +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] + +// Advanced subnet check [LuzZza] +struct s_subnet { + uint32 mask; + uint32 char_ip; + uint32 map_ip; +} subnet[16]; +int subnet_count = 0; + +struct char_session_data { + bool auth; // whether the session is authed or not + int account_id, login_id1, login_id2, sex; + int found_char[MAX_CHARS]; // ids of chars on this account + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t expiration_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +int char_id_count = START_CHAR_NUM; +struct character_data *char_dat; + +int char_num, char_max; +int max_connect_user = 0; +int gm_allow_level = 99; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; +int guild_exp_rate = 100; + +//Custom limits for the fame lists. [Skotlex] +int fame_list_size_chemist = MAX_FAME_LIST; +int fame_list_size_smith = MAX_FAME_LIST; +int fame_list_size_taekwon = MAX_FAME_LIST; + +// Char-server-side stored fame lists [DracoRPG] +struct fame_list smith_fame_list[MAX_FAME_LIST]; +struct fame_list chemist_fame_list[MAX_FAME_LIST]; +struct fame_list taekwon_fame_list[MAX_FAME_LIST]; + +// Initial position (it's possible to set it in conf file) +struct point start_point = { 0, 53, 111 }; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +// online players by [Yor] +char online_txt_filename[1024] = "online.txt"; +char online_html_filename[1024] = "online.html"; +int online_sorting_option = 0; // sorting option to display online players in online files +int online_display_option = 1; // display options: to know which columns must be displayed +int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer +int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +int console = 0; + +//----------------------------------------------------- +// Auth database +//----------------------------------------------------- +#define AUTH_TIMEOUT 30000 + +struct auth_node { + int account_id; + int char_id; + uint32 login_id1; + uint32 login_id2; + uint32 ip; + int sex; + time_t expiration_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +static DBMap* auth_db; // int account_id -> struct auth_node* + +//----------------------------------------------------- +// Online User Database +//----------------------------------------------------- + +struct online_char_data { + int account_id; + int char_id; + int fd; + int waiting_disconnect; + short server; +}; + +static DBMap* online_char_db; // int account_id -> struct online_char_data* +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; + CREATE(character, struct online_char_data, 1); + character->account_id = key.i; + character->char_id = -1; + character->server = -1; + character->fd = -1; + character->waiting_disconnect = -1; + return character; +} + +void set_char_online(int map_id, int char_id, int account_id) +{ + struct online_char_data* character; + + character = (struct online_char_data*)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 && map_id != -3) + { + //char == 99 <- Character logging in, so someone has logged in while one + //char is still on map-server, so kick him out, but don't print "error" + //as this is normal behaviour. [Skotlex] + if (char_id != 99) + 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[character->server].fd, character->account_id, character->char_id, 2); + } + + if( character->server > -1 ) + server[character->server].users++; + + if(character->waiting_disconnect != -1) { + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = -1; + } + + //If user is NOT at char screen, delete entry [Kevin] + if(character->char_id != -1) + { + idb_remove(online_char_db, account_id); + } + + if (login_fd > 0 && !session[login_fd]->flag.eof) + { + WFIFOHEAD(login_fd,6); + 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 online_char_data* character; + + if ((character = (struct online_char_data*)idb_get(online_char_db, account_id)) != NULL) + { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex] + if( character->server > -1 ) + server[character->server].users--; + + character->char_id = -1; + character->server = -1; + if(character->waiting_disconnect != -1){ + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = -1; + } + } + + if (login_fd > 0 && !session[login_fd]->flag.eof) + { + WFIFOHEAD(login_fd,6); + 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; + if(character->waiting_disconnect != -1){ + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = -1; + } + } else if (character->server == server) + character->server = -2; //In some map server that we aren't connected to. + return 0; +} + +static int char_db_kickoffline(DBKey key, void* data, va_list ap) +{ + struct online_char_data* character = (struct online_char_data*)data; + int server_id = va_arg(ap, int); + + if (server_id > -1 && character->server != server_id) + return 0; + + //Kick out any connected characters, and set them offline as appropiate. + if (character->server > -1) + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 1); + else if (character->waiting_disconnect == -1) + set_char_offline(character->char_id, character->account_id); + else + return 0; // fail + + return 1; +} + +void set_all_offline(int id) +{ + if (id < 0) + ShowNotice("Sending all users offline.\n"); + else + ShowNotice("Sending users of map-server %d offline.\n",id); + online_char_db->foreach(online_char_db,char_db_kickoffline,id); + + if (id >= 0 || login_fd <= 0 || session[login_fd]->flag.eof) + return; + //Tell login-server to also mark all our characters as offline. + WFIFOHEAD(login_fd,2); + WFIFOW(login_fd,0) = 0x2737; + WFIFOSET(login_fd,2); +} + +//------------------------------ +// Writing function of logs file +//------------------------------ +int char_log(char *fmt, ...) +{ + if(log_char) + { + FILE *logfp; + va_list ap; + time_t raw_time; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(char_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, "\n"); + else { + time(&raw_time); + strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&raw_time)); + sprintf(tmpstr + 19, ": %s", fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + va_end(ap); + } + return 0; +} + +//---------------------------------------------------------------------- +// 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) +//---------------------------------------------------------------------- +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; +} + +//Search character data from the aid/cid givem +struct mmo_charstatus* search_character(int aid, int cid) +{ + int i; + for (i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == cid && char_dat[i].status.account_id == aid) + return &char_dat[i].status; + } + return NULL; +} + +struct mmo_charstatus* search_character_byname(char* character_name) +{ + int i = search_character_index(character_name); + if (i == -1) return NULL; + return &char_dat[i].status; +} + +// Searches if the given character is online, and returns the fd of the +// map-server it is connected to. +int search_character_online(int aid, int cid) +{ + //Look for online character. + struct online_char_data* character; + character = idb_get(online_char_db, aid); + if(character && + character->char_id == cid && + character->server > -1) + return server[character->server].fd; + return -1; +} + +//---------------------------------------------- +// Search an character id +// (return character index or -1 (if not found)) +// If exact character name is not found, +// the function checks without case sensitive +// and returns index if only 1 character is found +// and similar to the searched name. +//---------------------------------------------- +int search_character_index(char* character_name) +{ + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) + if (stricmp(char_dat[i].status.name, character_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(char_dat[i].status.name, character_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return index; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return -1; +} + +/*--------------------------------------------------- + Make a data line for friends list + --------------------------------------------------*/ +int mmo_friends_list_data_str(char *str, struct mmo_charstatus *p) +{ + int i; + char *str_p = str; + str_p += sprintf(str_p, "%d", p->char_id); + + for (i=0;i<MAX_FRIENDS;i++){ + if (p->friends[i].account_id > 0 && p->friends[i].char_id > 0 && p->friends[i].name[0]) + str_p += sprintf(str_p, ",%d,%d,%s", p->friends[i].account_id, p->friends[i].char_id, p->friends[i].name); + } + + str_p += '\0'; + + return 0; +} + +/*--------------------------------------------------- + Make a data line for hotkeys list + --------------------------------------------------*/ +int mmo_hotkeys_tostr(char *str, struct mmo_charstatus *p) +{ +#ifdef HOTKEY_SAVING + int i; + char *str_p = str; + str_p += sprintf(str_p, "%d", p->char_id); + for (i=0;i<MAX_HOTKEYS;i++) + str_p += sprintf(str_p, ",%d,%d,%d", p->hotkeys[i].type, p->hotkeys[i].id, p->hotkeys[i].lv); + str_p += '\0'; +#endif + + return 0; +} + +//------------------------------------------------- +// Function to create the character line (for save) +//------------------------------------------------- +int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int reg_num) +{ + int i,j; + char *str_p = str; + + str_p += sprintf(str_p, + "%d\t%d,%d\t%s\t%d,%d,%d\t%u,%u,%d" //Up to Zeny field + "\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" //Up to Skill Point + "\t%d,%d,%d\t%d,%d,%d,%d" //Up to hom id + "\t%d,%d,%d\t%d,%d,%d,%d,%d" //Up to head bottom + "\t%d,%d,%d\t%d,%d,%d" //last point + save point + ",%d,%d,%d,%d,%d\t", //Family info + p->char_id, p->account_id, p->slot, p->name, // + p->class_, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->status_point, p->skill_point, + p->option, p->karma, p->manner, // + p->party_id, p->guild_id, p->pet_id, p->hom_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, // + p->save_point.map, p->save_point.x, p->save_point.y, + p->partner_id,p->father,p->mother,p->child,p->fame); + for(i = 0; i < MAX_MEMOPOINTS; i++) + if (p->memo_point[i].map) { + str_p += sprintf(str_p, "%d,%d,%d ", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->inventory[i].id,p->inventory[i].nameid,p->inventory[i].amount,p->inventory[i].equip, + p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute); + for(j=0; j<MAX_SLOTS; j++) + str_p += sprintf(str_p,",%d",p->inventory[i].card[j]); + str_p += sprintf(str_p," "); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->cart[i].id,p->cart[i].nameid,p->cart[i].amount,p->cart[i].equip, + p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute); + for(j=0; j<MAX_SLOTS; j++) + str_p += sprintf(str_p,",%d",p->cart[i].card[j]); + str_p += sprintf(str_p," "); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_SKILL; i++) + if (p->skill[i].id && p->skill[i].flag != 1) { + str_p += sprintf(str_p, "%d,%d ", p->skill[i].id, (p->skill[i].flag == 0) ? p->skill[i].lv : p->skill[i].flag-2); + } + *(str_p++) = '\t'; + + for(i = 0; i < reg_num; i++) + if (reg[i].str[0]) + str_p += sprintf(str_p, "%s,%s ", reg[i].str, reg[i].value); + *(str_p++) = '\t'; + + *str_p = '\0'; + return 0; +} +#endif //TXT_SQL_CONVERT +//------------------------------------------------------------------------- +// Function to set the character from the line (at read of characters file) +//------------------------------------------------------------------------- +int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int *reg_num) +{ + char tmp_str[3][128]; //To avoid deleting chars with too long names. + int tmp_int[256]; + unsigned int tmp_uint[2]; //To read exp.... + int next, len, i, j; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + +// Char structure of version 1500 (homun + mapindex maps) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%d,%d,%d\t%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], &tmp_int[44], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + &tmp_int[45], &tmp_int[35], &tmp_int[36], + &tmp_int[46], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &next) != 48) + { + tmp_int[44] = 0; //Hom ID. +// Char structure of version 1488 (fame field addition) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &next) != 47) + { + tmp_int[43] = 0; //Fame +// Char structure of version 1363 (family data addition) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &next) != 46) + { + tmp_int[40] = 0; // father + tmp_int[41] = 0; // mother + tmp_int[42] = 0; // child +// Char structure version 1008 (marriage partner addition) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], &next) != 43) + { + tmp_int[39] = 0; // partner id +// Char structure version 384 (pet addition) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &next) != 42) + { + tmp_int[26] = 0; // pet id +// Char structure of a version 1 (original data structure) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &next) != 41) + { + ShowError("Char-loading: Unrecognized character data version, info lost!\n"); + ShowDebug("Character info: %s\n", str); + return 0; + } + } // Char structure version 384 (pet addition) + } // Char structure version 1008 (marriage partner addition) + } // Char structure of version 1363 (family data addition) + } // Char structure of version 1488 (fame field addition) + //Convert save data from string to integer for older formats + tmp_int[45] = mapindex_name2id(tmp_str[1]); + tmp_int[46] = mapindex_name2id(tmp_str[2]); + } // Char structure of version 1500 (homun + mapindex maps) + + memcpy(p->name, tmp_str[0], NAME_LENGTH); //Overflow protection [Skotlex] + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->slot = tmp_int[2]; + p->class_ = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_uint[0]; + p->job_exp = tmp_uint[1]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = min(tmp_int[19], USHRT_MAX); + p->skill_point = min(tmp_int[20], USHRT_MAX); + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + p->father = tmp_int[40]; + p->mother = tmp_int[41]; + p->child = tmp_int[42]; + p->fame = tmp_int[43]; + p->hom_id = tmp_int[44]; + p->last_point.map = tmp_int[45]; + p->save_point.map = tmp_int[46]; + +#ifndef TXT_SQL_CONVERT + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == p->char_id) { + ShowError(CL_RED"mmmo_auth_init: a character has an identical id to another.\n"); + ShowError(" character id #%d -> new character not readed.\n", p->char_id); + ShowError(" Character saved in log file."CL_RESET"\n"); + return -1; + } else if (strcmp(char_dat[i].status.name, p->name) == 0) { + ShowError(CL_RED"mmmo_auth_init: a character name already exists.\n"); + ShowError(" character name '%s' -> new character not read.\n", p->name); + ShowError(" Character saved in log file."CL_RESET"\n"); + return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { + ShowWarning("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); + ShowWarning(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); + ShowWarning(" Character readed. Suggestion: change the wisp server name.\n"); + char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'.\n", + p->name, wisp_server_name); + } +#endif //TXT_SQL_CONVERT + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + //mapindex memo format + if (sscanf(str+next, "%d,%d,%d%n", &tmp_int[2], &tmp_int[0], &tmp_int[1], &len) != 3) + { //Old string-based memo format. + if (sscanf(str+next, "%[^,],%d,%d%n", tmp_str[0], &tmp_int[0], &tmp_int[1], &len) != 3) + return -3; + tmp_int[2] = mapindex_name2id(tmp_str[0]); + } + if (i < MAX_MEMOPOINTS) + { //Avoid overflowing (but we must also read through all saved memos) + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + p->memo_point[i].map = tmp_int[2]; + } + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8) + { + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str[0] && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++) + p->inventory[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } else // invalid structure + return -4; + } + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8) + { + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++) + p->cart[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } else // invalid structure + return -5; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len) != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + if (sscanf(str + next, "%[^,],%[^ ] %n", reg[i].str, reg[i].value, &len) != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%[^ ] %n", reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + *reg_num = i; + + return 1; +} + +//--------------------------------- +// Function to read friend list +//--------------------------------- +int parse_friend_txt(struct mmo_charstatus *p) +{ + char line[1024], temp[1024]; + int pos = 0, count = 0, next; + int i,len; + FILE *fp; + + // Open the file and look for the ID + fp = fopen(friends_txt, "r"); + + if(fp == NULL) + return -1; + + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%d%n",&i, &pos) < 1 || i != p->char_id) + continue; //Not this line... + //Read friends + len = strlen(line); + next = pos; + for (count = 0; next < len && count < MAX_FRIENDS; count++) + { //Read friends. + if (sscanf(line+next, ",%d,%d,%23[^,^\n]%n",&p->friends[count].account_id,&p->friends[count].char_id, p->friends[count].name, &pos) < 3) + { //Invalid friend? + memset(&p->friends[count], 0, sizeof(p->friends[count])); + break; + } + next+=pos; + //What IF the name contains a comma? while the next field is not a + //number, we assume it belongs to the current name. [Skotlex] + //NOTE: Of course, this will fail if someone sets their name to something like + //Bob,2005 but... meh, it's the problem of parsing a text file (encasing it in " + //won't do as quotes are also valid name chars!) + while(next < len && sscanf(line+next, ",%23[^,^\n]%n", temp, &pos) > 0) + { + if (atoi(temp)) //We read the next friend, just continue. + break; + //Append the name. + next+=pos; + i = strlen(p->friends[count].name); + if (i + strlen(temp) +1 < NAME_LENGTH) + { + p->friends[count].name[i] = ','; + strcpy(p->friends[count].name+i+1, temp); + } + } //End Guess Block + } //Friend's for. + break; //Found friends. + } + fclose(fp); + return count; +} + +//--------------------------------- +// Function to read hotkey list +//--------------------------------- +int parse_hotkey_txt(struct mmo_charstatus *p) +{ +#ifdef HOTKEY_SAVING + char line[1024]; + int pos = 0, count = 0, next; + int i,len; + int type, id, lv; + FILE *fp; + + // Open the file and look for the ID + fp = fopen(hotkeys_txt, "r"); + if(fp == NULL) + return -1; + + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%d%n",&i, &pos) < 1 || i != p->char_id) + continue; //Not this line... + //Read hotkeys + len = strlen(line); + next = pos; + for (count = 0; next < len && count < MAX_HOTKEYS; count++) + { + if (sscanf(line+next, ",%d,%d,%d%n",&type,&id,&lv, &pos) < 3) + //Invalid entry? + break; + p->hotkeys[count].type = type; + p->hotkeys[count].id = id; + p->hotkeys[count].lv = lv; + next+=pos; + } + break; //Found hotkeys. + } + fclose(fp); + return count; +#else + return 0; +#endif +} + + + +#ifndef TXT_SQL_CONVERT +//--------------------------------- +// Function to read characters file +//--------------------------------- +int mmo_char_init(void) +{ + char line[65536]; + int ret, line_count; + FILE* fp; + + char_num = 0; + char_max = 0; + char_dat = NULL; + + fp = fopen(char_txt, "r"); + + if (fp == NULL) { + ShowError("Characters file not found: %s.\n", char_txt); + char_log("Characters file not found: %s.\n", char_txt); + char_log("Id for the next created character: %d.\n", char_id_count); + return 0; + } + + line_count = 0; + while(fgets(line, sizeof(line), fp)) + { + int i, j; + line_count++; + + if (line[0] == '/' && line[1] == '/') + continue; + + j = 0; + if (sscanf(line, "%d\t%%newid%%%n", &i, &j) == 1 && j > 0) { + if (char_id_count < i) + char_id_count = i; + continue; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max); + if (!char_dat) { + ShowFatalError("Out of memory: mmo_char_init (realloc of char_dat).\n"); + char_log("Out of memory: mmo_char_init (realloc of char_dat).\n"); + exit(EXIT_FAILURE); + } + } + + ret = mmo_char_fromstr(line, &char_dat[char_num].status, char_dat[char_num].global, &char_dat[char_num].global_num); + + // Initialize friends list + parse_friend_txt(&char_dat[char_num].status); // Grab friends for the character + // Initialize hotkey list + parse_hotkey_txt(&char_dat[char_num].status); // Grab hotkeys for the character + + if (ret > 0) { // negative value or zero for errors + if (char_dat[char_num].status.char_id >= char_id_count) + char_id_count = char_dat[char_num].status.char_id + 1; + char_num++; + } else { + ShowError("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); + ShowError(" -> Character saved in log file.\n"); + switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):\n"); + break; + case -2: + char_log("Duplicate character name in the next character line (character not readed):\n"); + break; + case -3: + char_log("Invalid memo point structure in the next character line (character not readed):\n"); + break; + case -4: + char_log("Invalid inventory item structure in the next character line (character not readed):\n"); + break; + case -5: + char_log("Invalid cart item structure in the next character line (character not readed):\n"); + break; + case -6: + char_log("Invalid skill structure in the next character line (character not readed):\n"); + break; + case -7: + char_log("Invalid register structure in the next character line (character not readed):\n"); + break; + default: // 0 + char_log("Unabled to get a character in the next line - Basic structure of line (before inventory) is incorrect (character not readed):\n"); + break; + } + char_log("%s", line); + } + } + fclose(fp); + + if (char_num == 0) { + ShowNotice("mmo_char_init: No character found in %s.\n", char_txt); + char_log("mmo_char_init: No character found in %s.\n", char_txt); + } else if (char_num == 1) { + ShowStatus("mmo_char_init: 1 character read in %s.\n", char_txt); + char_log("mmo_char_init: 1 character read in %s.\n", char_txt); + } else { + ShowStatus("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); + char_log("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); + } + + char_log("Id for the next created character: %d.\n", char_id_count); + + return 0; +} + +//--------------------------------------------------------- +// Function to save characters in files (speed up by [Yor]) +//--------------------------------------------------------- +void mmo_char_sync(void) +{ + char line[65536],f_line[1024]; + int i, j, k; + int lock; + FILE *fp,*f_fp; + CREATE_BUFFER(id, int, char_num); + + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if ((char_dat[i].status.account_id < char_dat[id[j]].status.account_id) || + // if same account id, we sort by slot. + (char_dat[i].status.account_id == char_dat[id[j]].status.account_id && + char_dat[i].status.slot < char_dat[id[j]].status.slot)) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { + ShowWarning("Server cannot save characters.\n"); + char_log("WARNING: Server cannot save characters.\n"); + } else { + for(i = 0; i < char_num; i++) { + mmo_char_tostr(line, &char_dat[id[i]].status, char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index + fprintf(fp, "%s\n", line); + } + fprintf(fp, "%d\t%%newid%%\n", char_id_count); + lock_fclose(fp, char_txt, &lock); + } + + // Friends List data save (davidsiaw) + f_fp = lock_fopen(friends_txt, &lock); + for(i = 0; i < char_num; i++) { + mmo_friends_list_data_str(f_line, &char_dat[id[i]].status); + fprintf(f_fp, "%s\n", f_line); + } + + lock_fclose(f_fp, friends_txt, &lock); + +#ifdef HOTKEY_SAVING + // Hotkey List data save (Skotlex) + f_fp = lock_fopen(hotkeys_txt, &lock); + for(i = 0; i < char_num; i++) { + mmo_hotkeys_tostr(f_line, &char_dat[id[i]].status); + fprintf(f_fp, "%s\n", f_line); + } + + lock_fclose(f_fp, hotkeys_txt, &lock); +#endif + + DELETE_BUFFER(id); + + return; +} + +//---------------------------------------------------- +// Function to save (in a periodic way) datas in files +//---------------------------------------------------- +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) +{ + if (save_log) + ShowInfo("Saving all files...\n"); + mmo_char_sync(); + inter_save(); + return 0; +} + +//----------------------------------- +// Function to create a new character +//----------------------------------- +int make_new_char(struct char_session_data* sd, char* name_, int str, int agi, int vit, int int_, int dex, int luk, int slot, int hair_color, int hair_style) +{ + char name[NAME_LENGTH]; + int i; + + safestrncpy(name, name_, NAME_LENGTH); + normalize_name(name,TRIM_CHARS); + + // check length of character name + if( name[0] == '\0' ) + return -2; // empty character name + + // check content of character name + if( remove_control_chars(name) ) + return -2; // control chars in name + + // check for reserved names + if( strcmpi(name, main_chat_nick) == 0 || strcmpi(name, wisp_server_name) == 0 ) + return -1; // nick reserved for internal server messages + + // 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 name (already in use?) + ARR_FIND( 0, char_num, i, + (name_ignoring_case && strncmp(char_dat[i].status.name, name, NAME_LENGTH) == 0) || + (!name_ignoring_case && strncmpi(char_dat[i].status.name, name, NAME_LENGTH) == 0) ); + if( i < char_num ) + return -1; // name already exists + + //check other inputs + if((slot >= MAX_CHARS) // slots + || (hair_style >= 24) // hair style + || (hair_color >= 9) // hair color + || (str + agi + vit + int_ + dex + luk != 6*5 ) // stats + || (str < 1 || str > 9 || agi < 1 || agi > 9 || vit < 1 || vit > 9 || int_ < 1 || int_ > 9 || dex < 1 || dex > 9 || luk < 1 || luk > 9) // individual stat values + || (str + int_ != 10 || agi + luk != 10 || vit + dex != 10) ) // pairs + return -2; // invalid input + + // check char slot + ARR_FIND( 0, char_num, i, char_dat[i].status.account_id == sd->account_id && char_dat[i].status.slot == slot ); + if( i < char_num ) + return -2; // slot already in use + + if (char_num >= char_max) { + char_max += 256; + RECREATE(char_dat, struct character_data, char_max); + if (!char_dat) { + ShowFatalError("Out of memory: make_new_char (realloc of char_dat).\n"); + char_log("Out of memory: make_new_char (realloc of char_dat).\n"); + exit(EXIT_FAILURE); + } + } + + // validation success, log result + char_log("make new char: account: %d, slot %d, name: %s, stats: %d/%d/%d/%d/%d/%d, hair: %d, hair color: %d.\n", + sd->account_id, slot, name, str, agi, vit, int_, dex, luk, hair_style, hair_color); + + i = char_num; + memset(&char_dat[i], 0, sizeof(struct character_data)); + + char_dat[i].status.char_id = char_id_count++; + char_dat[i].status.account_id = sd->account_id; + char_dat[i].status.slot = slot; + safestrncpy(char_dat[i].status.name,name,NAME_LENGTH); + char_dat[i].status.class_ = 0; + char_dat[i].status.base_level = 1; + char_dat[i].status.job_level = 1; + char_dat[i].status.base_exp = 0; + char_dat[i].status.job_exp = 0; + char_dat[i].status.zeny = start_zeny; + char_dat[i].status.str = str; + char_dat[i].status.agi = agi; + char_dat[i].status.vit = vit; + char_dat[i].status.int_ = int_; + char_dat[i].status.dex = dex; + char_dat[i].status.luk = luk; + char_dat[i].status.max_hp = 40 * (100 + char_dat[i].status.vit) / 100; + char_dat[i].status.max_sp = 11 * (100 + char_dat[i].status.int_) / 100; + char_dat[i].status.hp = char_dat[i].status.max_hp; + char_dat[i].status.sp = char_dat[i].status.max_sp; + char_dat[i].status.status_point = 0; + char_dat[i].status.skill_point = 0; + char_dat[i].status.option = 0; + char_dat[i].status.karma = 0; + char_dat[i].status.manner = 0; + char_dat[i].status.party_id = 0; + char_dat[i].status.guild_id = 0; + char_dat[i].status.hair = hair_style; + char_dat[i].status.hair_color = hair_color; + char_dat[i].status.clothes_color = 0; + char_dat[i].status.inventory[0].nameid = start_weapon; // Knife + char_dat[i].status.inventory[0].amount = 1; + char_dat[i].status.inventory[0].identify = 1; + char_dat[i].status.inventory[1].nameid = start_armor; // Cotton Shirt + char_dat[i].status.inventory[1].amount = 1; + char_dat[i].status.inventory[1].identify = 1; + char_dat[i].status.weapon = 0; // W_FIST + char_dat[i].status.shield = 0; + char_dat[i].status.head_top = 0; + char_dat[i].status.head_mid = 0; + char_dat[i].status.head_bottom = 0; + memcpy(&char_dat[i].status.last_point, &start_point, sizeof(start_point)); + memcpy(&char_dat[i].status.save_point, &start_point, sizeof(start_point)); + char_num++; + + ShowInfo("Created char: account: %d, char: %d, slot: %d, name: %s\n", sd->account_id, i, slot, name); + mmo_char_sync(); + return i; +} + +//---------------------------------------------------- +// This function return the name of the job (by [Yor]) +//---------------------------------------------------- +char * job_name(int class_) +{ + switch (class_) { + case JOB_NOVICE: return "Novice"; + case JOB_SWORDMAN: return "Swordsman"; + case JOB_MAGE: return "Mage"; + case JOB_ARCHER: return "Archer"; + case JOB_ACOLYTE: return "Acolyte"; + case JOB_MERCHANT: return "Merchant"; + case JOB_THIEF: return "Thief"; + case JOB_KNIGHT: return "Knight"; + case JOB_PRIEST: return "Priest"; + case JOB_WIZARD: return "Wizard"; + case JOB_BLACKSMITH: return "Blacksmith"; + case JOB_HUNTER: return "Hunter"; + case JOB_ASSASSIN: return "Assassin"; + case JOB_KNIGHT2: return "Peco-Knight"; + case JOB_CRUSADER: return "Crusader"; + case JOB_MONK: return "Monk"; + case JOB_SAGE: return "Sage"; + case JOB_ROGUE: return "Rogue"; + case JOB_ALCHEMIST: return "Alchemist"; + case JOB_BARD: return "Bard"; + case JOB_DANCER: return "Dancer"; + case JOB_CRUSADER2: return "Peco-Crusader"; + case JOB_WEDDING: return "Wedding"; + case JOB_SUPER_NOVICE: return "Super Novice"; + case JOB_GUNSLINGER: return "Gunslinger"; + case JOB_NINJA: return "Ninja"; + case JOB_XMAS: return "Christmas"; + case JOB_NOVICE_HIGH: return "Novice High"; + case JOB_SWORDMAN_HIGH: return "Swordsman High"; + case JOB_MAGE_HIGH: return "Mage High"; + case JOB_ARCHER_HIGH: return "Archer High"; + case JOB_ACOLYTE_HIGH: return "Acolyte High"; + case JOB_MERCHANT_HIGH: return "Merchant High"; + case JOB_THIEF_HIGH: return "Thief High"; + case JOB_LORD_KNIGHT: return "Lord Knight"; + case JOB_HIGH_PRIEST: return "High Priest"; + case JOB_HIGH_WIZARD: return "High Wizard"; + case JOB_WHITESMITH: return "Whitesmith"; + case JOB_SNIPER: return "Sniper"; + case JOB_ASSASSIN_CROSS: return "Assassin Cross"; + case JOB_LORD_KNIGHT2: return "Peko Knight"; + case JOB_PALADIN: return "Paladin"; + case JOB_CHAMPION: return "Champion"; + case JOB_PROFESSOR: return "Professor"; + case JOB_STALKER: return "Stalker"; + case JOB_CREATOR: return "Creator"; + case JOB_CLOWN: return "Clown"; + case JOB_GYPSY: return "Gypsy"; + case JOB_PALADIN2: return "Peko Paladin"; + case JOB_BABY: return "Baby Novice"; + case JOB_BABY_SWORDMAN: return "Baby Swordsman"; + case JOB_BABY_MAGE: return "Baby Mage"; + case JOB_BABY_ARCHER: return "Baby Archer"; + case JOB_BABY_ACOLYTE: return "Baby Acolyte"; + case JOB_BABY_MERCHANT: return "Baby Merchant"; + case JOB_BABY_THIEF: return "Baby Thief"; + case JOB_BABY_KNIGHT: return "Baby Knight"; + case JOB_BABY_PRIEST: return "Baby Priest"; + case JOB_BABY_WIZARD: return "Baby Wizard"; + case JOB_BABY_BLACKSMITH: return "Baby Blacksmith"; + case JOB_BABY_HUNTER: return "Baby Hunter"; + case JOB_BABY_ASSASSIN: return "Baby Assassin"; + case JOB_BABY_KNIGHT2: return "Baby Peco Knight"; + case JOB_BABY_CRUSADER: return "Baby Crusader"; + case JOB_BABY_MONK: return "Baby Monk"; + case JOB_BABY_SAGE: return "Baby Sage"; + case JOB_BABY_ROGUE: return "Baby Rogue"; + case JOB_BABY_ALCHEMIST: return "Baby Alchemist"; + case JOB_BABY_BARD: return "Baby Bard"; + case JOB_BABY_DANCER: return "Baby Dancer"; + case JOB_BABY_CRUSADER2: return "Baby Peco Crusader"; + case JOB_SUPER_BABY: return "Super Baby"; + case JOB_TAEKWON: return "Taekwon"; + case JOB_STAR_GLADIATOR: return "Star Gladiator"; + case JOB_STAR_GLADIATOR2: return "Flying Star Gladiator"; + case JOB_SOUL_LINKER: return "Soul Linker"; + } + return "Unknown Job"; +} + +static int create_online_files_sub(DBKey key, void* data, va_list va) +{ + struct online_char_data *character; + int* players; + int *id; + int j,k,l; + character = (struct online_char_data*) data; + players = va_arg(va, int*); + id = va_arg(va, int*); + + // check if map-server is online + if (character->server == -1 || character->char_id == -1) { //Character not currently online. + return -1; + } + + j = character->server; + if (server[j].fd < 0) { + server[j].users = 0; + return -1; + } + // search position of character in char_dat and sort online characters. + for(j = 0; j < char_num; j++) { + if (char_dat[j].status.char_id != character->char_id) + continue; + id[*players] = j; + // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) + for(k = 0; k < *players; k++) + if (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0 || + // if same name, we sort with case sensitive. + (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) == 0 && + strcmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 2: // by zeny + for(k = 0; k < *players; k++) + if (char_dat[j].status.zeny < char_dat[id[k]].status.zeny || + // if same number of zenys, we sort by name. + (char_dat[j].status.zeny == char_dat[id[k]].status.zeny && + stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 3: // by base level + for(k = 0; k < *players; k++) + if (char_dat[j].status.base_level < char_dat[id[k]].status.base_level || + // if same base level, we sort by base exp. + (char_dat[j].status.base_level == char_dat[id[k]].status.base_level && + char_dat[j].status.base_exp < char_dat[id[k]].status.base_exp)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 4: // by job (and job level) + for(k = 0; k < *players; k++) + if (char_dat[j].status.class_ < char_dat[id[k]].status.class_ || + // if same job, we sort by job level. + (char_dat[j].status.class_ == char_dat[id[k]].status.class_ && + char_dat[j].status.job_level < char_dat[id[k]].status.job_level) || + // if same job and job level, we sort by job exp. + (char_dat[j].status.class_ == char_dat[id[k]].status.class_ && + char_dat[j].status.job_level == char_dat[id[k]].status.job_level && + char_dat[j].status.job_exp < char_dat[id[k]].status.job_exp)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 5: // by location map name + { + const char *map1, *map2; + map1 = mapindex_id2name(char_dat[j].status.last_point.map); + + for(k = 0; k < *players; k++) { + map2 = mapindex_id2name(char_dat[id[k]].status.last_point.map); + if (!map1 || !map2 || //Avoid sorting if either one failed to resolve. + stricmp(map1, map2) < 0 || + // if same map name, we sort by name. + (stricmp(map1, map2) == 0 && + stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + } + } + break; + default: // 0 or invalid value: no sorting + break; + } + (*players)++; + break; + } + return 0; +} +//------------------------------------------------------------- +// Function to create the online files (txt and html). by [Yor] +//------------------------------------------------------------- +void create_online_files(void) +{ + unsigned int k, j; // for loop with strlen comparing + int i, l; // for loops + int players; // count the number of players + FILE *fp; // for the txt file + FILE *fp2; // for the html file + char temp[256]; // to prepare what we must display + time_t time_server; // for number of seconds + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + int id[4096]; + + if (online_display_option == 0) // we display nothing, so return + return; + + // Get number of online players, id of each online players, and verify if a server is offline + players = 0; + online_char_db->foreach(online_char_db, create_online_files_sub, &players, &id); + + // write files + fp = fopen(online_txt_filename, "w"); + if (fp != NULL) { + fp2 = fopen(online_html_filename, "w"); + if (fp2 != NULL) { + // get time + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + strftime(temp, sizeof(temp), "%d %b %Y %X", datetime); // like sprintf, but only for date/time (05 dec 2003 15:12:52) + // write heading + fprintf(fp2, "<HTML>\n"); + fprintf(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n", online_refresh_html); // update on client explorer every x seconds + fprintf(fp2, " <HEAD>\n"); + fprintf(fp2, " <TITLE>Online Players on %s</TITLE>\n", server_name); + fprintf(fp2, " </HEAD>\n"); + fprintf(fp2, " <BODY>\n"); + fprintf(fp2, " <H3>Online Players on %s (%s):</H3>\n", server_name, temp); + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + + for (i = 0; i < players; i++) { + // if it's the first player + if (i == 0) { + j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " <table border=\"1\" cellspacing=\"1\">\n"); + fprintf(fp2, " <tr>\n"); + if ((online_display_option & 1) || (online_display_option & 64)) { + fprintf(fp2, " <td><b>Name</b></td>\n"); + if (online_display_option & 64) { + fprintf(fp, "Name "); // 30 + j += 30; + } else { + fprintf(fp, "Name "); // 25 + j += 25; + } + } + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td><b>Job (levels)</b></td>\n"); + fprintf(fp, "Job Levels "); // 27 + j += 27; + } else if (online_display_option & 2) { + fprintf(fp2, " <td><b>Job</b></td>\n"); + fprintf(fp, "Job "); // 19 + j += 19; + } else if (online_display_option & 4) { + fprintf(fp2, " <td><b>Levels</b></td>\n"); + fprintf(fp, " Levels "); // 8 + j += 8; + } + if (online_display_option & 24) { // 8 or 16 + fprintf(fp2, " <td><b>Location</b></td>\n"); + if (online_display_option & 16) { + fprintf(fp, "Location ( x , y ) "); // 23 + j += 23; + } else { + fprintf(fp, "Location "); // 13 + j += 13; + } + } + if (online_display_option & 32) { + fprintf(fp2, " <td ALIGN=CENTER><b>zenys</b></td>\n"); + fprintf(fp, " Zenys "); // 16 + j += 16; + } + fprintf(fp2, " </tr>\n"); + fprintf(fp, "\n"); + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); + } + fprintf(fp2, " <tr>\n"); + // get id of the character (more speed) + j = id[i]; + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display + strcpy(temp, char_dat[j].status.name); + l = isGM(char_dat[j].status.account_id); + if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); + else + fprintf(fp, "%-24s ", temp); + } else + fprintf(fp, "%-24s ", temp); + // name of the character in the html (no < >, because that create problem in html code) + fprintf(fp2, " <td>"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "<b>"); + for (k = 0; k < strlen(temp); k++) { + switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); + break; + case '>': // > + fprintf(fp2, ">"); + break; + default: + fprintf(fp2, "%c", temp[k]); + break; + }; + } + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "</b> (GM)"); + fprintf(fp2, "</td>\n"); + } + // displaying of the job + if (online_display_option & 6) { + char * jobname = job_name(char_dat[j].status.class_); + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level); + fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level); + } else if (online_display_option & 2) { + fprintf(fp2, " <td>%s</td>\n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { + fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].status.base_level, char_dat[j].status.job_level); + fprintf(fp, "%3d/%3d ", char_dat[j].status.base_level, char_dat[j].status.job_level); + } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name + memcpy(temp, mapindex_id2name(char_dat[j].status.last_point.map), MAP_NAME_LENGTH); + // write map name + if (online_display_option & 16) { // map-name AND coordinates + fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y); + fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y); + } else { + fprintf(fp2, " <td>%s</td>\n", temp); + fprintf(fp, "%-12s ", temp); + } + } + // displaying nimber of zenys + if (online_display_option & 32) { + // write number of zenys + if (char_dat[j].status.zeny == 0) { // if no zeny + fprintf(fp2, " <td ALIGN=RIGHT>no zeny</td>\n"); + fprintf(fp, " no zeny "); + } else { + fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].status.zeny); + fprintf(fp, "%13d z ", char_dat[j].status.zeny); + } + } + fprintf(fp, "\n"); + fprintf(fp2, " </tr>\n"); + } + // If we display at least 1 player + if (players > 0) { + fprintf(fp2, " </table>\n"); + fprintf(fp, "\n"); + } + + // Displaying number of online players + if (players == 0) { + fprintf(fp2, " <p>No user is online.</p>\n"); + fprintf(fp, "No user is online.\n"); + } else if (players == 1) { + fprintf(fp2, " <p>%d user is online.</p>\n", players); + fprintf(fp, "%d user is online.\n", players); + } else { + fprintf(fp2, " <p>%d users are online.</p>\n", players); + fprintf(fp, "%d users are online.\n", players); + } + fprintf(fp2, " </BODY>\n"); + fprintf(fp2, "</HTML>\n"); + fclose(fp2); + } + fclose(fp); + } + + return; +} + +//--------------------------------------------------------------------- +// This function return the number of online players in all map-servers +//--------------------------------------------------------------------- +int count_users(void) +{ + int i, users; + + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server[i].fd >= 0) + users += server[i].users; + + return users; +} + +/// Writes char data to the buffer in the format used by the client. +/// Used in packets 0x6b (chars info) and 0x6d (new char info) +/// Returns the size (106 or 108) +int mmo_char_tobuf(uint8* buf, struct mmo_charstatus* p) +{ + if( buf == NULL || p == NULL ) + return 0; + + WBUFL(buf,0) = p->char_id; + WBUFL(buf,4) = min(p->base_exp, LONG_MAX); + WBUFL(buf,8) = p->zeny; + WBUFL(buf,12) = min(p->job_exp, LONG_MAX); + WBUFL(buf,16) = p->job_level; + WBUFL(buf,20) = 0; // probably opt1 + WBUFL(buf,24) = 0; // probably opt2 + WBUFL(buf,28) = p->option; + WBUFL(buf,32) = p->karma; + WBUFL(buf,36) = p->manner; + WBUFW(buf,40) = min(p->status_point, SHRT_MAX); + WBUFW(buf,42) = min(p->hp, SHRT_MAX); + WBUFW(buf,44) = min(p->max_hp, SHRT_MAX); + WBUFW(buf,46) = min(p->sp, SHRT_MAX); + WBUFW(buf,48) = min(p->max_sp, SHRT_MAX); + WBUFW(buf,50) = DEFAULT_WALK_SPEED; // p->speed; + WBUFW(buf,52) = p->class_; + WBUFW(buf,54) = p->hair; + WBUFW(buf,56) = p->option&0x20 ? 0 : p->weapon; //When the weapon is sent and your option is riding, the client crashes on login!? + WBUFW(buf,58) = p->base_level; + WBUFW(buf,60) = min(p->skill_point, SHRT_MAX); + WBUFW(buf,62) = p->head_bottom; + WBUFW(buf,64) = p->shield; + WBUFW(buf,66) = p->head_top; + WBUFW(buf,68) = p->head_mid; + WBUFW(buf,70) = p->hair_color; + WBUFW(buf,72) = p->clothes_color; + memcpy(WBUFP(buf,74), p->name, NAME_LENGTH); + WBUFB(buf,98) = min(p->str, UCHAR_MAX); + WBUFB(buf,99) = min(p->agi, UCHAR_MAX); + WBUFB(buf,100) = min(p->vit, UCHAR_MAX); + WBUFB(buf,101) = min(p->int_, UCHAR_MAX); + WBUFB(buf,102) = min(p->dex, UCHAR_MAX); + WBUFB(buf,103) = min(p->luk, UCHAR_MAX); + WBUFW(buf,104) = p->slot; + if (char_rename) { + WBUFW(buf,106) = 1;// Rename bit (0=rename,1=no rename) + return 108; + } else { + return 106; + } +} + +//---------------------------------------- +// Function to send characters to a player +//---------------------------------------- +int mmo_char_send006b(int fd, struct char_session_data* sd) +{ + int i, j, found_num; + + found_num = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == sd->account_id) { + sd->found_char[found_num] = i; + if( ++found_num == MAX_CHARS ) + break; + } + } + for(i = found_num; i < MAX_CHARS; i++) + sd->found_char[i] = -1; + + j = 24; // offset + WFIFOHEAD(fd,j + found_num*108); // or 106(!) + WFIFOW(fd,0) = 0x6b; + memset(WFIFOP(fd,4), 0, 20); // unknown bytes + for(i = 0; i < found_num; i++) + j += mmo_char_tobuf(WFIFOP(fd,j), &char_dat[sd->found_char[i]].status); + WFIFOW(fd,2) = j; // packet len + WFIFOSET(fd,j); + + return 0; +} + +// 離婚(char削除時に使用) +int char_divorce(struct mmo_charstatus *cs) +{ + if (cs == NULL) + return 0; + + if (cs->partner_id > 0){ + int i, j; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == cs->partner_id && char_dat[i].status.partner_id == cs->char_id) { + cs->partner_id = 0; + char_dat[i].status.partner_id = 0; + for(j = 0; j < MAX_INVENTORY; j++) + if (char_dat[i].status.inventory[j].nameid == WEDDING_RING_M || char_dat[i].status.inventory[j].nameid == WEDDING_RING_F) + memset(&char_dat[i].status.inventory[j], 0, sizeof(char_dat[i].status.inventory[0])); + if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; + } + } + } + return 0; +} + +int char_married(int pl1,int pl2) +{ + return (char_dat[pl1].status.char_id == char_dat[pl2].status.partner_id && char_dat[pl2].status.char_id == char_dat[pl1].status.partner_id); +} + +int char_child(int parent_id, int child_id) +{ + return (char_dat[parent_id].status.child == char_dat[child_id].status.char_id && + ((char_dat[parent_id].status.char_id == char_dat[child_id].status.father) || + (char_dat[parent_id].status.char_id == char_dat[child_id].status.mother))); +} + +int char_family(int cid1, int cid2, int cid3) +{ + int i, idx1 = -1, idx2 =-1;//, idx3 =-1; + for(i = 0; i < char_num && (idx1 == -1 || idx2 == -1/* || idx3 == 1*/); i++) + { + if (char_dat[i].status.char_id == cid1) + idx1 = i; + if (char_dat[i].status.char_id == cid2) + idx2 = i; +// if (char_dat[i].status.char_id == cid2) +// idx3 = i; + } + if (idx1 == -1 || idx2 == -1/* || idx3 == -1*/) + return 0; //Some character not found?? + + //Unless the dbs are corrupted, these 3 checks should suffice, even though + //we could do a lot more checks and force cross-reference integrity. + if(char_dat[idx1].status.partner_id == cid2 && + char_dat[idx1].status.child == cid3) + return cid3; //cid1/cid2 parents. cid3 child. + + if(char_dat[idx1].status.partner_id == cid3 && + char_dat[idx1].status.child == cid2) + return cid2; //cid1/cid3 parents. cid2 child. + + if(char_dat[idx2].status.partner_id == cid3 && + char_dat[idx2].status.child == cid1) + return cid1; //cid2/cid3 parents. cid1 child. + return 0; +} + +//Clears the given party id from all characters. +//Since sometimes the party format changes and parties must be wiped, this +//method is required to prevent stress during the "party not found!" stages. +void char_clearparty(int party_id) +{ + int i; + for(i = 0; i < char_num; i++) + { + if (char_dat[i].status.party_id == party_id) + char_dat[i].status.party_id = 0; + } +} + +//---------------------------------------------------------------------- +// Force disconnection of an online player (with account value) by [Yor] +//---------------------------------------------------------------------- +int disconnect_player(int account_id) +{ + int i; + struct char_session_data *sd; + + // 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 == account_id) { + set_eof(i); + return 1; + } + } + } + + return 0; +} + +// キャラ削除に伴うデータ削除 +static int char_delete(struct mmo_charstatus *cs) +{ + int j; + + // ペット削除 + if (cs->pet_id) + inter_pet_delete(cs->pet_id); + if (cs->hom_id) + inter_homun_delete(cs->hom_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) + inter_pet_delete(MakeDWord(cs->inventory[j].card[1],cs->inventory[j].card[2])); + for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(cs->cart[j].card[1],cs->cart[j].card[2]) ); + // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) + inter_party_leave(cs->party_id, cs->account_id, cs->char_id); + // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 + unsigned char buf[10]; + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; + mapif_sendall(buf,10); + // 離婚 + char_divorce(cs); + } +#ifdef ENABLE_SC_SAVING + status_delete_scdata(cs->account_id, cs->char_id); +#endif + return 0; +} + +static void char_auth_ok(int fd, struct char_session_data *sd) +{ + struct online_char_data* character; + if (max_connect_user && count_users() >= max_connect_user && isGM(sd->account_id) < gm_allow_level) + { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + return; + } + + if( online_check && (character = (struct online_char_data*)idb_get(online_char_db, sd->account_id)) != NULL ) + { // check if character is not online already. [Skotlex] + if (character->server > -1) + { //Character already online. KICK KICK KICK + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 2); + if (character->waiting_disconnect == -1) + character->waiting_disconnect = add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + return; + } + if (character->fd >= 0 && character->fd != fd) + { //There's already a connection from this account that hasn't picked a char yet. + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + return; + } + character->fd = fd; + } + + if (login_fd > 0) { + // request to login-server to obtain e-mail/time limit + //FIXME: isn't this part of the auth_ok packet? [ultramage] + WFIFOHEAD(login_fd,6); + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + + // mark session as 'authed' + sd->auth = true; + + // set char online on charserver + set_char_online(-1, 99, sd->account_id); + + // send characters to player + mmo_char_send006b(fd, sd); +} + +int send_accounts_tologin(int tid, unsigned int tick, int id, int data); + +int parse_fromlogin(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. + if( fd != login_fd ) + set_eof(fd); + + if(session[fd]->flag.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; + + while(RFIFOREST(fd) >= 2) + { + uint16 command = RFIFOW(fd,0); + + switch( command ) + { + + // acknowledgement of connect-to-loginserver request + 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"); + ShowInfo("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); + ShowInfo("The communication passwords can be changed in map_athena.conf and char_athena.conf\n"); + } else { + ShowStatus("Connected to login-server (connection #%d).\n", fd); + + //Send online accounts to login server. + send_accounts_tologin(-1, gettick(), 0, 0); + + // if no map-server already connected, display a message... + ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd > 0 && server[i].map[0] ); + if( i == MAX_MAP_SERVERS ) + ShowStatus("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd,3); + break; + + // acknowledgement of account authentication request + case 0x2713: + if (RFIFOREST(fd) < 59) + return 0; + { + int account_id = RFIFOL(fd,2); + int login_id1 = RFIFOL(fd,6); + int login_id2 = RFIFOL(fd,10); + bool result = RFIFOB(fd,14); + const char* email = (const char*)RFIFOP(fd,15); + time_t expiration_time = (time_t)RFIFOL(fd,55); + + // find the session with this account id + ARR_FIND( 0, fd_max, i, session[i] && (sd = (struct char_session_data*)session[i]->session_data) && + sd->account_id == account_id && sd->login_id1 == login_id1 && sd->login_id2 == login_id2 ); + if( i < fd_max ) + { + if( result ) { // failure + WFIFOHEAD(i,3); + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else { // success + memcpy(sd->email, email, 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->expiration_time = expiration_time; + char_auth_ok(i, sd); + } + } + } + RFIFOSKIP(fd,59); + break; + + // Receiving of an e-mail/time limit from the login-server (answer of a request because a player comes back from map-server to char-server) by [Yor] + 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)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->expiration_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; + + // changesex reply + case 0x2723: + if (RFIFOREST(fd) < 7) + return 0; + { + int i, j; + unsigned char buf[7]; + + int acc = RFIFOL(fd,2); + int sex = RFIFOB(fd,6); + RFIFOSKIP(fd,7); + + if( acc > 0 ) + { + struct auth_node* node = (struct auth_node*)idb_get(auth_db, acc); + if( node != NULL ) + node->sex = sex; + + ARR_FIND( 0, char_num, i, char_dat[i].status.account_id == acc ); + if( i < char_num ) + { + int jobclass = char_dat[i].status.class_; + char_dat[i].status.sex = sex; + if (jobclass == JOB_BARD || jobclass == JOB_DANCER || + jobclass == JOB_CLOWN || jobclass == JOB_GYPSY || + jobclass == JOB_BABY_BARD || jobclass == JOB_BABY_DANCER) { + // job modification + if (jobclass == JOB_BARD || jobclass == JOB_DANCER) { + char_dat[i].status.class_ = (sex) ? JOB_BARD : JOB_DANCER; + } else if (jobclass == JOB_CLOWN || jobclass == JOB_GYPSY) { + char_dat[i].status.class_ = (sex) ? JOB_CLOWN : JOB_GYPSY; + } else if (jobclass == JOB_BABY_BARD || jobclass == JOB_BABY_DANCER) { + char_dat[i].status.class_ = (sex) ? JOB_BABY_BARD : JOB_BABY_DANCER; + } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { + if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) { + if (char_dat[i].status.skill_point > USHRT_MAX - char_dat[i].status.skill[j].lv) + char_dat[i].status.skill_point = USHRT_MAX; + else + char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv; + char_dat[i].status.skill[j].id = 0; + char_dat[i].status.skill[j].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { + if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) { + if (char_dat[i].status.skill_point > USHRT_MAX - char_dat[i].status.skill[j].lv) + char_dat[i].status.skill_point = USHRT_MAX; + else + char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv; + + char_dat[i].status.skill[j].id = 0; + char_dat[i].status.skill[j].lv = 0; + } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { + if (char_dat[i].status.inventory[j].nameid && char_dat[i].status.inventory[j].equip) + char_dat[i].status.inventory[j].equip = 0; + } + char_dat[i].status.weapon = 0; + char_dat[i].status.shield = 0; + char_dat[i].status.head_top = 0; + char_dat[i].status.head_mid = 0; + char_dat[i].status.head_bottom = 0; + + if (char_dat[i].status.guild_id) //If there is a guild, update the guild_member data [Skotlex] + inter_guild_sex_changed(char_dat[i].status.guild_id, acc, char_dat[i].status.char_id, sex); + } + // disconnect player if online on char-server + disconnect_player(acc); + } + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + mapif_sendall(buf, 7); + } + break; + + case 0x2726: // Request to send a broadcast message (no answer) + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + if (RFIFOL(fd,4) < 1) + char_log("Receiving a message for broadcast, but message is void.\n"); + else + { + // at least 1 map-server + ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd >= 0 ); + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online.\n"); + else { + unsigned char buf[128]; + char message[4096]; // +1 to add a null terminated if not exist in the packet + int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars(message); + // remove all first spaces + p = message; + while(p[0] == ' ') + p++; + // if message is only composed of spaces + if (p[0] == '\0') + char_log("Receiving a message for broadcast, but message is only a lot of spaces.\n"); + // else send message to all map-servers + else { + if (RFIFOW(fd,2) == 0) { + char_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s)\n", + message); + lp = 4; + } else { + char_log("'ladmin': Receiving a message for broadcast (message (in blue): %s)\n", + message); + lp = 8; + } + // split message to max 80 char + while(p[0] != '\0') { // if not finish + if (p[0] == ' ') // jump if first char is a space + p++; + else { + char split[80]; + char* last_space; + sscanf(p, "%79[^\t]", split); // max 79 char, any char (\t is control char and control char was removed before) + split[sizeof(split)-1] = '\0'; // last char always \0 + if ((last_space = strrchr(split, ' ')) != NULL) { // searching space from end of the string + last_space[0] = '\0'; // replace it by NULL to have correct length of split + p++; // to jump the new NULL + } + p += strlen(split); + // send broadcast to all map-servers + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = lp + strlen(split) + 1; + WBUFL(buf,4) = 0x65756c62; // only write if in blue (lp = 8) + memcpy(WBUFP(buf,lp), split, strlen(split) + 1); + mapif_sendall(buf, WBUFW(buf,2)); + } + } + } + } + } + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + 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; + + // Account deletion notification (from login-server) + case 0x2730: + if (RFIFOREST(fd) < 6) + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,2)) { + char_delete(&char_dat[i].status); + if (i < char_num - 1) { + memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct character_data)); + // if moved character owns to deleted account, check again it's character + if (char_dat[i].status.account_id == RFIFOL(fd,2)) { + i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].status.account_id) { + for (k = 0; k < MAX_CHARS; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; + break; + } + } + break; + } + } + } + } + char_num--; + } + } + // Deletion of the storage + inter_storage_delete(RFIFOL(fd,2)); + // send to all map-servers to disconnect the player + { + unsigned char buf[6]; + WBUFW(buf,0) = 0x2b13; + WBUFL(buf,2) = RFIFOL(fd,2); + mapif_sendall(buf, 6); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + 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[11]; + 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 + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,11); + break; + + // Receiving GM acounts info from login-server (by [Yor]) + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + unsigned char buf[32000]; //FIXME: this will crash + if (gm_account != NULL) + aFree(gm_account); + CREATE(gm_account, struct gm_account, (RFIFOW(fd,2) - 4)/5); + 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); + char_log("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; + + // Login server request to kick a character out. [Skotlex] + case 0x2734: + if (RFIFOREST(fd) < 6) + return 0; + { + int aid = RFIFOL(fd,2); + struct online_char_data* character = (struct online_char_data*)idb_get(online_char_db, aid); + RFIFOSKIP(fd,6); + if( character != NULL ) + {// account is already marked as online! + if( character->server > -1 ) + { //Kick it from the map server it is on. + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 2); + if (character->waiting_disconnect == -1) + character->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, chardb_waiting_disconnect, character->account_id, 0); + } + else + {// Manual kick from char server. + struct char_session_data *tsd; + int i; + ARR_FIND( 0, fd_max, i, session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid ); + if( i < fd_max ) + { + WFIFOHEAD(i,3); + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; // "Someone has already logged in with this id" + WFIFOSET(i,3); + set_eof(i); + } + else //Shouldn't happen, but just in case. + set_char_offline(99, aid); + } + } + } + break; + + case 0x2735: + { + unsigned char buf[2]; + uint32 new_ip = 0; + + WBUFW(buf,0) = 0x2b1e; + mapif_sendall(buf, 2); + + new_ip = host2ip(login_ip_str); + if (new_ip && new_ip != login_ip) + login_ip = new_ip; //Update login up. + + new_ip = host2ip(char_ip_str); + if (new_ip && new_ip != char_ip) + { //Update ip. + char_ip = new_ip; + ShowInfo("Updating IP for [%s].\n", char_ip_str); + WFIFOHEAD(fd,6); + WFIFOW(fd,0) = 0x2736; + WFIFOL(fd,2) = htonl(char_ip); + WFIFOSET(fd,6); + } + + RFIFOSKIP(fd,2); + } + break; + + default: + ShowError("Unknown packet 0x%04x received from login-server, disconnecting.\n", RFIFOW(fd,0)); + set_eof(fd); + return 0; + } + } + + RFIFOFLUSH(fd); + return 0; +} + +int request_accreg2(int account_id, int char_id) +{ + if (login_fd > 0) { + WFIFOHEAD(login_fd,10); + 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; +} + +//Receive Registry information for a character. +int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int buf_len) +{ + int i,j,p,len; + for (i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id) + break; + } + if(i >= char_num) //Character not found? + return 1; + for(j=0,p=0;j<GLOBAL_REG_NUM && p<buf_len;j++){ + sscanf((char*)WBUFP(buf,p), "%31c%n",char_dat[i].global[j].str,&len); + char_dat[i].global[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf((char*)WBUFP(buf,p), "%255c%n",char_dat[i].global[j].value,&len); + char_dat[i].global[j].value[len]='\0'; + p +=len+1; + } + char_dat[i].global_num = j; + return 0; +} + +//Reply to map server with acc reg values. +int char_account_reg_reply(int fd,int account_id,int char_id) +{ + int i,j,p; + WFIFOHEAD(fd, GLOBAL_REG_NUM*288 + 13); + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=char_id; + WFIFOB(fd,12)=3; //Type 3: char acc reg. + for (i = 0;i < char_num; i++) { + if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id) + break; + } + if(i >= char_num){ //Character not found? Sent empty packet. + WFIFOW(fd,2)=13; + }else{ + for (p=13,j = 0; j < char_dat[i].global_num; j++) { + if (char_dat[i].global[j].str[0]) { + p+= sprintf((char*)WFIFOP(fd,p), "%s", char_dat[i].global[j].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf((char*)WFIFOP(fd,p), "%s", char_dat[i].global[j].value)+1; + } + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +void char_read_fame_list(void) +{ + int i, j, k; + struct fame_list fame_item; + CREATE_BUFFER(id, int, char_num); + + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if (char_dat[i].status.fame > char_dat[id[j]].status.fame) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Empty ranking lists + memset(smith_fame_list, 0, sizeof(smith_fame_list)); + memset(chemist_fame_list, 0, sizeof(chemist_fame_list)); + memset(taekwon_fame_list, 0, sizeof(taekwon_fame_list)); + // Build Blacksmith ranking list + for (i = 0, j = 0; i < char_num && j < fame_list_size_smith; i++) { + if (char_dat[id[i]].status.fame && ( + char_dat[id[i]].status.class_ == JOB_BLACKSMITH || + char_dat[id[i]].status.class_ == JOB_WHITESMITH || + char_dat[id[i]].status.class_ == JOB_BABY_BLACKSMITH)) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(&smith_fame_list[j],&fame_item,sizeof(struct fame_list)); + j++; + } + } + // Build Alchemist ranking list + for (i = 0, j = 0; i < char_num && j < fame_list_size_chemist; i++) { + if (char_dat[id[i]].status.fame && ( + char_dat[id[i]].status.class_ == JOB_ALCHEMIST || + char_dat[id[i]].status.class_ == JOB_CREATOR || + char_dat[id[i]].status.class_ == JOB_BABY_ALCHEMIST)) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(&chemist_fame_list[j],&fame_item,sizeof(struct fame_list)); + + j++; + } + } + // Build Taekwon ranking list + for (i = 0, j = 0; i < char_num && j < fame_list_size_taekwon; i++) { + if (char_dat[id[i]].status.fame && + char_dat[id[i]].status.class_ == JOB_TAEKWON) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(&taekwon_fame_list[j],&fame_item,sizeof(struct fame_list)); + + j++; + } + } + DELETE_BUFFER(id); +} +// Send map-servers the fame ranking lists +int char_send_fame_list(int fd) +{ + int i, len = 8; + unsigned char buf[32000]; + + WBUFW(buf,0) = 0x2b1b; + + for(i = 0; i < fame_list_size_smith && smith_fame_list[i].id; i++) { + memcpy(WBUFP(buf, len), &smith_fame_list[i], sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + // add blacksmith's block length + WBUFW(buf, 6) = len; + + for(i = 0; i < fame_list_size_chemist && chemist_fame_list[i].id; i++) { + memcpy(WBUFP(buf, len), &chemist_fame_list[i], sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + // add alchemist's block length + WBUFW(buf, 4) = len; + + for(i = 0; i < fame_list_size_taekwon && taekwon_fame_list[i].id; i++) { + memcpy(WBUFP(buf, len), &taekwon_fame_list[i], sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + // add total packet length + WBUFW(buf, 2) = len; + + if(fd!=-1) + mapif_send(fd, buf, len); + else + mapif_sendall(buf, len); + + return 0; +} + +void char_update_fame_list(int type, int index, int fame) +{ + unsigned char buf[9]; + WBUFW(buf,0) = 0x2b22; + WBUFB(buf,2) = type; + WBUFB(buf,3) = index; + WBUFL(buf,4) = fame; + mapif_sendall(buf, 8); +} + +//Loads a character's name and stores it in the buffer given (must be NAME_LENGTH in size) +//Returns 1 on found, 0 on not found (buffer is filled with Unknown char name) +int char_loadName(int char_id, char* name) +{ + int j; + for( j = 0; j < char_num && char_dat[j].status.char_id != char_id; ++j ) + ;// find char + if( j < char_num ) + strncpy(name, char_dat[j].status.name, NAME_LENGTH); + else + strncpy(name, unknown_char_name, NAME_LENGTH); + + return (j < char_num) ? 1 : 0; +} + +int search_mapserver(unsigned short map, uint32 ip, uint16 port); + +int parse_frommap(int fd) +{ + int i, j; + int id; + + ARR_FIND( 0, MAX_MAP_SERVERS, id, server[id].fd == fd ); + if(id == MAX_MAP_SERVERS) + set_eof(fd); + if(session[fd]->flag.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) = htonl(server[id].ip); + WBUFW(buf,8) = htons(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)); + } + server[id].fd = -1; + online_char_db->foreach(online_char_db,char_db_setoffline,i); //Tag relevant chars as 'in disconnected' server. + } + do_close(fd); + create_online_files(); + return 0; + } + + while(RFIFOREST(fd) >= 2) + { + //ShowDebug("Received packet 0x%4x (%d bytes) from map-server (connection %d)\n", RFIFOW(fd, 0), RFIFOREST(fd), fd); + + switch(RFIFOW(fd,0)) + { + + case 0x2af7: // request from map-server to reload GM accounts. Transmission to login-server + if (login_fd > 0) { // don't send request if no login-server + WFIFOHEAD(login_fd,2); + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd,2); + } + RFIFOSKIP(fd,2); + break; + + case 0x2afa: // Receiving map names list from the map-server + 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); + j++; + } + + ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, CONVIP(server[id].ip), server[id].port); + ShowStatus("Map-server %d loading complete.\n", id); + char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete.\n", + id, j, CONVIP(server[id].ip), server[id].port, id); + + // send name for wisp to player + WFIFOHEAD(fd, 3 + NAME_LENGTH); + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); + WFIFOSET(fd,3+NAME_LENGTH); + + char_send_fame_list(fd); //Send fame list. + + { + unsigned char buf[16384]; + int x; + if (j == 0) { + ShowWarning("Map-server %d has NO maps.\n", id); + char_log("WARNING: Map-server %d has NO maps.\n", id); + } else { + // Transmitting maps information to the other map-servers + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 4 + 10; + WBUFL(buf,4) = htonl(server[id].ip); + WBUFW(buf,8) = htons(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[x].fd > 0 && x != id) { + WFIFOHEAD(fd,10 +4*MAX_MAP_PER_SERVER); + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = htonl(server[x].ip); + WFIFOW(fd,8) = htons(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; + + case 0x2afc: //Packet command is now used for sc_data request. [Skotlex] + if (RFIFOREST(fd) < 10) + return 0; + { +#ifdef ENABLE_SC_SAVING + int aid, cid; + struct scdata *data; + aid = RFIFOL(fd,2); + cid = RFIFOL(fd,6); + data = status_search_scdata(aid, cid); + if (data->count > 0) + { //Deliver status change data. + WFIFOW(fd,0) = 0x2b1d; + WFIFOW(fd,2) = 14 + data->count*sizeof(struct status_change_data); + WFIFOL(fd,4) = aid; + WFIFOL(fd,8) = cid; + WFIFOW(fd,12) = data->count; + for (i = 0; i < data->count; i++) + memcpy(WFIFOP(fd,14+i*sizeof(struct status_change_data)), &data->data[i], sizeof(struct status_change_data)); + WFIFOSET(fd, WFIFOW(fd,2)); + status_delete_scdata(aid, cid); //Data sent, so it needs be discarded now. + } +#endif + RFIFOSKIP(fd, 10); + } + break; + + case 0x2afe: //set MAP user count + 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; + + case 0x2aff: //set MAP users + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //TODO: When data mismatches memory, update guild/party online/offline states. + server[id].users = RFIFOW(fd,4); + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown' + for(i = 0; i < server[id].users; i++) { + int aid, cid; + struct online_char_data* character; + aid = RFIFOL(fd,6+i*8); + cid = RFIFOL(fd,6+i*8+4); + character = (struct online_char_data*)idb_ensure(online_char_db, aid, create_online_char_data); + if (online_check && 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[character->server].fd, character->account_id, character->char_id, 2); + } + character->char_id = cid; + character->server = id; + } + //If any chars remain in -2, they will be cleaned in the cleanup timer. + RFIFOSKIP(fd,6+i*8); + break; + + case 0x2b01: // Receive character data from map-server for saving + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,4) && + char_dat[i].status.char_id == RFIFOL(fd,8)) + break; + } + if (i != char_num) + memcpy(&char_dat[i].status, RFIFOP(fd,13), sizeof(struct mmo_charstatus)); + if (RFIFOB(fd,12)) + { //Flag, set character offline. [Skotlex] + set_char_offline(RFIFOL(fd,8),RFIFOL(fd,4)); + WFIFOW(fd,0) = 0x2b21; //Save ack only needed on final save. + WFIFOL(fd,2) = RFIFOL(fd,4); + WFIFOL(fd,6) = RFIFOL(fd,8); + WFIFOSET(fd,10); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x2b02: // req char selection + if( RFIFOREST(fd) < 18 ) + return 0; + { + struct auth_node* node; + + int account_id = RFIFOL(fd,2); + uint32 login_id1 = RFIFOL(fd,6); + uint32 login_id2 = RFIFOL(fd,10); + uint32 ip = RFIFOL(fd,14); + RFIFOSKIP(fd,18); + + // create temporary auth entry + CREATE(node, struct auth_node, 1); + node->account_id = account_id; + node->char_id = 0; + node->login_id1 = login_id1; + node->login_id2 = login_id2; + //node->sex = 0; + node->ip = ntohl(ip); + node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server) + idb_put(auth_db, account_id, node); + + //Set char to "@ char select" in online db [Kevin] + set_char_online(-3, 99, account_id); + + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 0; + WFIFOSET(fd,7); + } + break; + + case 0x2b05: // request "change map server" + 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, ntohl(RFIFOL(fd,24)), ntohs(RFIFOW(fd,28))); //Locate mapserver by ip and port. + if (map_id >= 0) + map_fd = server[map_id].fd; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,2) && + char_dat[i].status.char_id == RFIFOL(fd,14)) + break; + } + char_data = i < char_num ? &char_dat[i].status : NULL; + //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); + char_data->sex = RFIFOB(fd,30); + + WFIFOHEAD(map_fd, 20 + sizeof(struct mmo_charstatus)); + 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: expiration_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 = (struct online_char_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. + WFIFOHEAD(fd,30); + WFIFOW(fd,0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOSET(fd,30); + } else { //Reply with nak + WFIFOHEAD(fd,30); + 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; + + case 0x2b08: // char name request + if (RFIFOREST(fd) < 6) + return 0; + + WFIFOHEAD(fd,30); + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + char_loadName((int)RFIFOL(fd,2), (char*)WFIFOP(fd,6)); + WFIFOSET(fd,30); + + RFIFOSKIP(fd,6); + break; + + case 0x2b0c: // Map server send information to change an email of an account -> login-server + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + WFIFOHEAD(login_fd,86); + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0),86); // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd,86); + } + RFIFOSKIP(fd, 86); + break; + + case 0x2b0e: // Request from map-server to change an account's status (will just be forwarded to login server) + if (RFIFOREST(fd) < 44) + return 0; + { + int result = 0; // 0-login-server request done, 1-player not found, 2-gm level too low, 3-login-server offline + char character_name[NAME_LENGTH]; + + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if server itself made this request) + const char* name = (char*)RFIFOP(fd,6); // name of the target character + int type = RFIFOW(fd,30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban + short year = RFIFOW(fd,32); + short month = RFIFOW(fd,34); + short day = RFIFOW(fd,36); + short hour = RFIFOW(fd,38); + short minute = RFIFOW(fd,40); + short second = RFIFOW(fd,42); + RFIFOSKIP(fd,44); + + safestrncpy(character_name, name, NAME_LENGTH); + i = search_character_index(character_name); + if( i < 0 ) + { + result = 1; // 1-player not found + } + else + { + char name[NAME_LENGTH]; + int account_id; + + account_id = char_dat[i].status.account_id; + safestrncpy(name, char_dat[i].status.name, NAME_LENGTH); + + if( login_fd <= 0 ) + result = 3; // 3-login-server offline + else + if( acc != -1 && isGM(acc) < isGM(account_id) ) + result = 2; // 2-gm level too low + else + switch( type ) { + case 1: // block + WFIFOHEAD(login_fd,10); + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = account_id; + WFIFOL(login_fd,6) = 5; // new account status + WFIFOSET(login_fd,10); + break; + case 2: // ban + WFIFOHEAD(login_fd,18); + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = account_id; + WFIFOW(login_fd, 6) = year; + WFIFOW(login_fd, 8) = month; + WFIFOW(login_fd,10) = day; + WFIFOW(login_fd,12) = hour; + WFIFOW(login_fd,14) = minute; + WFIFOW(login_fd,16) = second; + WFIFOSET(login_fd,18); + break; + case 3: // unblock + WFIFOHEAD(login_fd,10); + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = account_id; + WFIFOL(login_fd,6) = 0; // new account status + WFIFOSET(login_fd,10); + break; + case 4: // unban + WFIFOHEAD(login_fd,6); + WFIFOW(login_fd,0) = 0x272a; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + break; + case 5: // changesex + WFIFOHEAD(login_fd,6); + WFIFOW(login_fd,0) = 0x2727; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + break; + } + } + + // send answer if a player ask, not if the server ask + if( acc != -1 ) { + WFIFOHEAD(fd,34); + WFIFOW(fd, 0) = 0x2b0f; + WFIFOL(fd, 2) = acc; + safestrncpy((char*)WFIFOP(fd,6), name, NAME_LENGTH); + WFIFOW(fd,30) = type; + WFIFOW(fd,32) = result; + WFIFOSET(fd,34); + } + } + break; + + case 0x2b10: // Update and send fame ranking list + if (RFIFOREST(fd) < 11) + return 0; + { + int cid = RFIFOL(fd, 2); + int fame = RFIFOL(fd, 6); + char type = RFIFOB(fd, 10); + int size; + struct fame_list* list; + int player_pos; + int fame_pos; + + switch(type) + { + case 1: size = fame_list_size_smith; list = smith_fame_list; break; + case 2: size = fame_list_size_chemist; list = chemist_fame_list; break; + case 3: size = fame_list_size_taekwon; list = taekwon_fame_list; break; + default: size = 0; list = NULL; break; + } + + ARR_FIND(0, size, player_pos, list[player_pos].id == cid);// position of the player + ARR_FIND(0, size, fame_pos, list[fame_pos].fame <= fame);// where the player should be + + if( player_pos == size && fame_pos == size ) + ;// not on list and not enough fame to get on it + else if( fame_pos == player_pos ) + {// same position + list[player_pos].fame = fame; + char_update_fame_list(type, player_pos, fame); + } + else + {// move in the list + if( player_pos == size ) + {// new ranker - not in the list + ARR_MOVE(size - 1, fame_pos, list, struct fame_list); + list[fame_pos].id = cid; + list[fame_pos].fame = fame; + char_loadName(cid, list[fame_pos].name); + } + else + {// already in the list + if( fame_pos == size ) + --fame_pos;// move to the end of the list + ARR_MOVE(player_pos, fame_pos, list, struct fame_list); + list[fame_pos].fame = fame; + } + char_send_fame_list(-1); + } + + RFIFOSKIP(fd,11); + } + break; + + case 0x2b16: // Receive rates [Wizputer] + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + // Txt doesn't need this packet, so just skip it + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + + case 0x2b17: // Character disconnected set online 0 [Wizputer] + if (RFIFOREST(fd) < 6) + return 0; + set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + case 0x2b18: // Reset all chars to offline [Wizputer] + set_all_offline(id); + RFIFOSKIP(fd,2); + break; + + case 0x2b19: // Character set online [Wizputer] + if (RFIFOREST(fd) < 10) + return 0; + set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + case 0x2b1a: // Build and send fame ranking lists [DracoRPG] + if (RFIFOREST(fd) < 2) + return 0; + char_read_fame_list(); + char_send_fame_list(-1); + RFIFOSKIP(fd,2); + break; + + case 0x2b1c: //Request to save status change data. [Skotlex] + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +#ifdef ENABLE_SC_SAVING + int count, aid, cid; + struct scdata *data; + + aid = RFIFOL(fd, 4); + cid = RFIFOL(fd, 8); + count = RFIFOW(fd, 12); + + data = status_search_scdata(aid, cid); + if (data->count != count) + { + data->count = count; + data->data = (struct status_change_data*)aRealloc(data->data, count*sizeof(struct status_change_data)); + } + for (i = 0; i < count; i++) + memcpy (&data->data[i], RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data)); +#endif + RFIFOSKIP(fd, RFIFOW(fd, 2)); + } + break; + + case 0x2b23: // map-server alive packet + WFIFOHEAD(fd,2); + WFIFOW(fd,0) = 0x2b24; + WFIFOSET(fd,2); + RFIFOSKIP(fd,2); + break; + + case 0x2736: // ip address update + if (RFIFOREST(fd) < 6) return 0; + server[id].ip = ntohl(RFIFOL(fd, 2)); + ShowInfo("Updated IP address of map-server #%d to %d.%d.%d.%d.\n", id, CONVIP(server[id].ip)); + RFIFOSKIP(fd,6); + 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)); + set_eof(fd); + return 0; + } + } // switch + } // while + + return 0; +} + +// Searches for the mapserver that has a given map (and optionally ip/port, if not -1). +// If found, returns the server's index in the 'server' array (otherwise returns -1). +int search_mapserver(unsigned short map, uint32 ip, uint16 port) +{ + int i, j; + + for(i = 0; i < MAX_MAP_SERVERS; i++) + { + if (server[i].fd > 0 + && (ip == (uint32)-1 || server[i].ip == ip) + && (port == (uint16)-1 || server[i].port == port)) + { + for (j = 0; server[i].map[j]; j++) + if (server[i].map[j] == map) + return i; + } + } + + return -1; +} + +// char_mapifの初期化処理(現在はinter_mapif初期化のみ) +static int char_mapif_init(int fd) +{ + return inter_mapif_init(fd); +} + +//-------------------------------------------- +// Test to know if an IP come from LAN or WAN. +//-------------------------------------------- +int lan_subnetcheck(uint32 ip) +{ + int i; + ARR_FIND( 0, subnet_count, i, (subnet[i].char_ip & subnet[i].mask) == (ip & subnet[i].mask) ); + if ( i < subnet_count ) { + ShowInfo("Subnet check [%u.%u.%u.%u]: Matches "CL_CYAN"%u.%u.%u.%u/%u.%u.%u.%u"CL_RESET"\n", CONVIP(ip), CONVIP(subnet[i].char_ip & subnet[i].mask), CONVIP(subnet[i].mask)); + return subnet[i].char_ip; + } else { + ShowInfo("Subnet check [%u.%u.%u.%u]: "CL_CYAN"WAN"CL_RESET"\n", CONVIP(ip)); + return 0; + } +} + +int parse_char(int fd) +{ + int i, ch; + char email[40]; + unsigned short cmd; + int map_fd; + struct char_session_data* sd; + uint32 ipl = session[fd]->client_addr; + + sd = (struct char_session_data*)session[fd]->session_data; + + // disconnect any player if no login-server. + if(login_fd < 0) + set_eof(fd); + + if(session[fd]->flag.eof) + { + if( sd != NULL && sd->auth ) + { + struct online_char_data* data = (struct online_char_data*)idb_get(online_char_db, sd->account_id); + if( data == NULL || data->server == -1) //If it is not in any server, send it offline. [Skotlex] + set_char_offline(-1,sd->account_id); + if( data != NULL && data->fd == fd) + data->fd = -1; + } + do_close(fd); + return 0; + } + + while( RFIFOREST(fd) >= 2 ) + { + //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 || !sd->auth) { RFIFOSKIP(fd,rest); return 0; } } + + cmd = RFIFOW(fd,0); + switch( cmd ) + { + + // request to connect + // 0065 <account id>.L <login id1>.L <login id2>.L <???>.W <sex>.B + case 0x65: + if( RFIFOREST(fd) < 17 ) + return 0; + { + struct auth_node* node; + int GM_value; + + int account_id = RFIFOL(fd,2); + uint32 login_id1 = RFIFOL(fd,6); + uint32 login_id2 = RFIFOL(fd,10); + int sex = RFIFOB(fd,16); + RFIFOSKIP(fd,17); + + ShowInfo("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", account_id, login_id1, login_id2); + + if (sd) { + //Received again auth packet for already authentified account?? Discard it. + //TODO: Perhaps log this as a hack attempt? + //TODO: and perhaps send back a reply? + break; + } + + if( (GM_value = isGM(account_id)) != 0 ) + ShowInfo("Account Logged On; Account ID: %d (GM level %d).\n", account_id, GM_value); + else + ShowInfo("Account Logged On; Account ID: %d.\n", account_id); + + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = (struct char_session_data*)session[fd]->session_data; + strncpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail + sd->expiration_time = 0; // unknown or unlimited (not displaying on map-server) + sd->account_id = account_id; + sd->login_id1 = login_id1; + sd->login_id2 = login_id2; + sd->sex = sex; + sd->auth = false; // not authed yet + + // send back account_id + WFIFOHEAD(fd,4); + WFIFOL(fd,0) = account_id; + WFIFOSET(fd,4); + + // search authentification + node = (struct auth_node*)idb_get(auth_db, account_id); + if( node != NULL && + node->account_id == account_id && + node->login_id1 == login_id1 && + node->login_id2 == login_id2 && + node->ip == ipl ) + {// authentication found (coming from map server) + idb_remove(auth_db, account_id); + char_auth_ok(fd, sd); + } + else + {// authentication not found (coming from login server) + if (login_fd > 0) { // don't send request if no login-server + WFIFOHEAD(login_fd,19); + 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) = htonl(ipl); + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x6c; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + break; + + // char select + case 0x66: + FIFOSD_CHECK(3); + { + struct mmo_charstatus *cd; + uint32 subnet_map_ip; + struct auth_node* node; + + int slot = RFIFOB(fd,2); + RFIFOSKIP(fd,3); + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd,3); + break; + } + // otherwise, load the character + ARR_FIND( 0, MAX_CHARS, ch, sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].status.slot == slot ); + if (ch == MAX_CHARS) + { //Not found?? May be forged packet. + break; + } + cd = &char_dat[sd->found_char[ch]].status; + char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s.\n", sd->account_id, slot, cd->name); + + cd->sex = sd->sex; + + // searching map server + i = search_mapserver(cd->last_point.map,-1,-1); + + // if map is not found, we check major cities + if (i < 0) { + unsigned short j; + //First check that there's actually a map server online. + ARR_FIND( 0, MAX_MAP_SERVERS, j, server[j].fd >= 0 && server[j].map[0] ); + if (j == MAX_MAP_SERVERS) { + ShowInfo("Connection Closed. No map servers available.\n"); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) { + cd->last_point.x = 273; + cd->last_point.y = 354; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) { + cd->last_point.x = 120; + cd->last_point.y = 100; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) { + cd->last_point.x = 160; + cd->last_point.y = 94; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) { + cd->last_point.x = 116; + cd->last_point.y = 57; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) { + cd->last_point.x = 87; + cd->last_point.y = 117; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) { + cd->last_point.x = 94; + cd->last_point.y = 103; + } else { + ShowInfo("Connection Closed. No map server available that has a major city, and unable to find map-server for '%s'.\n", mapindex_id2name(cd->last_point.map)); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + ShowWarning("Unable to find map-server for '%s', sending to major city '%s'.\n", mapindex_id2name(cd->last_point.map), mapindex_id2name(j)); + cd->last_point.map = j; + } + + //Send NEW auth packet [Kevin] + //FIXME: is this case even possible? [ultramage] + if ((map_fd = server[i].fd) < 1 || session[map_fd] == NULL) + { + ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i); + server[i].fd = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + //Send server closed. + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + + //Send player to map + WFIFOHEAD(fd,28); + WFIFOW(fd,0) = 0x71; + WFIFOL(fd,2) = cd->char_id; + mapindex_getmapname_ext(mapindex_id2name(cd->last_point.map), (char*)WFIFOP(fd,6)); + + // Advanced subnet check [LuzZza] + subnet_map_ip = lan_subnetcheck(ipl); + WFIFOL(fd,22) = htonl((subnet_map_ip) ? subnet_map_ip : server[i].ip); + WFIFOW(fd,26) = ntows(htons(server[i].port)); // [!] LE byte order here [!] + WFIFOSET(fd,28); + + ShowInfo("Character selection '%s' (account: %d, slot: %d).\n", cd->name, sd->account_id, ch); + + //Send auth ok to map server + WFIFOHEAD(map_fd,20 + sizeof(struct mmo_charstatus)); + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = sd->account_id; + WFIFOL(map_fd,8) = sd->login_id1; + WFIFOL(map_fd,16) = sd->login_id2; + WFIFOL(map_fd,12) = (unsigned long)sd->expiration_time; + memcpy(WFIFOP(map_fd,20), cd, sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + + // create temporary auth entry + CREATE(node, struct auth_node, 1); + node->account_id = sd->account_id; + node->char_id = cd->char_id; + node->login_id1 = sd->login_id1; + node->login_id2 = sd->login_id2; + node->sex = sd->sex; + node->expiration_time = sd->expiration_time; + node->ip = ipl; + idb_put(auth_db, sd->account_id, node); + } + break; + + // create new char + // S 0067 <name>.24B <str>.B <agi>.B <vit>.B <int>.B <dex>.B <luk>.B <slot>.B <hair color>.W <hair style>.W + case 0x67: + FIFOSD_CHECK(37); + + if( !char_new ) //turn character creation on/off [Kevin] + i = -2; + else + i = make_new_char(sd, (char*)RFIFOP(fd,2),RFIFOB(fd,26),RFIFOB(fd,27),RFIFOB(fd,28),RFIFOB(fd,29),RFIFOB(fd,30),RFIFOB(fd,31),RFIFOB(fd,32),RFIFOW(fd,33),RFIFOW(fd,35)); + + //'Charname already exists' (-1), 'Char creation denied' (-2) and 'You are underaged' (-3) + if (i < 0) + { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x6e; + switch (i) { + case -1: WFIFOB(fd,2) = 0x00; break; + case -2: WFIFOB(fd,2) = 0x02; break; + case -3: WFIFOB(fd,2) = 0x01; break; + } + WFIFOSET(fd,3); + } + else + { + int len; + // send to player + WFIFOHEAD(fd,110); + WFIFOW(fd,0) = 0x6d; + len = 2 + mmo_char_tobuf(WFIFOP(fd,2), &char_dat[i].status); + WFIFOSET(fd,len); + + // add new entry to the chars list + ARR_FIND( 0, MAX_CHARS, ch, sd->found_char[ch] == -1 ); + if( ch < MAX_CHARS ) + sd->found_char[ch] = i; // position of the new char in the char_dat[] array + } + + RFIFOSKIP(fd,37); + break; + + // delete char + case 0x68: + // 2004-04-19aSakexe+ langtype 12 char deletion packet + case 0x1fb: + if (cmd == 0x68) FIFOSD_CHECK(46); + if (cmd == 0x1fb) FIFOSD_CHECK(56); + { + int cid = RFIFOL(fd,2); + struct mmo_charstatus* cs = NULL; + ShowInfo(CL_RED"Request Char Deletion: "CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, cid); + memcpy(email, RFIFOP(fd,6), 40); + RFIFOSKIP(fd,RFIFOREST(fd)); // hack to make the other deletion packet work + + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail + + // BEGIN HACK: "change email using the char deletion 'confirm email' menu" + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + // if sended email is incorrect e-mail + if (strcmp(email, "a@a.com") == 0) { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd,3); + break; + } + // we change the packet to set it like selection. + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] != -1 && char_dat[sd->found_char[i]].status.char_id == cid ); + if( i < MAX_CHARS ) + { + // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) + WFIFOHEAD(login_fd,46); + WFIFOW(login_fd,0) = 0x2715; + WFIFOL(login_fd,2) = sd->account_id; + memcpy(WFIFOP(login_fd, 6), email, 40); + WFIFOSET(login_fd,46); + + // change value to put new packet (char selection) + RFIFOSKIP(fd,-3); //FIXME: Will this work? Messing with the received buffer is ugly anyway... + RFIFOW(fd,0) = 0x66; + RFIFOB(fd,2) = char_dat[sd->found_char[i]].status.slot; + // not send packet, it's modify of actual packet + } else { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd,3); + } + break; + } + // END HACK + + // otherwise, we delete the character + if (strcmpi(email, sd->email) != 0) { // if it's an invalid email + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd,3); + break; + } + + // check if this char exists + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] != -1 && char_dat[sd->found_char[i]].status.char_id == cid ); + if( i == MAX_CHARS ) + { // Such a character does not exist in the account + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + break; + } + + // deletion process + cs = &char_dat[sd->found_char[i]].status; + char_delete(cs); + if (sd->found_char[i] != char_num - 1) { + int j, k; + struct char_session_data *sd2; + memcpy(&char_dat[sd->found_char[i]], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // Correct moved character reference in the character's owner + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].status.account_id) { + for (k = 0; k < MAX_CHARS; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; + break; + } + } + break; + } + } + } + char_num--; + + // remove char from list and compact it + for(ch = i; ch < MAX_CHARS-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[MAX_CHARS-1] = -1; + + /* Char successfully deleted.*/ + WFIFOHEAD(fd,2); + WFIFOW(fd,0) = 0x6f; + WFIFOSET(fd,2); + } + break; + + // client keep-alive packet (every 12 seconds) + // R 0187 <account ID>.l + case 0x187: + if (RFIFOREST(fd) < 6) + return 0; + RFIFOSKIP(fd,6); + break; + + // char rename request + // R 028d <account ID>.l <char ID>.l <new name>.24B + case 0x28d: + FIFOSD_CHECK(34); + //not implemented + RFIFOSKIP(fd,34); + break; + + // login as map-server + case 0x2af8: + if (RFIFOREST(fd) < 60) + return 0; + { + char* l_user = (char*)RFIFOP(fd,2); + char* l_pass = (char*)RFIFOP(fd,26); + l_user[23] = '\0'; + l_pass[23] = '\0'; + ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd <= 0 ); + if (i == MAX_MAP_SERVERS || strcmp(l_user, userid) || strcmp(l_pass, passwd)) { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2af9; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } else { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2af9; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + + server[i].fd = fd; + server[i].ip = ntohl(RFIFOL(fd,54)); + server[i].port = ntohs(RFIFOW(fd,58)); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + session[fd]->func_parse = parse_frommap; + session[fd]->flag.server = 1; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers + WFIFOHEAD(fd,4+5*GM_num); + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,4+5*i) = gm_account[i].account_id; + WFIFOB(fd,4+5*i+4) = (unsigned char)gm_account[i].level; + } + WFIFOW(fd,2) = 4+5*GM_num; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + RFIFOSKIP(fd,60); + } + return 0; // avoid processing of followup packets here + + // Athena info get + case 0x7530: + WFIFOHEAD(fd,10); + 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); + break; + + // unknown packet received + default: + ShowError("parse_char: Received unknown packet "CL_WHITE"0x%x"CL_RESET" from ip '"CL_WHITE"%s"CL_RESET"'! Disconnecting!\n", RFIFOW(fd,0), ip2str(ipl, NULL)); + set_eof(fd); + return 0; + } + } + + RFIFOFLUSH(fd); + return 0; +} + +// Console Command Parser [Wizputer] +int parse_console(char* buf) +{ + char command[256]; + + memset(command, 0, sizeof(command)); + + sscanf(buf, "%[^\n]", command); + + //login_log("Console command :%s\n", command); + + if( strcmpi("shutdown", command) == 0 || + strcmpi("exit", command) == 0 || + strcmpi("quit", command) == 0 || + strcmpi("end", command) == 0 ) + runflag = 0; + else if( strcmpi("alive", command) == 0 || + strcmpi("status", command) == 0 ) + ShowInfo(CL_CYAN"Console: "CL_BOLD"I'm Alive."CL_RESET"\n"); + else if( strcmpi("help", command) == 0 ){ + ShowInfo(CL_BOLD"Help of commands:"CL_RESET"\n"); + ShowInfo(" To shutdown the server:\n"); + ShowInfo(" 'shutdown|exit|qui|end'\n"); + ShowInfo(" To know if server is alive:\n"); + ShowInfo(" 'alive|status'\n"); + } + + return 0; +} + +// 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendall(unsigned char *buf, unsigned int len) +{ + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server[i].fd) > 0) { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +// 自分以外の全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) +{ + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server[i].fd) > 0 && fd != sfd) { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} +// MAPサーバーにデータ送信(map鯖生存確認有り) +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[i].fd) { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int broadcast_user_count(int tid, unsigned int tick, int id, int data) +{ + uint8 buf[6]; + int users = count_users(); + + // only send an update when needed + static int prev_users = 0; + if( prev_users == users ) + return 0; + prev_users = users; + + if( login_fd > 0 && session[login_fd] ) + { + // send number of user to login server + WFIFOHEAD(login_fd,6); + 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); + + // refresh online files (txt and html) + create_online_files(); + + return 0; +} + +/// load this char's account id into the 'online accounts' packet +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*); + + if(character->server > -1) + { + 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) +{ + if (login_fd > 0 && session[login_fd]) + { + // send account list to login server + int users = online_char_db->size(online_char_db); + int i = 0; + + WFIFOHEAD(login_fd,8+users*4); + WFIFOW(login_fd,0) = 0x272d; + online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i, users); + WFIFOW(login_fd,2) = 8+ i*4; + WFIFOL(login_fd,4) = i; + 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) + return 0; + + 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_fromlogin; + session[login_fd]->flag.server = 1; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + WFIFOHEAD(login_fd,86); + WFIFOW(login_fd,0) = 0x2710; + memcpy(WFIFOP(login_fd,2), userid, 24); + memcpy(WFIFOP(login_fd,26), passwd, 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = htonl(char_ip); + WFIFOL(login_fd,58) = htons(char_port); + memcpy(WFIFOP(login_fd,60), 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); + + return 1; +} + +// sends a ping packet to login server (will receive pong 0x2718) +int ping_login_server(int tid, unsigned int tick, int id, int data) +{ + if (login_fd > 0 && session[login_fd] != NULL) + { + WFIFOHEAD(login_fd,2); + WFIFOW(login_fd,0) = 0x2719; + WFIFOSET(login_fd,2); + } + 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 = (struct online_char_data*)idb_get(online_char_db, id)) != NULL && character->waiting_disconnect == tid) + { //Mark it offline due to timeout. + character->waiting_disconnect = -1; + set_char_offline(character->char_id, character->account_id); + } + return 0; +} + +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->fd != -1) + return 0; //Character still connected + 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; +} + +//---------------------------------- +// Reading Lan Support configuration +// Rewrote: Anvanced subnet check [LuzZza] +//---------------------------------- +int char_lan_config_read(const char *lancfgName) +{ + FILE *fp; + int line_num = 0; + char line[1024], w1[64], w2[64], w3[64], w4[64]; + + if((fp = fopen(lancfgName, "r")) == NULL) { + ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName); + return 1; + } + + ShowInfo("Reading the configuration file %s...\n", lancfgName); + + while(fgets(line, sizeof(line), fp)) + { + line_num++; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n') + continue; + + if(sscanf(line,"%[^:]: %[^:]:%[^:]:%[^\r\n]", w1, w2, w3, w4) != 4) { + + ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num); + continue; + } + + remove_control_chars(w1); + remove_control_chars(w2); + remove_control_chars(w3); + remove_control_chars(w4); + + if( strcmpi(w1, "subnet") == 0 ) + { + subnet[subnet_count].mask = str2ip(w2); + subnet[subnet_count].char_ip = str2ip(w3); + subnet[subnet_count].map_ip = str2ip(w4); + + if( (subnet[subnet_count].char_ip & subnet[subnet_count].mask) != (subnet[subnet_count].map_ip & subnet[subnet_count].mask) ) + { + ShowError("%s: Configuration Error: The char server (%s) and map server (%s) belong to different subnetworks!\n", lancfgName, w3, w4); + continue; + } + + subnet_count++; + } + } + + ShowStatus("Read information about %d subnetworks.\n", subnet_count); + + fclose(fp); + return 0; +} +#endif //TXT_SQL_CONVERT + +int char_config_read(const char *cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE* fp = fopen(cfgName, "r"); + + if (fp == NULL) { + ShowError("Configuration file not found: %s.\n", cfgName); + return 1; + } + + ShowInfo("Reading configuration file %s...\n", cfgName); + while(fgets(line, sizeof(line), fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if(strcmpi(w1,"timestamp_format") == 0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); +#ifndef TXT_SQL_CONVERT + } else if(strcmpi(w1,"stdout_with_ansisequence")==0){ + stdout_with_ansisequence = config_switch(w2); + } else if (strcmpi(w1, "userid") == 0) { + strncpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + strncpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + strncpy(server_name, w2, 20); + 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) { + char ip_str[16]; + login_ip = host2ip(w2); + if (login_ip) { + strncpy(login_ip_str, w2, sizeof(login_ip_str)); + ShowStatus("Login server IP address : %s -> %s\n", w2, ip2str(login_ip, ip_str)); + } + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + char ip_str[16]; + char_ip = host2ip(w2); + if (char_ip){ + strncpy(char_ip_str, w2, sizeof(char_ip_str)); + ShowStatus("Character server IP address : %s -> %s\n", w2, ip2str(char_ip, ip_str)); + } + } else if (strcmpi(w1, "bind_ip") == 0) { + char ip_str[16]; + bind_ip = host2ip(w2); + if (bind_ip) { + strncpy(bind_ip_str, w2, sizeof(bind_ip_str)); + ShowStatus("Character server binding IP address : %s -> %s\n", w2, ip2str(bind_ip, ip_str)); + } + } 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 = (bool)atoi(w2); + } else if (strcmpi(w1, "char_new_display") == 0) { + char_new_display = atoi(w2); + } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "scdata_txt") == 0) { //By Skotlex + strcpy(scdata_txt, w2); +#endif + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); + } else if (strcmpi(w1, "friends_txt") == 0) { //By davidsiaw + strcpy(friends_txt, w2); + } else if (strcmpi(w1, "hotkeys_txt") == 0) { //By davidsiaw + strcpy(hotkeys_txt, w2); +#ifndef TXT_SQL_CONVERT + } 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, "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_EXT]; + int x, y; + if (sscanf(w2, "%15[^,],%d,%d", map, &x, &y) < 3) + continue; + 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.map = 0; + } + 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, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = (bool)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, "char_rename") == 0) { + char_rename = config_switch(w2); +// online files options + } else if (strcmpi(w1, "online_txt_filename") == 0) { + strcpy(online_txt_filename, w2); + } else if (strcmpi(w1, "online_html_filename") == 0) { + strcpy(online_html_filename, w2); + } else if (strcmpi(w1, "online_sorting_option") == 0) { + online_sorting_option = atoi(w2); + } else if (strcmpi(w1, "online_display_option") == 0) { + online_display_option = atoi(w2); + } else if (strcmpi(w1, "online_gm_display_min_level") == 0) { // minimum GM level to display 'GM' when we want to display it + online_gm_display_min_level = atoi(w2); + if (online_gm_display_min_level < 5) // send online file every 5 seconds to player is enough + online_gm_display_min_level = 5; + } else if (strcmpi(w1, "online_refresh_html") == 0) { + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; + } else if(strcmpi(w1,"db_path")==0) { + strcpy(db_path,w2); + } else if (strcmpi(w1, "console") == 0) { + console = config_switch(w2); + } else if (strcmpi(w1, "fame_list_alchemist") == 0) { + fame_list_size_chemist = atoi(w2); + if (fame_list_size_chemist > MAX_FAME_LIST) { + ShowWarning("Max fame list size is %d (fame_list_alchemist)\n", MAX_FAME_LIST); + fame_list_size_chemist = MAX_FAME_LIST; + } + } else if (strcmpi(w1, "fame_list_blacksmith") == 0) { + fame_list_size_smith = atoi(w2); + if (fame_list_size_smith > MAX_FAME_LIST) { + ShowWarning("Max fame list size is %d (fame_list_blacksmith)\n", MAX_FAME_LIST); + fame_list_size_smith = MAX_FAME_LIST; + } + } else if (strcmpi(w1, "fame_list_taekwon") == 0) { + fame_list_size_taekwon = atoi(w2); + if (fame_list_size_taekwon > MAX_FAME_LIST) { + ShowWarning("Max fame list size is %d (fame_list_taekwon)\n", MAX_FAME_LIST); + fame_list_size_taekwon = MAX_FAME_LIST; + } + } else if (strcmpi(w1, "guild_exp_rate") == 0) { + guild_exp_rate = atoi(w2); +#endif //TXT_SQL_CONVERT + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", cfgName); + return 0; +} + +#ifndef TXT_SQL_CONVERT +int chardb_final(int key, void* data, va_list va) +{ + aFree(data); + return 0; +} +void do_final(void) +{ + ShowStatus("Terminating server.\n"); + + mmo_char_sync(); + inter_save(); + set_all_offline(-1); + flush_fifos(); + // write online players files with no player + online_char_db->clear(online_char_db, NULL); //clean the db... + create_online_files(); + online_char_db->destroy(online_char_db, NULL); //dispose the db... + auth_db->destroy(auth_db, NULL); + + if(gm_account) aFree(gm_account); + if(char_dat) aFree(char_dat); + + if (login_fd > 0) + do_close(login_fd); + if (char_fd > 0) + do_close(char_fd); + +#ifdef ENABLE_SC_SAVING + status_final(); +#endif + inter_final(); + mapindex_final(); + + char_log("----End of char-server (normal end with closing of all files).\n"); +} + +//------------------------------ +// Function called when the server +// has received a crash signal. +//------------------------------ +void do_abort(void) +{ +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_CHAR; +} + +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[i].fd = -1; + } + + //Read map indexes + mapindex_init(); + start_point.map = mapindex_name2id("new_zone01"); + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + char_lan_config_read((argc > 3) ? argv[3] : LAN_CONF_NAME); + + if (strcmp(userid, "s1")==0 && strcmp(passwd, "p1")==0) { + ShowError("Using the default user/password s1/p1 is NOT RECOMMENDED.\n"); + ShowNotice("Please edit your save/account.txt file to create a proper inter-server user/password (gender 'S')\n"); + ShowNotice("And then change the user/password to use in conf/char_athena.conf (or conf/import/char_conf.txt)\n"); + } + + ShowInfo("Finished reading the char-server configuration.\n"); + + // a newline in the log... + char_log(""); + // moved behind char_config_read in case we changed the filename [celest] + char_log("The char-server starting...\n"); + + ShowInfo("Initializing char server.\n"); + auth_db = idb_alloc(DB_OPT_RELEASE_DATA); + online_char_db = idb_alloc(DB_OPT_RELEASE_DATA); + mmo_char_init(); + char_read_fame_list(); //Read fame lists. +#ifdef ENABLE_SC_SAVING + status_init(); +#endif + inter_init_txt((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + ShowInfo("char server initialized.\n"); + + set_defaultparse(parse_char); + + if ((naddr_ != 0) && (!login_ip || !char_ip)) + { + char ip_str[16]; + ip2str(addr_[0], ip_str); + + if (naddr_ > 1) + ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", ip_str); + else + ShowStatus("Defaulting to %s as our IP address\n", ip_str); + if (!login_ip) { + strcpy(login_ip_str, ip_str); + login_ip = str2ip(login_ip_str); + } + if (!char_ip) { + strcpy(char_ip_str, ip_str); + char_ip = str2ip(char_ip_str); + } + } + + // establish char-login connection if not present + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); + + // keep the char-login connection alive + add_timer_func_list(ping_login_server, "ping_login_server"); + add_timer_interval(gettick() + 1000, ping_login_server, 0, 0, ((int)stall_time-2) * 1000); + + // periodically update the overall user count on all mapservers + login server + add_timer_func_list(broadcast_user_count, "broadcast_user_count"); + add_timer_interval(gettick() + 1000, broadcast_user_count, 0, 0, 5 * 1000); + + // send a list of all online account IDs to login server + add_timer_func_list(send_accounts_tologin, "send_accounts_tologin"); + add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour + + // ??? + add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect"); + + // ??? + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 1000, online_data_cleanup, 0, 0, 600 * 1000); + + // periodic flush of all saved data to disk + add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); + add_timer_interval(gettick() + 1000, mmo_char_sync_timer, 0, 0, autosave_interval); + + if( console ) + { + //##TODO invoke a CONSOLE_START plugin event + } + + char_fd = make_listen_bind(bind_ip, char_port); + char_log("The char-server is ready (Server is listening on the port %d).\n", char_port); + ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port); + + return 0; +} + +#endif //TXT_SQL_CONVERT diff --git a/src/char/char.h b/src/char/char.h new file mode 100644 index 000000000..72077ac09 --- /dev/null +++ b/src/char/char.h @@ -0,0 +1,54 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#include "../common/mmo.h" + +#define START_CHAR_NUM 150000 +#define MAX_MAP_SERVERS 30 + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct character_data { + struct mmo_charstatus status; + int global_num; + struct global_reg global[GLOBAL_REG_NUM]; +}; + +struct mmo_charstatus* search_character(int aid, int cid); +struct mmo_charstatus* search_character_byname(char* character_name); +int search_character_index(char* character_name); +char* search_character_name(int index); +int search_character_online(int aid, int cid); + +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_married(int pl1,int pl2); +int char_child(int parent_id, int child_id); +int char_family(int cid1, int cid2, int cid3); +void char_clearparty(int party_id); + +int char_log(char *fmt, ...); + +int request_accreg2(int account_id, int char_id); +int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int len); +int save_accreg2(unsigned char *buf, int len); +int char_account_reg_reply(int fd,int account_id,int char_id); + +extern int char_name_option; +extern char char_name_letters[]; +extern int autosave_interval; +extern char db_path[]; +extern int guild_exp_rate; +extern int log_inter; +//Exported for use in the TXT-SQL converter. +extern char char_txt[]; +int char_config_read(const char *cfgName); +int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int *reg_num); +int parse_friend_txt(struct mmo_charstatus *p); + +#endif /* _CHAR_H_ */ diff --git a/src/char/int_guild.c b/src/char/int_guild.c new file mode 100644 index 000000000..e8379c130 --- /dev/null +++ b/src/char/int_guild.c @@ -0,0 +1,1579 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "char.h" +#include "inter.h" +#include "int_storage.h" +#include "int_guild.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +char guild_txt[1024] = "save/guild.txt"; +char castle_txt[1024] = "save/castle.txt"; + +#ifndef TXT_SQL_CONVERT +static DBMap* guild_db; // int guild_id -> struct guild* +static DBMap* castle_db; // int castle_id -> struct guild_castle* + +static int guild_newid = 10000; + +static unsigned int guild_exp[100]; + +int mapif_guild_broken(int guild_id, int flag); +static bool 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(DBKey key, void *data, va_list ap); + +/// serializes the guild data structure into the provided string +int inter_guild_tostr(char* str, struct guild* g) +{ + int i, c, len; + + // save guild base info + len = sprintf(str, "%d\t%s\t%s\t%d,%d,%u,%d,%d\t%s#\t%s#\t", + g->guild_id, g->name, g->master, g->guild_lv, g->max_member, g->exp, g->skill_point, 0, g->mes1, g->mes2); + + // save guild member info + for(i = 0; i < g->max_member; i++) + { + struct guild_member *m = &g->member[i]; + len += sprintf(str + len, "%d,%d,%d,%d,%d,%d,%d,%u,%d,%d\t%s\t", + m->account_id, m->char_id, + m->hair, m->hair_color, m->gender, + m->class_, m->lv, m->exp, m->exp_payper, m->position, + ((m->account_id > 0) ? m->name : "-")); + } + + // save guild position info + for(i = 0; i < MAX_GUILDPOSITION; i++) { + struct guild_position *p = &g->position[i]; + len += sprintf(str + len, "%d,%d\t%s#\t", p->mode, p->exp_mode, p->name); + } + + // save guild emblem + len += sprintf(str + len, "%d,%d,", g->emblem_len, g->emblem_id); + for(i = 0; i < g->emblem_len; i++) { + len += sprintf(str + len, "%02x", (unsigned char)(g->emblem_data[i])); + } + len += sprintf(str + len, "$\t"); + + // save guild alliance info + c = 0; + for(i = 0; i < MAX_GUILDALLIANCE; i++) + if (g->alliance[i].guild_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (a->guild_id > 0) + len += sprintf(str + len, "%d,%d\t%s\t", a->guild_id, a->opposition, a->name); + } + + // save guild expulsion info + c = 0; + for(i = 0; i < MAX_GUILDEXPULSION; i++) + if (g->expulsion[i].account_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDEXPULSION; i++) { + struct guild_expulsion *e = &g->expulsion[i]; + if (e->account_id > 0) + len += sprintf(str + len, "%d,%d,%d,%d\t%s\t%s\t%s#\t", + e->account_id, 0, 0, 0, e->name, "#", e->mes ); + } + + // save guild skill info + for(i = 0; i < MAX_GUILDSKILL; i++) { + len += sprintf(str + len, "%d,%d ", g->skill[i].id, g->skill[i].lv); + } + len += sprintf(str + len, "\t"); + + return 0; +} +#endif //TXT_SQL_CONVERT + +/// parses the guild data string into a guild data structure +int inter_guild_fromstr(char* str, struct guild* g) +{ + int i, c; + char *pstr; + + memset(g, 0, sizeof(struct guild)); + + {// load guild base info + int guildid; + char name[256]; // only 24 used + char master[256]; // only 24 used + int guildlv; + int max_member; + unsigned int exp; + int skpoint; + char mes1[256]; // only 60 used + char mes2[256]; // only 120 used + int len; + + if( sscanf(str, "%d\t%[^\t]\t%[^\t]\t%d,%d,%u,%d,%*d\t%[^\t]\t%[^\t]\t%n", + &guildid, name, master, &guildlv, &max_member, &exp, &skpoint, mes1, mes2, &len) < 9 ) + return 1; + + // remove '#' + mes1[strlen(mes1)-1] = '\0'; + mes2[strlen(mes2)-1] = '\0'; + + g->guild_id = guildid; + g->guild_lv = guildlv; + g->max_member = max_member; + g->exp = exp; + g->skill_point = skpoint; + safestrncpy(g->name, name, sizeof(g->name)); + safestrncpy(g->master, master, sizeof(g->master)); + safestrncpy(g->mes1, mes1, sizeof(g->mes1)); + safestrncpy(g->mes2, mes2, sizeof(g->mes2)); + + str+= len; + } + + {// load guild member info + int accountid; + int charid; + int hair, hair_color, gender; + int class_, lv; + unsigned int exp; + int exp_payper; + int position; + char name[256]; // only 24 used + int len; + int i; + + for( i = 0; i < g->max_member; i++ ) + { + struct guild_member* m = &g->member[i]; + if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%u,%d,%d\t%[^\t]\t%n", + &accountid, &charid, &hair, &hair_color, &gender, + &class_, &lv, &exp, &exp_payper, &position, + name, &len) < 11) + return 1; + + m->account_id = accountid; + m->char_id = charid; + m->hair = hair; + m->hair_color = hair_color; + m->gender = gender; + m->class_ = class_; + m->lv = lv; + m->exp = exp; + m->exp_payper = exp_payper; + m->position = position; + safestrncpy(m->name, name, NAME_LENGTH); + + str+= len; + } + } + + {// load guild position info + int mode, exp_mode; + char name[256]; // only 24 used + int len; + int i = 0; + int j; + + while (sscanf(str, "%d,%d%n", &mode, &exp_mode, &j) == 2 && str[j] == '\t') + { + struct guild_position *p = &g->position[i]; + if (sscanf(str, "%d,%d\t%[^\t]\t%n", &mode, &exp_mode, name, &len) < 3) + return 1; + + p->mode = mode; + p->exp_mode = exp_mode; + name[strlen(name)-1] = 0; + safestrncpy(p->name, name, NAME_LENGTH); + + i++; + str+= len; + } + } + + {// load guild emblem + int emblemlen; + int emblemid; + char emblem[4096]; + int len; + + emblemid = 0; + if( sscanf(str, "%d,%d,%[^\t]\t%n", &emblemlen, &emblemid, emblem, &len) < 3 ) + if( sscanf(str, "%d,%[^\t]\t%n", &emblemlen, emblem, &len) < 2 ) //! pre-svn format + return 1; + + g->emblem_len = emblemlen; + g->emblem_id = emblemid; + for(i = 0, pstr = emblem; i < g->emblem_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; + } + + str+= len; + } + + {// load guild alliance info + int guildid; + int opposition; + char name[256]; // only 24 used + int len; + + if (sscanf(str, "%d\t%n", &c, &len) < 1) + return 1; + str+= len; + + for(i = 0; i < c; i++) + { + struct guild_alliance* a = &g->alliance[i]; + if (sscanf(str, "%d,%d\t%[^\t]\t%n", &guildid, &opposition, name, &len) < 3) + return 1; + + a->guild_id = guildid; + a->opposition = opposition; + safestrncpy(a->name, name, NAME_LENGTH); + + str+= len; + } + } + + {// load guild expulsion info + int accountid; + char name[256]; // only 24 used + char message[256]; // only 40 used + int len; + int i; + + if (sscanf(str, "%d\t%n", &c, &len) < 1) + return 1; + str+= len; + + for(i = 0; i < c; i++) + { + struct guild_expulsion *e = &g->expulsion[i]; + if (sscanf(str, "%d,%*d,%*d,%*d\t%[^\t]\t%*[^\t]\t%[^\t]\t%n", &accountid, name, message, &len) < 3) + return 1; + + e->account_id = accountid; + safestrncpy(e->name, name, sizeof(e->name)); + message[strlen(message)-1] = 0; // remove '#' + safestrncpy(e->mes, message, sizeof(e->mes)); + + str+= len; + } + } + + {// load guild skill info + int skillid; + int skilllv; + int len; + int i; + + for(i = 0; i < MAX_GUILDSKILL; i++) + { + if (sscanf(str, "%d,%d %n", &skillid, &skilllv, &len) < 2) + break; + g->skill[i].id = skillid; + g->skill[i].lv = skilllv; + + str+= len; + } + str = strchr(str, '\t'); + } + + return 0; +} + +#ifndef TXT_SQL_CONVERT +// ギルド城データの文字列への変換 +int inter_guildcastle_tostr(char *str, struct guild_castle *gc) +{ + int len; + + len = sprintf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + 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); + + return 0; +} +#endif ///TXT_SQL_CONVERT + +// ギルド城データの文字列からの変換 +int inter_guildcastle_fromstr(char *str, struct guild_castle *gc) +{ + int castleid, guildid, economy, defense, triggerE, triggerD, nextTime, payTime, createTime, visibleC; + int guardian[8]; + int dummy; + + memset(gc, 0, sizeof(struct guild_castle)); + // structure of guild castle with the guardian hp included + if( sscanf(str, "%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", + &castleid, &guildid, &economy, &defense, &triggerE, &triggerD, &nextTime, &payTime, &createTime, &visibleC, + &guardian[0], &guardian[1], &guardian[2], &guardian[3], &guardian[4], &guardian[5], &guardian[6], &guardian[7], + &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy) != 26 ) + // structure of guild castle without the hps (current one) + if( sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &castleid, &guildid, &economy, &defense, &triggerE, &triggerD, &nextTime, &payTime, &createTime, &visibleC, + &guardian[0], &guardian[1], &guardian[2], &guardian[3], &guardian[4], &guardian[5], &guardian[6], &guardian[7]) != 18 ) + return 1; + + gc->castle_id = castleid; + gc->guild_id = guildid; + gc->economy = economy; + gc->defense = defense; + gc->triggerE = triggerE; + gc->triggerD = triggerD; + gc->nextTime = nextTime; + gc->payTime = payTime; + gc->createTime = createTime; + gc->visibleC = visibleC; + gc->guardian[0].visible = guardian[0]; + gc->guardian[1].visible = guardian[1]; + gc->guardian[2].visible = guardian[2]; + gc->guardian[3].visible = guardian[3]; + gc->guardian[4].visible = guardian[4]; + gc->guardian[5].visible = guardian[5]; + gc->guardian[6].visible = guardian[6]; + gc->guardian[7].visible = guardian[7]; + + return 0; +} + +#ifndef TXT_SQL_CONVERT +// ギルド関連データベース読み込み +int inter_guild_readdb(void) +{ + int i; + FILE *fp; + char line[1024]; + char path[1024]; + + sprintf(path, "%s%s", db_path, "/exp_guild.txt"); + fp = fopen(path, "r"); + if (fp == NULL) { + ShowError("can't read db/exp_guild.txt\n"); + return 1; + } + i = 0; + while(fgets(line, sizeof(line), fp) && i < 100) + { + if (line[0] == '/' && line[1] == '/') + continue; + guild_exp[i] = (unsigned int)atof(line); + i++; + } + fclose(fp); + + return 0; +} + +// ギルドデータの読み込み +int inter_guild_init() +{ + char line[16384]; + struct guild *g; + struct guild_castle *gc; + FILE *fp; + int i, j, c = 0; + + inter_guild_readdb(); + + guild_db = idb_alloc(DB_OPT_RELEASE_DATA); + castle_db = idb_alloc(DB_OPT_RELEASE_DATA); + + if ((fp = fopen(guild_txt,"r")) == NULL) + return 1; + while(fgets(line, sizeof(line), fp)) + { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && guild_newid <= i) { + guild_newid = i; + continue; + } + + g = (struct guild *) aCalloc(sizeof(struct guild), 1); + if(g == NULL){ + ShowFatalError("int_guild: out of memory!\n"); + exit(EXIT_FAILURE); + } +// memset(g, 0, sizeof(struct guild)); not needed... + if (inter_guild_fromstr(line, g) == 0 && g->guild_id > 0) { + if (g->guild_id >= guild_newid) + guild_newid = g->guild_id + 1; + idb_put(guild_db, g->guild_id, g); + guild_check_empty(g); + guild_calcinfo(g); + } else { + ShowError("int_guild: broken data [%s] line %d\n", guild_txt, c); + aFree(g); + } + c++; + } + fclose(fp); + + c = 0;//カウンタ初期化 + + if ((fp = fopen(castle_txt, "r")) == NULL) { + return 1; + } + + while(fgets(line, sizeof(line), fp)) + { + gc = (struct guild_castle *) aCalloc(sizeof(struct guild_castle), 1); + if(gc == NULL){ + ShowFatalError("int_guild: out of memory!\n"); + exit(EXIT_FAILURE); + } +// memset(gc, 0, sizeof(struct guild_castle)); No need... + if (inter_guildcastle_fromstr(line, gc) == 0) { + idb_put(castle_db, gc->castle_id, gc); + } else { + ShowError("int_guild: broken data [%s] line %d\n", castle_txt, c); + aFree(gc); + } + c++; + } + + fclose(fp); + + if (!c) { + ShowStatus(" %s - making Default Data...\n", castle_txt); + //デフォルトデータを作成 + for(i = 0; i < MAX_GUILDCASTLE; i++) { + gc = (struct guild_castle *) aCalloc(sizeof(struct guild_castle), 1); + if (gc == NULL) { + ShowFatalError("int_guild: out of memory!\n"); + exit(EXIT_FAILURE); + } + gc->castle_id = i; + idb_put(castle_db, gc->castle_id, gc); + } + ShowStatus(" %s - making done\n",castle_txt); + return 0; + } + + return 0; +} + +void inter_guild_final() +{ + castle_db->destroy(castle_db, NULL); + guild_db->destroy(guild_db, NULL); + return; +} + +struct guild *inter_guild_search(int guild_id) +{ + return (struct guild*)idb_get(guild_db, guild_id); +} + +// ギルドデータのセーブ +int inter_guild_save() +{ + FILE *fp; + int lock; + DBIterator* iter; + struct guild* g; + struct guild_castle* gc; + + // save guild data + if ((fp = lock_fopen(guild_txt, &lock)) == NULL) { + ShowError("int_guild: can't write [%s] !!! data is lost !!!\n", guild_txt); + return 1; + } + + iter = guild_db->iterator(guild_db); + for( g = (struct guild*)iter->first(iter,NULL); iter->exists(iter); g = (struct guild*)iter->next(iter,NULL) ) + { + char line[16384]; + inter_guild_tostr(line, g); + fprintf(fp, "%s\n", line); + } + iter->destroy(iter); + +// fprintf(fp, "%d\t%%newid%%\n", guild_newid); + lock_fclose(fp, guild_txt, &lock); + + // save castle data + if ((fp = lock_fopen(castle_txt,&lock)) == NULL) { + ShowError("int_guild: can't write [%s] !!! data is lost !!!\n", castle_txt); + return 1; + } + + iter = castle_db->iterator(castle_db); + for( gc = (struct guild_castle*)iter->first(iter,NULL); iter->exists(iter); gc = (struct guild_castle*)iter->next(iter,NULL) ) + { + char line[16384]; + inter_guildcastle_tostr(line, gc); + fprintf(fp, "%s\n", line); + } + iter->destroy(iter); + + lock_fclose(fp, castle_txt, &lock); + + return 0; +} + +// ギルド名検索 +struct guild* search_guildname(char *str) +{ + DBIterator* iter; + struct guild* g; + + iter = guild_db->iterator(guild_db); + for( g = (struct guild*)iter->first(iter,NULL); iter->exists(iter); g = (struct guild*)iter->next(iter,NULL) ) + { + if (strcmpi(g->name, str) == 0) + break; + } + iter->destroy(iter); + + return g; +} + +// ギルドが空かどうかチェック +static bool guild_check_empty(struct guild *g) +{ + int i; + ARR_FIND( 0, g->max_member, i, g->member[i].account_id > 0 ); + if( i < g->max_member) + return false; // not empty + + // 誰もいないので解散 + guild_db->foreach(guild_db, guild_break_sub, g->guild_id); + inter_guild_storage_delete(g->guild_id); + mapif_guild_broken(g->guild_id, 0); + idb_remove(guild_db, g->guild_id); + return true; +} + +unsigned int guild_nextexp(int level) +{ + if (level == 0) + return 1; + if (level > 0 && level < 100) + 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; + unsigned int nextexp; + struct guild before = *g; + + // スキルIDの設定 + for(i = 0; i < MAX_GUILDSKILL; i++) + g->skill[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; + c = 0; + for(i = 0; i < g->max_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) +{ + WFIFOHEAD(fd, 10); + WFIFOW(fd,0) = 0x3830; + WFIFOL(fd,2) = account_id; + if (g != NULL) { + WFIFOL(fd,6) = g->guild_id; + ShowInfo("Created Guild (%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) +{ + WFIFOHEAD(fd, 8); + WFIFOW(fd,0) = 0x3831; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = guild_id; + WFIFOSET(fd,8); + ShowNotice("int_guild: info not found %d\n", guild_id); + + 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) +{ + WFIFOHEAD(fd, 15); + 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[79]; + + 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("Character left guild (Guild %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[19]; + + 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[7]; + + WBUFW(buf,0) = 0x3836; + WBUFL(buf,2) = guild_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + ShowInfo("Guild Break (%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[2048]; + + 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[4096]; + + 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[14]; + + 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[67]; + + 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[2048]; + + 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[186]; + + 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 aid, int cid) +{ + unsigned char buf[14]; + WBUFW(buf,0)=0x3843; + WBUFL(buf,2)=g->guild_id; + WBUFL(buf,6)=aid; + WBUFL(buf,10)=cid; + mapif_sendall(buf,14); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id, int index, int value) +{ + unsigned char buf[9]; + + 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[9]; + + 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) +{ + DBIterator* iter; + struct guild_castle* gc; + int len = 4; + + WFIFOHEAD(fd, 4 + MAX_GUILDCASTLE*sizeof(struct guild_castle)); + WFIFOW(fd,0) = 0x3842; + iter = castle_db->iterator(castle_db); + for( gc = (struct guild_castle*)iter->first(iter,NULL); iter->exists(iter); gc = (struct guild_castle*)iter->next(iter,NULL) ) + { + memcpy(WFIFOP(fd,len), gc, sizeof(struct guild_castle)); + len += sizeof(struct guild_castle); + } + iter->destroy(iter); + WFIFOW(fd,2) = len; + WFIFOSET(fd, len); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd, int account_id, char *name, struct guild_member *master) +{ + struct guild *g; + int i; + + for(i = 0; i < NAME_LENGTH && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + ShowInfo("Create Guild: illegal guild name [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + } + + if ((g = search_guildname(name)) != NULL) { + ShowInfo("Create Guild: same name guild exists [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + + // 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) { + mapif_guild_created(fd,account_id,NULL); + return 0; + } + } 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) { + mapif_guild_created(fd,account_id,NULL); + return 0; + } + } + + g = (struct guild *) aCalloc(sizeof(struct guild), 1); + if (g == NULL) { + ShowFatalError("int_guild: CreateGuild: out of memory !\n"); + mapif_guild_created(fd, account_id, NULL); + exit(EXIT_FAILURE); + } +// memset(g, 0, sizeof(struct guild)); Meh... + g->guild_id = guild_newid++; + 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; i < MAX_GUILDPOSITION-1; i++) + sprintf(g->position[i].name, "Position %d", i + 1); + + // ここでギルド情報計算が必要と思われる + g->max_member = 16; + g->average_lv = master->lv; + g->connect_member = 1; + for(i = 0; i < MAX_GUILDSKILL; i++) + g->skill[i].id=i + GD_SKILLBASE; + + idb_put(guild_db, g->guild_id, g); + + 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)\n", + name, g->guild_id, master->name, master->account_id); + + return 0; +} + +// ギルド情報要求 +int mapif_parse_GuildInfo(int fd, int guild_id) +{ + struct guild *g; + + g = (struct guild*)idb_get(guild_db, guild_id); + if (g != NULL){ + guild_calcinfo(g); + mapif_guild_info(fd, g); + } else + mapif_guild_noinfo(fd, guild_id); + + return 0; +} + +// ギルドメンバ追加要求 +int mapif_parse_GuildAddMember(int fd, int guild_id, struct guild_member *m) +{ + struct guild *g; + int i; + + g = (struct guild*)idb_get(guild_db, guild_id); + if (g == NULL) { + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + return 0; + } + + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == 0 ); + if( i < g->max_member ) + { + memcpy(&g->member[i], m, sizeof(struct guild_member)); + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 0); + guild_calcinfo(g); + mapif_guild_info(-1, g); + } + else + 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) +{ + int i, j; + + struct guild* g = (struct guild*)idb_get(guild_db, guild_id); + if( g == NULL ) + { + //TODO + return 0; + } + + // Find the member + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == account_id && g->member[i].char_id == char_id ); + if( i == g->max_member ) + { + //TODO + return 0; + } + + if( flag ) + { // 追放の場合追放リストに入れる + ARR_FIND( 0, MAX_GUILDEXPULSION, j, g->expulsion[j].account_id == 0 ); + if (j == MAX_GUILDEXPULSION) + { // 一杯なので古いのを消す + for(j = 0; j < MAX_GUILDEXPULSION - 1; j++) + g->expulsion[j] = g->expulsion[j+1]; + j = MAX_GUILDEXPULSION - 1; + } + // Save the expulsion entry + g->expulsion[j].account_id = account_id; + safestrncpy(g->expulsion[j].name, g->member[i].name, NAME_LENGTH); + safestrncpy(g->expulsion[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) == 0) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + + return 0; +} + +// オンライン/Lv更新 +int mapif_parse_GuildChangeMemberInfoShort(int fd, int guild_id, int account_id, int char_id, int online, int lv, int class_) +{ + struct guild *g; + int i, sum, c; + + g = (struct guild*)idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == account_id && g->member[i].char_id == char_id ); + if( i < g->max_member ) + { + g->member[i].online = online; + g->member[i].lv = lv; + g->member[i].class_ = class_; + mapif_guild_memberinfoshort(g, i); + } + + g->average_lv = 0; + g->connect_member = 0; + c = 0; // member count + sum = 0; // total sum of base levels + + for(i = 0; i < g->max_member; i++) + { + if( g->member[i].account_id > 0 ) + { + sum += g->member[i].lv; + c++; + } + if( g->member[i].online ) + g->connect_member++; + } + + if( c ) // this check should always succeed... + g->average_lv = sum / c; + + //FIXME: how about sending a mapif_guild_info() update to the mapserver? [ultramage] + + return 0; +} + +// ギルド解散処理用(同盟/敵対を解除) +int guild_break_sub(DBKey key, void *data, va_list ap) +{ + struct guild *g = (struct guild *)data; + int guild_id = va_arg(ap, int); + int i; + + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + if (g->alliance[i].guild_id == guild_id) + g->alliance[i].guild_id = 0; + } + return 0; +} + +// ギルド解散要求 +int mapif_parse_BreakGuild(int fd, int guild_id) +{ + struct guild *g; + + g = (struct guild*)idb_get(guild_db, guild_id); + if(g == NULL) + return 0; + + guild_db->foreach(guild_db, guild_break_sub, guild_id); + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id, 0); + + if(log_inter) + inter_log("guild %s (id=%d) broken\n", g->name, guild_id); + + 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; + short dw = *((short *)data); + + g = (struct guild*)idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + + switch(type) { + case GBI_GUILDLV: + 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); + 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) +{ + int i; + struct guild *g; + + g = (struct guild*)idb_get(guild_db, guild_id); + if(g == NULL) + return 0; + + for(i = 0; i < g->max_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 %d[%s]\n", account_id, char_id, guild_id, g->name); + return 0; + } + switch(type) { + case GMI_POSITION: // 役職 + g->member[i].position = *((int *)data); + break; + case GMI_EXP: // EXP + { + unsigned int exp, old_exp=g->member[i].exp; + g->member[i].exp=*((unsigned int *)data); + if (g->member[i].exp > old_exp) + { + exp = g->member[i].exp - old_exp; + if (guild_exp_rate != 100) + exp = exp*guild_exp_rate/100; + if (exp > UINT_MAX - g->exp) + g->exp = UINT_MAX; + else + g->exp+=exp; + guild_calcinfo(g); + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,4); + } + break; + } + case GMI_HAIR: + { + g->member[i].hair=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_HAIR_COLOR: + { + g->member[i].hair_color=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_GENDER: + { + g->member[i].gender=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_CLASS: + { + g->member[i].class_=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_LEVEL: + { + g->member[i].lv=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + + default: + ShowError("int_guild: GuildMemberInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_memberinfochanged(guild_id, account_id, char_id, type, data, len); + + 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) +{ + struct guild *g = (struct guild*)idb_get(guild_db, 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 [%d] changed\n", idx); + + return 0; +} + +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd, int guild_id, int skill_num, int account_id) +{ + struct guild *g = (struct guild*)idb_get(guild_db, guild_id); + int idx = skill_num - GD_SKILLBASE; + + 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) == 0) + mapif_guild_info(-1, g); + mapif_guild_skillupack(guild_id, skill_num, account_id); + } + + 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;i<MAX_GUILDALLIANCE;i++) + if(g->alliance[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); + return 0; +} + +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd, int guild_id1, int guild_id2, int account_id1, int account_id2, int flag) +{ + struct guild *g[2]; + int j, i; + + g[0] = (struct guild*)idb_get(guild_db, guild_id1); + g[1] = (struct guild*)idb_get(guild_db, 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; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[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); + g[i]->alliance[j].opposition = flag & 1; + break; + } + } + } else { // 関係解消 + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[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); + + return 0; +} + +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd, int guild_id, const char *mes1, const char *mes2) +{ + struct guild *g; + + g = (struct guild*)idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->mes1, mes1, 60); + memcpy(g->mes2, mes2, 120); + + 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 = (struct guild*)idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->emblem_data, data, len); + g->emblem_len = len; + g->emblem_id++; + + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd, int castle_id, int index) +{ + struct guild_castle *gc = (struct guild_castle*)idb_get(castle_db, castle_id); + + if (gc == NULL) { + 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); + case 2: return mapif_guild_castle_dataload(gc->castle_id, index, gc->economy); + case 3: return mapif_guild_castle_dataload(gc->castle_id, index, gc->defense); + case 4: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerE); + case 5: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerD); + case 6: return mapif_guild_castle_dataload(gc->castle_id, index, gc->nextTime); + case 7: return mapif_guild_castle_dataload(gc->castle_id, index, gc->payTime); + case 8: return mapif_guild_castle_dataload(gc->castle_id, index, gc->createTime); + case 9: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleC); + 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); + default: + ShowError("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return 0; +} + +int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value) +{ + struct guild_castle *gc = (struct guild_castle*)idb_get(castle_db, castle_id); + + if (gc == NULL) + 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 = (struct guild*)idb_get(guild_db, gid); + if(log_inter) + inter_log("guild %s (id=%d) %s castle id=%d\n", + (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", castle_id); + } + gc->guild_id = value; + if(gc->guild_id == 0) { + //Delete guardians. + memset(&gc->guardian, 0, sizeof(gc->guardian)); + } + 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; + default: + ShowError("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return mapif_guild_castle_datasave(gc->castle_id, index, value); +} + +int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len) +{ + struct guild *g = (struct guild*)idb_get(guild_db, guild_id); + struct guild_member gm; + int pos; + + if(g==NULL || g->guild_id<=0 || 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. + safestrncpy(g->master, name, NAME_LENGTH); + + ShowInfo("int_guild: Guildmaster Changed to %s (Guild %d - %s)\n",name, guild_id, g->name); + return mapif_guild_master_changed(g, g->member[0].account_id, g->member[0].char_id); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) +{ + RFIFOHEAD(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 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; +} + +// processes a mapserver connection event +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 **"); +} +#endif //TXT_SQL_CONVERT diff --git a/src/char/int_guild.h b/src/char/int_guild.h new file mode 100644 index 000000000..06293eb0f --- /dev/null +++ b/src/char/int_guild.h @@ -0,0 +1,26 @@ +// 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_ + +struct guild; +struct guild_castle; + +int inter_guild_init(void); +void inter_guild_final(void); +int inter_guild_save(void); +int inter_guild_parse_frommap(int fd); +struct guild *inter_guild_search(int guild_id); +int inter_guild_mapif_init(int fd); +int inter_guild_leave(int guild_id,int account_id,int char_id); +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender); + +extern char guild_txt[1024]; +extern char castle_txt[1024]; + +//For the TXT->SQL converter +int inter_guild_fromstr(char *str, struct guild *g); +int inter_guildcastle_fromstr(char *str, struct guild_castle *gc); + +#endif /* _INT_GUILD_H_ */ diff --git a/src/char/int_homun.c b/src/char/int_homun.c new file mode 100644 index 000000000..d547b60f7 --- /dev/null +++ b/src/char/int_homun.c @@ -0,0 +1,365 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_homun.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char homun_txt[1024]="save/homun.txt"; + +static DBMap* homun_db; // int hom_id -> struct s_homunculus* +static int homun_newid = 100; + +int inter_homun_tostr(char *str,struct s_homunculus *p) +{ + int i; + + str+=sprintf(str,"%d,%d\t%s\t%d,%d,%d,%d,%d," + "%u,%d,%d,%d," + "%u,%d,%d," + "%d,%d,%d,%d,%d,%d\t", + p->hom_id, p->class_, p->name, + p->char_id, p->hp, p->max_hp, p->sp, p->max_sp, + p->intimacy, p->hunger, p->skillpts, p->level, + p->exp, p->rename_flag, p->vaporize, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk); + + for (i = 0; i < MAX_HOMUNSKILL; i++) + { + if (p->hskill[i].id && !p->hskill[i].flag) + str+=sprintf(str,"%d,%d,", p->hskill[i].id, p->hskill[i].lv); + } + + return 0; +} + +int inter_homun_fromstr(char *str,struct s_homunculus *p) +{ + int i, next, len; + int tmp_int[25]; + unsigned int tmp_uint[5]; + char tmp_str[256]; + + memset(p,0,sizeof(struct s_homunculus)); + + i=sscanf(str,"%d,%d\t%127[^\t]\t%d,%d,%d,%d,%d," + "%u,%d,%d,%d," + "%u,%d,%d," + "%d,%d,%d,%d,%d,%d\t%n", + &tmp_int[0],&tmp_int[1],tmp_str, + &tmp_int[2],&tmp_int[3],&tmp_int[4],&tmp_int[5],&tmp_int[6], + &tmp_uint[0],&tmp_int[7],&tmp_int[8],&tmp_int[9], + &tmp_uint[1],&tmp_int[10],&tmp_int[11], + &tmp_int[12],&tmp_int[13],&tmp_int[14],&tmp_int[15],&tmp_int[16],&tmp_int[17], + &next); + + if(i!=21) + return 1; + + p->hom_id = tmp_int[0]; + p->class_ = tmp_int[1]; + memcpy(p->name, tmp_str, NAME_LENGTH); + + p->char_id = tmp_int[2]; + p->hp = tmp_int[3]; + p->max_hp = tmp_int[4]; + p->sp = tmp_int[5]; + p->max_sp = tmp_int[6]; + + p->intimacy = tmp_uint[0]; + p->hunger = tmp_int[7]; + p->skillpts = tmp_int[8]; + p->level = tmp_int[9]; + + p->exp = tmp_uint[1]; + p->rename_flag = tmp_int[10]; + p->vaporize = tmp_int[11]; + + p->str = tmp_int[12]; + p->agi = tmp_int[13]; + p->vit = tmp_int[14]; + p->int_= tmp_int[15]; + p->dex = tmp_int[16]; + p->luk = tmp_int[17]; + + //Read skills. + while(str[next] && str[next] != '\n' && str[next] != '\r') { + if (sscanf(str+next, "%d,%d,%n", &tmp_int[0], &tmp_int[1], &len) != 2) + return 2; + + if (tmp_int[0] >= HM_SKILLBASE && tmp_int[0] < HM_SKILLBASE+MAX_HOMUNSKILL) + { + i = tmp_int[0] - HM_SKILLBASE; + p->hskill[i].id = tmp_int[0]; + p->hskill[i].lv = tmp_int[1]; + } else + ShowError("Read Homun: Unsupported Skill ID %d for homunculus (Homun ID=%d)\n", tmp_int[0], p->hom_id); + next += len; + if (str[next] == ' ') + next++; + } + return 0; +} + +int inter_homun_init() +{ + char line[8192]; + struct s_homunculus *p; + FILE *fp; + int c=0; + + homun_db= idb_alloc(DB_OPT_RELEASE_DATA); + + if( (fp=fopen(homun_txt,"r"))==NULL ) + return 1; + while(fgets(line, sizeof(line), fp)) + { + p = (struct s_homunculus*)aCalloc(sizeof(struct s_homunculus), 1); + if(p==NULL){ + ShowFatalError("int_homun: out of memory!\n"); + exit(EXIT_FAILURE); + } + if(inter_homun_fromstr(line,p)==0 && p->hom_id>0){ + if( p->hom_id >= homun_newid) + homun_newid=p->hom_id+1; + idb_put(homun_db,p->hom_id,p); + }else{ + ShowError("int_homun: broken data [%s] line %d\n",homun_txt,c); + aFree(p); + } + c++; + } + fclose(fp); + return 0; +} + +void inter_homun_final() +{ + homun_db->destroy(homun_db, NULL); + return; +} + +int inter_homun_save_sub(DBKey key,void *data,va_list ap) +{ + char line[8192]; + FILE *fp; + inter_homun_tostr(line,(struct s_homunculus *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s\n",line); + return 0; +} + +int inter_homun_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(homun_txt,&lock))==NULL ){ + ShowError("int_homun: can't write [%s] !!! data is lost !!!\n",homun_txt); + return 1; + } + homun_db->foreach(homun_db,inter_homun_save_sub,fp); + lock_fclose(fp,homun_txt,&lock); + return 0; +} + +int inter_homun_delete(int hom_id) +{ + struct s_homunculus *p; + p = (struct s_homunculus*)idb_get(homun_db,hom_id); + if( p == NULL) + return 0; + idb_remove(homun_db,hom_id); + ShowInfo("Deleted homun (hom_id: %d)\n",hom_id); + return 1; +} + +int mapif_homun_created(int fd,int account_id, struct s_homunculus *p) +{ + WFIFOHEAD(fd, sizeof(struct s_homunculus)+9); + WFIFOW(fd, 0) =0x3890; + WFIFOW(fd,2) = sizeof(struct s_homunculus)+9; + WFIFOL(fd,4) = account_id; + WFIFOB(fd,8)= p->hom_id?1:0; + memcpy(WFIFOP(fd,9), p, sizeof(struct s_homunculus)); + WFIFOSET(fd, WFIFOW(fd,2)); + return 0; +} + +int mapif_homun_info(int fd,int account_id,struct s_homunculus *p) +{ + WFIFOHEAD(fd, sizeof(struct s_homunculus)+9); + WFIFOW(fd,0) = 0x3891; + WFIFOW(fd,2) = sizeof(struct s_homunculus)+9; + WFIFOL(fd,4) = account_id; + WFIFOB(fd,8) = 1; // account loaded with success + + memcpy(WFIFOP(fd,9), p, sizeof(struct s_homunculus)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +int mapif_homun_noinfo(int fd,int account_id) +{ + WFIFOHEAD(fd,sizeof(struct s_homunculus) + 9); + WFIFOW(fd,0)=0x3891; + WFIFOW(fd,2)=sizeof(struct s_homunculus) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=0; + memset(WFIFOP(fd,9),0,sizeof(struct s_homunculus)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_save_homun_ack(int fd,int account_id,int flag) +{ + WFIFOHEAD(fd, 7); + WFIFOW(fd,0)=0x3892; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,7); + return 0; +} + +int mapif_delete_homun_ack(int fd,int flag) +{ + WFIFOHEAD(fd, 3); + WFIFOW(fd,0)=0x3893; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,3); + return 0; +} + +int mapif_rename_homun_ack(int fd, int account_id, int char_id, int flag, char *name){ + WFIFOHEAD(fd, NAME_LENGTH+12); + WFIFOW(fd, 0) =0x3894; + WFIFOL(fd, 2) =account_id; + WFIFOL(fd, 6) =char_id; + WFIFOB(fd, 10) =flag; + memcpy(WFIFOP(fd, 11), name, NAME_LENGTH); + WFIFOSET(fd, NAME_LENGTH+12); + + return 0; +} + +int mapif_create_homun(int fd) +{ + struct s_homunculus *p; + p= (struct s_homunculus *) aCalloc(sizeof(struct s_homunculus), 1); + if(p==NULL){ + ShowFatalError("int_homun: out of memory !\n"); + //Sending the received data will pass hom_id == 0 <- fail. + mapif_homun_created(fd,RFIFOL(fd,4),(struct s_homunculus*)RFIFOP(fd,8)); + return 0; + } + memcpy(p, RFIFOP(fd,8), sizeof(struct s_homunculus)); + p->hom_id = homun_newid++; //New ID + idb_put(homun_db,p->hom_id,p); + mapif_homun_created(fd,RFIFOL(fd,4),p); + return 0; +} + +int mapif_load_homun(int fd) +{ + struct s_homunculus *p; + int account_id; + account_id = RFIFOL(fd,2); + + p = (struct s_homunculus*)idb_get(homun_db,RFIFOL(fd,6)); + if(p==NULL) { + mapif_homun_noinfo(fd,account_id); + return 0; + } + mapif_homun_info(fd,account_id,p); + return 0; +} + +static void* create_homun(DBKey key, va_list args) { + struct s_homunculus *p; + p=(struct s_homunculus *)aCalloc(sizeof(struct s_homunculus),1); + p->hom_id = key.i; + return p; +} +int mapif_save_homun(int fd,int account_id,struct s_homunculus *data) +{ + struct s_homunculus *p; + int hom_id; + + if (data->hom_id == 0) + data->hom_id = homun_newid++; + hom_id = data->hom_id; + p = (struct s_homunculus*)idb_ensure(homun_db,hom_id,create_homun); + memcpy(p,data,sizeof(struct s_homunculus)); + mapif_save_homun_ack(fd,account_id,1); + return 0; +} + +int mapif_delete_homun(int fd,int hom_id) +{ + mapif_delete_homun_ack(fd,inter_homun_delete(hom_id)); + return 0; +} + +int mapif_rename_homun(int fd, int account_id, int char_id, char *name){ + int i; + + // Check Authorised letters/symbols in the name of the homun + 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) { + mapif_rename_homun_ack(fd, account_id, char_id, 0, name); + return 0; + } + } 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) { + mapif_rename_homun_ack(fd, account_id, char_id, 0, name); + return 0; + } + } + + mapif_rename_homun_ack(fd, account_id, char_id, 1, name); + return 0; +} + +int mapif_parse_SaveHomun(int fd) +{ + mapif_save_homun(fd,RFIFOL(fd,4),(struct s_homunculus *)RFIFOP(fd,8)); + return 0; +} + +int mapif_parse_DeleteHomun(int fd) +{ + mapif_delete_homun(fd,RFIFOL(fd,2)); + return 0; +} + +int mapif_parse_RenameHomun(int fd) +{ + mapif_rename_homun(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), (char*)RFIFOP(fd, 10)); + return 0; +} + +int inter_homun_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3090: mapif_create_homun(fd); break; + case 0x3091: mapif_load_homun(fd); break; + case 0x3092: mapif_parse_SaveHomun(fd); break; + case 0x3093: mapif_parse_DeleteHomun(fd); break; + case 0x3094: mapif_parse_RenameHomun(fd); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_homun.h b/src/char/int_homun.h new file mode 100644 index 000000000..1858ed4ba --- /dev/null +++ b/src/char/int_homun.h @@ -0,0 +1,15 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_HOMUN_H_ +#define _INT_HOMUN_H_ + +int inter_homun_init(void); +void inter_homun_final(void); +int inter_homun_save(void); +int inter_homun_delete(int homun_id); +int inter_homun_parse_frommap(int fd); + +extern char homun_txt[1024]; + +#endif /* _INT_HOMUN_H_ */ diff --git a/src/char/int_party.c b/src/char/int_party.c new file mode 100644 index 000000000..f0668a820 --- /dev/null +++ b/src/char/int_party.c @@ -0,0 +1,732 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_party.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char party_txt[1024] = "save/party.txt"; +#ifndef TXT_SQL_CONVERT +struct party_data { + struct party party; + unsigned int min_lv, max_lv; + int family; //Is this party a family? if so, this holds the child id. + unsigned char size; //Total size of party. +}; + +static DBMap* party_db; // int party_id -> struct party_data* +static int party_newid = 100; + +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); +int party_check_exp_share(struct party_data *p); +int mapif_party_optionchanged(int fd,struct party *p, int account_id, int flag); + +//Updates party's level range and unsets even share if broken. +static int int_party_check_lv(struct party_data *p) { + int i; + unsigned int lv; + p->min_lv = UINT_MAX; + p->max_lv = 0; + for(i=0;i<MAX_PARTY;i++){ + if(!p->party.member[i].online) + continue; + + lv=p->party.member[i].lv; + if (lv < p->min_lv) p->min_lv = lv; + if (lv > p->max_lv) p->max_lv = lv; + } + + if (p->party.exp && !party_check_exp_share(p)) { + p->party.exp = 0; + mapif_party_optionchanged(0, &p->party, 0, 0); + return 0; + } + return 1; +} + +//Calculates the state of a party. +static void int_party_calc_state(struct party_data *p) +{ + int i; + unsigned int lv; + p->min_lv = UINT_MAX; + p->max_lv = 0; + p->party.count = + p->size = + p->family = 0; + + //Check party size. + for(i=0;i<MAX_PARTY;i++){ + if (!p->party.member[i].lv) continue; + p->size++; + if(p->party.member[i].online) + p->party.count++; + } + if(p->size == 3) { + //Check Family State. + p->family = char_family( + p->party.member[0].char_id, + p->party.member[1].char_id, + p->party.member[2].char_id + ); + } + //max/min levels. + for(i=0;i<MAX_PARTY;i++){ + lv=p->party.member[i].lv; + if (!lv) continue; + if(p->party.member[i].online && + //On families, the kid is not counted towards exp share rules. + p->party.member[i].char_id != p->family) + { + if( lv < p->min_lv ) p->min_lv=lv; + if( p->max_lv < lv ) p->max_lv=lv; + } + } + + if (p->party.exp && !party_check_exp_share(p)) { + p->party.exp = 0; //Set off even share. + mapif_party_optionchanged(0, &p->party, 0, 0); + } + return; +} + +// パ?ティデ?タの文字列への?換 +int inter_party_tostr(char *str, struct party *p) { + int i, len; + + len = sprintf(str, "%d\t%s\t%d,%d\t", p->party_id, p->name, p->exp, p->item); + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + len += sprintf(str + len, "%d,%d,%d\t", m->account_id, m->char_id, m->leader); + } + + return 0; +} +#endif //TXT_SQL_CONVERT +// パ?ティデ?タの文字列からの?換 +int inter_party_fromstr(char *str, struct party *p) { + int i, j; + int tmp_int[16]; + char tmp_str[256]; +#ifndef TXT_SQL_CONVERT + struct mmo_charstatus* status; +#endif + + memset(p, 0, sizeof(struct party)); + + if (sscanf(str, "%d\t%255[^\t]\t%d,%d\t", &tmp_int[0], tmp_str, &tmp_int[1], &tmp_int[2]) != 4) + return 1; + + p->party_id = tmp_int[0]; + memcpy(p->name, tmp_str, NAME_LENGTH); + p->exp = tmp_int[1]?1:0; + p->item = tmp_int[2]; + + for(j = 0; j < 3 && str != NULL; j++) + str = strchr(str + 1, '\t'); + + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + if (str == NULL) + return 1; + + if (sscanf(str + 1, "%d,%d,%d\t", &tmp_int[0], &tmp_int[1], &tmp_int[2]) != 3) + return 1; + + m->account_id = tmp_int[0]; + m->char_id = tmp_int[1]; + m->leader = tmp_int[2]?1:0; + + str = strchr(str + 1, '\t'); +#ifndef TXT_SQL_CONVERT + if (!m->account_id) continue; + //Lookup player for rest of data. + status = search_character(m->account_id, m->char_id); + if (!status) continue; + + memcpy(m->name, status->name, NAME_LENGTH); + m->class_ = status->class_; + m->map = status->last_point.map; + m->lv = status->base_level; +#endif //TXT_SQL_CONVERT + } + + return 0; +} +#ifndef TXT_SQL_CONVERT +// パ?ティデ?タのロ?ド +int inter_party_init() { + char line[8192]; + struct party_data *p; + FILE *fp; + int c = 0; + int i, j; + + party_db = idb_alloc(DB_OPT_RELEASE_DATA); + + if ((fp = fopen(party_txt, "r")) == NULL) + return 1; + + while(fgets(line, sizeof(line), fp)) + { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && party_newid <= i) { + party_newid = i; + continue; + } + + p = (struct party_data*)aCalloc(sizeof(struct party_data), 1); + if (p == NULL){ + ShowFatalError("int_party: out of memory!\n"); + exit(EXIT_FAILURE); + } + memset(p, 0, sizeof(struct party_data)); + if (inter_party_fromstr(line, &p->party) == 0 && p->party.party_id > 0) { + int_party_calc_state(p); + if (p->party.party_id >= party_newid) + party_newid = p->party.party_id + 1; + idb_put(party_db, p->party.party_id, p); + party_check_empty(&p->party); + } else { + ShowError("int_party: broken data [%s] line %d\n", party_txt, c + 1); + aFree(p); + } + c++; + } + fclose(fp); + + return 0; +} + +void inter_party_final() +{ + party_db->destroy(party_db, NULL); + return; +} + +// パ?ティ?デ?タのセ?ブ用 +int inter_party_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + + inter_party_tostr(line, &((struct party_data*)data)->party); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s\n", line); + + return 0; +} + +// パ?ティ?デ?タのセ?ブ +int inter_party_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(party_txt, &lock)) == NULL) { + ShowError("int_party: can't write [%s] !!! data is lost !!!\n", party_txt); + return 1; + } + party_db->foreach(party_db, inter_party_save_sub, fp); + lock_fclose(fp,party_txt, &lock); + return 0; +} + +// パ?ティ名?索用 +int search_partyname_sub(DBKey key,void *data,va_list ap) { + struct party_data *p = (struct party_data *)data,**dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct party_data **); + if (strncmpi(p->party.name, str, NAME_LENGTH) == 0) + *dst = p; + + return 0; +} + +// パ?ティ名?索 +struct party_data* search_partyname(char *str) { + struct party_data *p = NULL; + party_db->foreach(party_db, search_partyname_sub, str, &p); + return p; +} + +// Returns whether this party can keep having exp share or not. +int party_check_exp_share(struct party_data *p) { + return (p->party.count < 2|| p->max_lv - p->min_lv <= party_share_level); +} + +// パ?ティが空かどうかチェック +int party_check_empty(struct party *p) { + int i; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id > 0) { + return 0; + } + } + mapif_party_broken(p->party_id, 0); + idb_remove(party_db, p->party_id); + + return 1; +} + +// キャラの競合がないかチェック用 +int party_check_conflict_sub(DBKey key, void *data, va_list ap) { + struct party_data *p = (struct party_data *)data; + int party_id, account_id, char_id, i; + + party_id=va_arg(ap, int); + account_id=va_arg(ap, int); + char_id=va_arg(ap, int); + + if (p->party.party_id == party_id) //No conflict to check + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if(p->party.member[i].account_id == account_id && + p->party.member[i].char_id == char_id) + { + ShowWarning("int_party: party conflict! %d %d %d\n", account_id, party_id, p->party.party_id); + mapif_parse_PartyLeave(-1, p->party.party_id, account_id, char_id); + } + } + + return 0; +} + +// キャラの競合がないかチェック +int party_check_conflict(int party_id, int account_id, int char_id) { + party_db->foreach(party_db, party_check_conflict_sub, party_id, account_id, char_id); + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パ?ティ作成可否 +int mapif_party_created(int fd,int account_id, int char_id, struct party *p) { + WFIFOHEAD(fd, 39); + 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("Created party (%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) { + WFIFOHEAD(fd, 8); + 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[2048]; + + 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[15]; + + 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; +} + +// パ?ティ?退通知 +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[7]; + WBUFW(buf,0) = 0x3826; + WBUFL(buf,2) = party_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + ShowInfo("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[2048]; + + 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からの通信 + + +// パ?ティ +int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct party_member *leader) { + struct party_data *p; + int i; + + for(i = 0; i < NAME_LENGTH && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + ShowInfo("int_party: illegal party name [%s]\n", name); + mapif_party_created(fd, leader->account_id, leader->char_id, NULL); + return 0; + } + } + + if ((p = search_partyname(name)) != NULL) { + ShowInfo("int_party: same name party exists [%s]\n", name); + mapif_party_created(fd, leader->account_id, leader->char_id, NULL); + return 0; + } + + // 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) { + mapif_party_created(fd, leader->account_id, leader->char_id, NULL); + return 0; + } + } 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) { + mapif_party_created(fd, leader->account_id, leader->char_id, NULL); + return 0; + } + } + + p = (struct party_data *) aCalloc(sizeof(struct party_data), 1); + if (p == NULL) { + ShowFatalError("int_party: out of memory !\n"); + mapif_party_created(fd,leader->account_id,leader->char_id,NULL); + return 0; + } + p->party.party_id = party_newid++; + memcpy(p->party.name, name, NAME_LENGTH); + p->party.exp = 0; + p->party.item=(item?1:0)|(item2?2:0); + memcpy(&p->party.member[0], leader, sizeof(struct party_member)); + p->party.member[0].leader = 1; + int_party_calc_state(p); + idb_put(party_db, p->party.party_id, p); + + mapif_party_created(fd, leader->account_id, leader->char_id, &p->party); + mapif_party_info(fd, &p->party); + + return 0; +} + +// パ?ティ情報要求 +int mapif_parse_PartyInfo(int fd, int party_id) +{ + struct party_data *p; + + p = (struct party_data*)idb_get(party_db, party_id); + if (p != NULL) + mapif_party_info(fd, &p->party); + else { + mapif_party_noinfo(fd, party_id); + char_clearparty(party_id); + } + + return 0; +} + +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, struct party_member *member) +{ + struct party_data *p; + int i; + + p = (struct party_data*)idb_get(party_db, party_id); + if( p == NULL || p->size == MAX_PARTY ) { + mapif_party_memberadded(fd, party_id, member->account_id, member->char_id, 1); + return 0; + } + + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == 0 ); + if( i == MAX_PARTY ) + {// Party full + mapif_party_memberadded(fd, party_id, member->account_id, member->char_id, 1); + return 0; + } + + memcpy(&p->party.member[i], member, sizeof(struct party_member)); + p->party.member[i].leader = 0; + if (p->party.member[i].online) p->party.count++; + p->size++; + if (p->size == 3) //Check family state. + int_party_calc_state(p); + else //Check even share range. + if (member->lv < p->min_lv || member->lv > p->max_lv || p->family) { + if (p->family) p->family = 0; //Family state broken. + int_party_check_lv(p); + } + + mapif_party_memberadded(fd, party_id, member->account_id, member->char_id, 0); + mapif_party_info(-1, &p->party); + + return 0; +} + +// パ?ティ?設定?更要求 +int mapif_parse_PartyChangeOption(int fd, int party_id, int account_id, int exp, int item) +{ + struct party_data *p; + int flag = 0; + + p = (struct party_data*)idb_get(party_db, party_id); + if (p == NULL) + return 0; + + p->party.exp = exp; + if (exp>0 && !party_check_exp_share(p)) { + flag |= 0x01; + p->party.exp = 0; + } + p->party.item = item&0x3; + mapif_party_optionchanged(fd, &p->party, account_id, flag); + return 0; +} + +// パ?ティ?退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id) +{ + struct party_data *p; + int i,lv; + + p = (struct party_data*)idb_get(party_db, party_id); + if (!p) return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if(p->party.member[i].account_id == account_id && + p->party.member[i].char_id == char_id) + { + mapif_party_leaved(party_id, account_id, char_id); + lv = p->party.member[i].lv; + if(p->party.member[i].online) p->party.count--; + memset(&p->party.member[i], 0, sizeof(struct party_member)); + p->size--; + if (lv == p->min_lv || lv == p->max_lv || p->family) + { + if(p->family) p->family = 0; //Family state broken. + int_party_check_lv(p); + } + if (party_check_empty(&p->party) == 0) + mapif_party_info(-1, &p->party); + return 0; + } + } + return 0; +} + +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, int char_id, unsigned short map, int online, unsigned int lv) +{ + struct party_data *p; + int i; + + p = (struct party_data*)idb_get(party_db, party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY && + (p->party.member[i].account_id != account_id || + p->party.member[i].char_id != char_id); i++); + + if (i == MAX_PARTY) return 0; + + if (p->party.member[i].online != online) + { + p->party.member[i].online = online; + if (online) + p->party.count++; + else + p->party.count--; + // Even share check situations: Family state (always breaks) + // character logging on/off is max/min level (update level range) + // or character logging on/off has a different level (update level range using new level) + if (p->family || + (p->party.member[i].lv <= p->min_lv || p->party.member[i].lv >= p->max_lv) || + (p->party.member[i].lv != lv && (lv <= p->min_lv || lv >= p->max_lv)) + ) + { + p->party.member[i].lv = lv; + int_party_check_lv(p); + } + //Send online/offline update. + mapif_party_membermoved(&p->party, i); + } + if (p->party.member[i].lv != lv) { + if(p->party.member[i].lv == p->min_lv || + p->party.member[i].lv == p->max_lv) + { + p->party.member[i].lv = lv; + int_party_check_lv(p); + } else + p->party.member[i].lv = lv; + //There is no need to send level update to map servers + //since they do nothing with it. + } + if (p->party.member[i].map != map) { + p->party.member[i].map = map; + mapif_party_membermoved(&p->party, i); + } + return 0; +} + +// パ?ティ解散要求 +int mapif_parse_BreakParty(int fd, int party_id) { + + idb_remove(party_db, party_id); + 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_data *p; + int i; + + p = (struct party_data*)idb_get(party_db, party_id); + if (p == NULL) + return 0; + + for (i = 0; i < MAX_PARTY; i++) + { + if(p->party.member[i].leader) + p->party.member[i].leader = 0; + if(p->party.member[i].account_id == account_id && + p->party.member[i].char_id == char_id) + p->party.member[i].leader = 1; + } + 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, (char*)RFIFOP(fd,4), RFIFOB(fd,28), RFIFOB(fd,29), (struct party_member*)RFIFOP(fd,30)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,4), (struct party_member*)RFIFOP(fd,8)); 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); +} +#endif //TXT_SQL_CONVERT diff --git a/src/char/int_party.h b/src/char/int_party.h new file mode 100644 index 000000000..ad724e475 --- /dev/null +++ b/src/char/int_party.h @@ -0,0 +1,20 @@ +// 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_ + +struct party; + +int inter_party_init(void); +void inter_party_final(void); +int inter_party_save(void); +int inter_party_parse_frommap(int fd); +int inter_party_leave(int party_id,int account_id, int char_id); + +extern char party_txt[1024]; + +//For the TXT->SQL converter +int inter_party_fromstr(char *str, struct party *p); + +#endif /* _INT_PARTY_H_ */ diff --git a/src/char/int_pet.c b/src/char/int_pet.c new file mode 100644 index 000000000..41d61fbc3 --- /dev/null +++ b/src/char/int_pet.c @@ -0,0 +1,380 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_pet.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char pet_txt[1024]="save/pet.txt"; + +#ifndef TXT_SQL_CONVERT +static DBMap* pet_db; // int pet_id -> struct s_pet* +static int pet_newid = 100; + +int inter_pet_tostr(char *str,struct s_pet *p) +{ + int len; + + 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; + + len=sprintf(str,"%d,%d,%s\t%d,%d,%d,%d,%d,%d,%d,%d,%d", + p->pet_id,p->class_,p->name,p->account_id,p->char_id,p->level,p->egg_id, + p->equip,p->intimate,p->hungry,p->rename_flag,p->incuvate); + + return 0; +} +#endif //TXT_SQL_CONVERT +int inter_pet_fromstr(char *str,struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p,0,sizeof(struct s_pet)); + + s=sscanf(str,"%d,%d,%[^\t]\t%d,%d,%d,%d,%d,%d,%d,%d,%d",&tmp_int[0],&tmp_int[1],tmp_str,&tmp_int[2], + &tmp_int[3],&tmp_int[4],&tmp_int[5],&tmp_int[6],&tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class_ = tmp_int[1]; + memcpy(p->name,tmp_str,NAME_LENGTH); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + 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; + + return 0; +} +#ifndef TXT_SQL_CONVERT +int inter_pet_init() +{ + char line[8192]; + struct s_pet *p; + FILE *fp; + int c=0; + + pet_db= idb_alloc(DB_OPT_RELEASE_DATA); + + if( (fp=fopen(pet_txt,"r"))==NULL ) + return 1; + while(fgets(line, sizeof(line), fp)) + { + p = (struct s_pet*)aCalloc(sizeof(struct s_pet), 1); + if(p==NULL){ + ShowFatalError("int_pet: out of memory!\n"); + exit(EXIT_FAILURE); + } + memset(p,0,sizeof(struct s_pet)); + if(inter_pet_fromstr(line,p)==0 && p->pet_id>0){ + if( p->pet_id >= pet_newid) + pet_newid=p->pet_id+1; + idb_put(pet_db,p->pet_id,p); + }else{ + ShowError("int_pet: broken data [%s] line %d\n",pet_txt,c); + aFree(p); + } + c++; + } + fclose(fp); + return 0; +} + +void inter_pet_final() +{ + pet_db->destroy(pet_db, NULL); + return; +} + +int inter_pet_save_sub(DBKey key,void *data,va_list ap) +{ + char line[8192]; + FILE *fp; + inter_pet_tostr(line,(struct s_pet *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s\n",line); + return 0; +} + +int inter_pet_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(pet_txt,&lock))==NULL ){ + ShowError("int_pet: can't write [%s] !!! data is lost !!!\n",pet_txt); + return 1; + } + pet_db->foreach(pet_db,inter_pet_save_sub,fp); + lock_fclose(fp,pet_txt,&lock); + return 0; +} + +int inter_pet_delete(int pet_id) +{ + struct s_pet *p; + p = (struct s_pet*)idb_get(pet_db,pet_id); + if( p == NULL) + return 1; + else { + idb_remove(pet_db,pet_id); + ShowInfo("Deleted pet (pet_id: %d)\n",pet_id); + } + return 0; +} + +int mapif_pet_created(int fd,int account_id,struct s_pet *p) +{ + WFIFOHEAD(fd, 11); + WFIFOW(fd,0)=0x3880; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->pet_id; + ShowInfo("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) +{ + WFIFOHEAD(fd, sizeof(struct s_pet) + 9); + 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) +{ + WFIFOHEAD(fd, sizeof(struct s_pet) + 9); + 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) +{ + WFIFOHEAD(fd, 7); + 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) +{ + WFIFOHEAD(fd, 3); + 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) +{ + struct s_pet *p; + p= (struct s_pet *) aCalloc(sizeof(struct s_pet), 1); + if(p==NULL){ + ShowFatalError("int_pet: out of memory !\n"); + mapif_pet_created(fd,account_id,NULL); + return 0; + } +// memset(p,0,sizeof(struct s_pet)); unnecessary after aCalloc [Skotlex] + p->pet_id = pet_newid++; + memcpy(p->name,pet_name,NAME_LENGTH); + if(incuvate == 1) + p->account_id = p->char_id = 0; + else { + p->account_id = account_id; + p->char_id = char_id; + } + p->class_ = pet_class; + p->level = pet_lv; + p->egg_id = pet_egg_id; + p->equip = pet_equip; + p->intimate = intimate; + p->hungry = hungry; + p->rename_flag = rename_flag; + p->incuvate = incuvate; + + 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; + + idb_put(pet_db,p->pet_id,p); + + mapif_pet_created(fd,account_id,p); + + return 0; +} + +int mapif_load_pet(int fd,int account_id,int char_id,int pet_id) +{ + struct s_pet *p; + p = (struct s_pet*)idb_get(pet_db,pet_id); + if(p!=NULL) { + if(p->incuvate == 1) { + p->account_id = p->char_id = 0; + mapif_pet_info(fd,account_id,p); + } + else if(account_id == p->account_id && char_id == p->char_id) + mapif_pet_info(fd,account_id,p); + else + mapif_pet_noinfo(fd,account_id); + } + else + mapif_pet_noinfo(fd,account_id); + + return 0; +} + +static void* create_pet(DBKey key, va_list args) { + struct s_pet *p; + p=(struct s_pet *)aCalloc(sizeof(struct s_pet),1); + p->pet_id = key.i; + return p; +} +int mapif_save_pet(int fd,int account_id,struct s_pet *data) +{ + struct s_pet *p; + int pet_id, len; + RFIFOHEAD(fd); + 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{ + pet_id = data->pet_id; + if (pet_id == 0) + pet_id = data->pet_id = pet_newid++; + p = (struct s_pet*)idb_ensure(pet_db,pet_id,create_pet); + 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; + memcpy(p,data,sizeof(struct s_pet)); + if(p->incuvate == 1) + p->account_id = p->char_id = 0; + + 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) +{ + RFIFOHEAD(fd); + mapif_create_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOW(fd,14),RFIFOW(fd,16),RFIFOW(fd,18), + RFIFOW(fd,20),RFIFOB(fd,22),RFIFOB(fd,23),(char*)RFIFOP(fd,24)); + return 0; +} + +int mapif_parse_LoadPet(int fd) +{ + RFIFOHEAD(fd); + mapif_load_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +int mapif_parse_SavePet(int fd) +{ + RFIFOHEAD(fd); + mapif_save_pet(fd,RFIFOL(fd,4),(struct s_pet *)RFIFOP(fd,8)); + return 0; +} + +int mapif_parse_DeletePet(int fd) +{ + RFIFOHEAD(fd); + mapif_delete_pet(fd,RFIFOL(fd,2)); + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_pet_parse_frommap(int fd) +{ + RFIFOHEAD(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; +} +#endif //TXT_SQL_CONVERT diff --git a/src/char/int_pet.h b/src/char/int_pet.h new file mode 100644 index 000000000..9234af109 --- /dev/null +++ b/src/char/int_pet.h @@ -0,0 +1,21 @@ +// 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_ + +struct s_pet; + +int inter_pet_init(void); +void inter_pet_final(void); +int inter_pet_save(void); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +extern char pet_txt[1024]; + +//Exported for use in the TXT-SQL converter. +int inter_pet_fromstr(char *str,struct s_pet *p); + +#endif /* _INT_PET_H_ */ diff --git a/src/char/int_status.c b/src/char/int_status.c new file mode 100644 index 000000000..bf5d94155 --- /dev/null +++ b/src/char/int_status.c @@ -0,0 +1,183 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "int_status.h" + +#include <stdio.h> + +// Contains all the status change data in-memory. [Skotlex] +static DBMap* scdata_db = NULL; // int char_id -> struct scdata* +char scdata_txt[1024]="save/scdata.txt"; //By [Skotlex] + +#ifdef ENABLE_SC_SAVING +static void* create_scdata(DBKey key, va_list args) +{ + struct scdata *data; + data = (struct scdata*)aCalloc(1, sizeof(struct scdata)); + data->account_id = va_arg(args, int); + data->char_id = key.i; + return data; +} + +/*========================================== + * Loads status change data of the player given. [Skotlex] + *------------------------------------------*/ +struct scdata* status_search_scdata(int aid, int cid) +{ + return (struct scdata*)scdata_db->ensure(scdata_db, i2key(cid), create_scdata, aid); +} + +/*========================================== + * Deletes status change data of the player given. [Skotlex] + * Should be invoked after the data of said player was successfully loaded. + *------------------------------------------*/ +void status_delete_scdata(int aid, int cid) +{ + struct scdata* scdata = (struct scdata*)idb_remove(scdata_db, cid); + if (scdata) + { + if (scdata->data) + aFree(scdata->data); + aFree(scdata); + } +} + + +static void inter_status_tostr(char* line, struct scdata *sc_data) +{ + int i, len; + + len = sprintf(line, "%d,%d,%d\t", sc_data->account_id, sc_data->char_id, sc_data->count); + for(i = 0; i < sc_data->count; i++) { + len += sprintf(line + len, "%d,%d,%d,%d,%d,%d\t", sc_data->data[i].type, sc_data->data[i].tick, + sc_data->data[i].val1, sc_data->data[i].val2, sc_data->data[i].val3, sc_data->data[i].val4); + } + return; +} + +static int inter_scdata_fromstr(char *line, struct scdata *sc_data) +{ + int i, len, next; + + if (sscanf(line,"%d,%d,%d\t%n",&sc_data->account_id, &sc_data->char_id, &sc_data->count, &next) < 3) + return 0; + + if (sc_data->count < 1) + return 0; + + sc_data->data = (struct status_change_data*)aCalloc(sc_data->count, sizeof (struct status_change_data)); + + for (i = 0; i < sc_data->count; i++) + { + if (sscanf(line + next, "%hu,%d,%d,%d,%d,%d\t%n", &sc_data->data[i].type, &sc_data->data[i].tick, + &sc_data->data[i].val1, &sc_data->data[i].val2, &sc_data->data[i].val3, &sc_data->data[i].val4, &len) < 6) + { + aFree(sc_data->data); + return 0; + } + next+=len; + } + return 1; +} +/*========================================== + * Loads all scdata from the given filename. + *------------------------------------------*/ +void status_load_scdata(const char* filename) +{ + FILE *fp; + int sd_count=0, sc_count=0; + char line[8192]; + struct scdata *sc; + + if ((fp = fopen(filename, "r")) == NULL) { + ShowError("status_load_scdata: Cannot open file %s!\n", filename); + return; + } + + while(fgets(line, sizeof(line), fp)) + { + sc = (struct scdata*)aCalloc(1, sizeof(struct scdata)); + if (inter_scdata_fromstr(line, sc)) { + sd_count++; + sc_count+= sc->count; + sc = (struct scdata*)idb_put(scdata_db, sc->char_id, sc); + if (sc) { + ShowError("Duplicate entry in %s for character %d\n", filename, sc->char_id); + if (sc->data) aFree(sc->data); + aFree(sc); + } + } else { + ShowError("status_load_scdata: Broken line data: %s\n", line); + aFree(sc); + } + } + fclose(fp); + ShowStatus("Loaded %d saved status changes for %d characters.\n", sc_count, sd_count); +} + +static int inter_status_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + struct scdata * sc_data; + FILE *fp; + + sc_data = (struct scdata *)data; + if (sc_data->count < 1) + return 0; + + fp = va_arg(ap, FILE *); + inter_status_tostr(line, sc_data); + fprintf(fp, "%s\n", line); + return 0; +} + +/*========================================== + * Saves all scdata to the given filename. + *------------------------------------------*/ +void inter_status_save() +{ + FILE *fp; + int lock; + + if ((fp = lock_fopen(scdata_txt, &lock)) == NULL) { + ShowError("int_status: can't write [%s] !!! data is lost !!!\n", scdata_txt); + return; + } + scdata_db->foreach(scdata_db, inter_status_save_sub, fp); + lock_fclose(fp,scdata_txt, &lock); +} + +/*========================================== + * Initializes db. + *------------------------------------------*/ +void status_init() +{ + scdata_db = idb_alloc(DB_OPT_BASE); + status_load_scdata(scdata_txt); +} + +/*========================================== + * Frees up memory. + *------------------------------------------*/ +static int scdata_db_final(DBKey k,void *d,va_list ap) +{ + struct scdata *data = (struct scdata*)d; + if (data->data) + aFree(data->data); + aFree(data); + return 0; +} + +/*========================================== + * Final cleanup. + *------------------------------------------*/ +void status_final(void) +{ + scdata_db->destroy(scdata_db, scdata_db_final); +} + +#endif //ENABLE_SC_SAVING diff --git a/src/char/int_status.h b/src/char/int_status.h new file mode 100644 index 000000000..3202cd88e --- /dev/null +++ b/src/char/int_status.h @@ -0,0 +1,23 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_STATUS_H_ +#define _INT_STATUS_H_ + +struct status_change_data; + +struct scdata { + int account_id, char_id; + int count; + struct status_change_data* data; +}; + +extern char scdata_txt[1024]; + +struct scdata* status_search_scdata(int aid, int cid); +void status_delete_scdata(int aid, int cid); +void inter_status_save(void); +void status_init(void); +void status_final(void); + +#endif /* _INT_STATUS_H_ */ diff --git a/src/char/int_storage.c b/src/char/int_storage.c new file mode 100644 index 000000000..5ae51b833 --- /dev/null +++ b/src/char/int_storage.c @@ -0,0 +1,481 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "char.h" +#include "inter.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_guild.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +// ファイル名のデフォルト +// inter_config_read()で再設定される +char storage_txt[1024]="save/storage.txt"; +char guild_storage_txt[1024]="save/g_storage.txt"; + +#ifndef TXT_SQL_CONVERT +static DBMap* storage_db; // int account_id -> struct storage* +static DBMap* guild_storage_db; // int guild_id -> struct guild_storage* + +// 倉庫データを文字列に変換 +int storage_tostr(char *str,struct storage *p) +{ + int i,j,f=0; + char *str_p = str; + str_p += sprintf(str_p,"%d,%d\t",p->account_id,p->storage_amount); + + for(i=0;i<MAX_STORAGE;i++) + if( (p->storage_[i].nameid) && (p->storage_[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->storage_[i].id,p->storage_[i].nameid,p->storage_[i].amount,p->storage_[i].equip, + p->storage_[i].identify,p->storage_[i].refine,p->storage_[i].attribute); + for(j=0; j<MAX_SLOTS; j++) + str_p += sprintf(str_p,",%d",p->storage_[i].card[j]); + str_p += sprintf(str_p," "); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} +#endif //TXT_SQL_CONVERT +// 文字列を倉庫データに変換 +int storage_fromstr(char *str,struct storage *p) +{ + int tmp_int[256]; + char tmp_str[256]; + int set,next,len,i,j; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t' && i < MAX_STORAGE;i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str, &len) == 8) { + p->storage_[i].id = tmp_int[0]; + p->storage_[i].nameid = tmp_int[1]; + p->storage_[i].amount = tmp_int[2]; + p->storage_[i].equip = tmp_int[3]; + p->storage_[i].identify = tmp_int[4]; + p->storage_[i].refine = tmp_int[5]; + p->storage_[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str, ",%d%[0-9,-]",&tmp_int[0], tmp_str) > 0; j++) + p->storage_[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } + else return 1; + } + if (i >= MAX_STORAGE && str[next] && str[next]!='\t') + ShowWarning("storage_fromstr: Found a storage line with more items than MAX_STORAGE (%d), remaining items have been discarded!\n", MAX_STORAGE); + return 0; +} +#ifndef TXT_SQL_CONVERT +int guild_storage_tostr(char *str,struct guild_storage *p) +{ + int i,j,f=0; + char *str_p = str; + str_p+=sprintf(str,"%d,%d\t",p->guild_id,p->storage_amount); + + for(i=0;i<MAX_GUILD_STORAGE;i++) + if( (p->storage_[i].nameid) && (p->storage_[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->storage_[i].id,p->storage_[i].nameid,p->storage_[i].amount,p->storage_[i].equip, + p->storage_[i].identify,p->storage_[i].refine,p->storage_[i].attribute); + for(j=0; j<MAX_SLOTS; j++) + str_p += sprintf(str_p,",%d",p->storage_[i].card[j]); + str_p += sprintf(str_p," "); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} +#endif //TXT_SQL_CONVERT +int guild_storage_fromstr(char *str,struct guild_storage *p) +{ + int tmp_int[256]; + char tmp_str[256]; + int set,next,len,i,j; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t' && i < MAX_GUILD_STORAGE;i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str, &len) == 8) + { + p->storage_[i].id = tmp_int[0]; + p->storage_[i].nameid = tmp_int[1]; + p->storage_[i].amount = tmp_int[2]; + p->storage_[i].equip = tmp_int[3]; + p->storage_[i].identify = tmp_int[4]; + p->storage_[i].refine = tmp_int[5]; + p->storage_[i].attribute = tmp_int[6]; + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str, ",%d%[0-9,-]",&tmp_int[0], tmp_str) > 0; j++) + p->storage_[i].card[j] = tmp_int[0]; + next += len; + if (str[next] == ' ') + next++; + } + else return 1; + } + if (i >= MAX_GUILD_STORAGE && str[next] && str[next]!='\t') + ShowWarning("guild_storage_fromstr: Found a storage line with more items than MAX_GUILD_STORAGE (%d), remaining items have been discarded!\n", MAX_GUILD_STORAGE); + return 0; +} +#ifndef TXT_SQL_CONVERT +static void* create_storage(DBKey key, va_list args) { + struct storage *s; + s = (struct storage *) aCalloc(sizeof(struct storage), 1); + s->account_id=key.i; + return s; +} + +// アカウントから倉庫データインデックスを得る(新規倉庫追加可能) +struct storage *account2storage(int account_id) +{ + struct storage *s; + s = (struct storage*)idb_ensure(storage_db, account_id, create_storage); + return s; +} + +static void* create_guildstorage(DBKey key, va_list args) { + struct guild_storage *gs = NULL; + gs = (struct guild_storage *) aCalloc(sizeof(struct guild_storage), 1); + gs->guild_id=key.i; + return gs; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(inter_guild_search(guild_id) != NULL) + gs = (struct guild_storage*)idb_ensure(guild_storage_db, guild_id, create_guildstorage); + return gs; +} + +//--------------------------------------------------------- +// 倉庫データを読み込む +int inter_storage_init() +{ + char line[65536]; + int c=0,tmp_int; + struct storage *s; + struct guild_storage *gs; + FILE *fp; + + storage_db = idb_alloc(DB_OPT_RELEASE_DATA); + + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + ShowError("can't read : %s\n",storage_txt); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + sscanf(line,"%d",&tmp_int); + s = (struct storage*)aCalloc(sizeof(struct storage), 1); + if(s==NULL){ + ShowFatalError("int_storage: out of memory!\n"); + exit(EXIT_FAILURE); + } +// memset(s,0,sizeof(struct storage)); aCalloc does this... + s->account_id=tmp_int; + if(s->account_id > 0 && storage_fromstr(line,s) == 0) { + idb_put(storage_db,s->account_id,s); + } + else{ + ShowError("int_storage: broken data [%s] line %d\n",storage_txt,c); + aFree(s); + } + c++; + } + fclose(fp); + + c = 0; + guild_storage_db = idb_alloc(DB_OPT_RELEASE_DATA); + + fp=fopen(guild_storage_txt,"r"); + if(fp==NULL){ + ShowError("can't read : %s\n",guild_storage_txt); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + sscanf(line,"%d",&tmp_int); + gs = (struct guild_storage*)aCalloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + ShowFatalError("int_storage: out of memory!\n"); + exit(EXIT_FAILURE); + } +// memset(gs,0,sizeof(struct guild_storage)); aCalloc... + gs->guild_id=tmp_int; + if(gs->guild_id > 0 && guild_storage_fromstr(line,gs) == 0) { + idb_put(guild_storage_db,gs->guild_id,gs); + } + else{ + ShowError("int_storage: broken data [%s] line %d\n",guild_storage_txt,c); + aFree(gs); + } + c++; + } + fclose(fp); + + return 0; +} + +void inter_storage_final() { + storage_db->destroy(storage_db, NULL); + guild_storage_db->destroy(guild_storage_db, NULL); + return; +} + +int inter_storage_save_sub(DBKey key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + storage_tostr(line,(struct storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s\n",line); + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(storage_txt,&lock))==NULL ){ + ShowError("int_storage: can't write [%s] !!! data is lost !!!\n",storage_txt); + return 1; + } + storage_db->foreach(storage_db,inter_storage_save_sub,fp); + lock_fclose(fp,storage_txt,&lock); + return 0; +} + +int inter_guild_storage_save_sub(DBKey key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + if(inter_guild_search(((struct guild_storage *)data)->guild_id) != NULL) { + guild_storage_tostr(line,(struct guild_storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s\n",line); + } + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_guild_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(guild_storage_txt,&lock))==NULL ){ + ShowError("int_storage: can't write [%s] !!! data is lost !!!\n",guild_storage_txt); + return 1; + } + guild_storage_db->foreach(guild_storage_db,inter_guild_storage_save_sub,fp); + lock_fclose(fp,guild_storage_txt,&lock); + return 0; +} + +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + struct storage *s = (struct storage*)idb_get(storage_db,account_id); + if(s) { + int i; + for(i=0;i<s->storage_amount;i++){ + if(s->storage_[i].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(s->storage_[i].card[1],s->storage_[i].card[2]) ); + } + idb_remove(storage_db,account_id); + } + return 0; +} + +// ギルド倉庫データ削除 +int inter_guild_storage_delete(int guild_id) +{ + struct guild_storage *gs = (struct guild_storage*)idb_get(guild_storage_db,guild_id); + if(gs) { + int i; + for(i=0;i<gs->storage_amount;i++){ + if(gs->storage_[i].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(gs->storage_[i].card[1],gs->storage_[i].card[2]) ); + } + idb_remove(guild_storage_db,guild_id); + } + return 0; +} + +//--------------------------------------------------------- +// map serverへの通信 + +// 倉庫データの送信 +int mapif_load_storage(int fd,int account_id) +{ + struct storage *s=account2storage(account_id); + WFIFOHEAD(fd, sizeof(struct storage)+8); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),s,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// 倉庫データ保存完了送信 +int mapif_save_storage_ack(int fd,int account_id) +{ + WFIFOHEAD(fd, 7); + 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) +{ + struct guild_storage *gs=guild2storage(guild_id); + WFIFOHEAD(fd, sizeof(struct guild_storage)+12); + WFIFOW(fd,0)=0x3818; + if(gs) { + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),gs,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) +{ + WFIFOHEAD(fd, 11); + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// map serverからの通信 + +// 倉庫データ要求受信 +int mapif_parse_LoadStorage(int fd) +{ + RFIFOHEAD(fd); + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// 倉庫データ受信&保存 +int mapif_parse_SaveStorage(int fd) +{ + struct storage *s; + int account_id, len; + RFIFOHEAD(fd); + account_id=RFIFOL(fd,4); + 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 { + s=account2storage(account_id); + memcpy(s,RFIFOP(fd,8),sizeof(struct storage)); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + RFIFOHEAD(fd); + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +int mapif_parse_SaveGuildStorage(int fd) +{ + struct guild_storage *gs; + int guild_id, len; + RFIFOHEAD(fd); + guild_id=RFIFOL(fd,8); + 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 { + gs=guild2storage(guild_id); + if(gs) { + memcpy(gs,RFIFOP(fd,12),sizeof(struct guild_storage)); + 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; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_storage_parse_frommap(int fd) +{ + RFIFOHEAD(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; +} +#endif //TXT_SQL_CONVERT diff --git a/src/char/int_storage.h b/src/char/int_storage.h new file mode 100644 index 000000000..dbc487c4c --- /dev/null +++ b/src/char/int_storage.h @@ -0,0 +1,25 @@ +// 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_ + +struct storage; +struct guild_storage; + +int inter_storage_init(void); +void inter_storage_final(void); +int inter_storage_save(void); +int inter_guild_storage_save(void); +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[1024]; +extern char guild_storage_txt[1024]; + +//Exported for use in the TXT-SQL converter. +int storage_fromstr(char *str,struct storage *p); +int guild_storage_fromstr(char *str,struct guild_storage *p); + +#endif /* _INT_STORAGE_H_ */ diff --git a/src/char/inter.c b/src/char/inter.c new file mode 100644 index 000000000..c987ce8f2 --- /dev/null +++ b/src/char/inter.c @@ -0,0 +1,711 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/db.h" +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "char.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_status.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_homun.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#define WISDATA_TTL (60*1000) // Existence time of Wisp/page data (60 seconds) + // that is the waiting time of answers of all map-servers +#define WISDELLIST_MAX 256 // Number of elements of Wisp/page data deletion list + +char accreg_txt[1024] = "save/accreg.txt"; +#ifndef TXT_SQL_CONVERT +char inter_log_filename[1024] = "log/inter.log"; +char main_chat_nick[16] = "Main"; + +static DBMap* accreg_db = NULL; // int account_id -> struct accreg* + +unsigned int party_share_level = 10; + +// 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, //0x3000-0x300f + -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, 36, 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,36, 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 + -1, 6,-1,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, //0x3040-0x304f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3050-0x305f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3060-0x306f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3070-0x307f + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3080-0x308f + -1,10,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3090-0x309f Homunculus packets [albator] +}; + +struct WisData { + int id, fd, count, len; + unsigned long tick; + unsigned char src[24], dst[24], msg[1024]; +}; +static DBMap* wis_db = NULL; // int wis_id -> struct WisData* +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + + +//-------------------------------------------------------- + +// アカウント変数を文字列へ変換 +int inter_accreg_tostr(char *str, struct accreg *reg) { + int j; + char *p = str; + + p += sprintf(p, "%d\t", reg->account_id); + for(j = 0; j < reg->reg_num; j++) { + p += sprintf(p,"%s,%s ", reg->reg[j].str, reg->reg[j].value); + } + + return 0; +} +#endif //TXT_SQL_CONVERT +// アカウント変数を文字列から変換 +int inter_accreg_fromstr(const char *str, struct accreg *reg) { + int j, n; + const char *p = str; + + if (sscanf(p, "%d\t%n", ®->account_id, &n ) != 1 || reg->account_id <= 0) + return 1; + + for(j = 0, p += n; j < ACCOUNT_REG_NUM; j++, p += n) { + if (sscanf(p, "%[^,],%[^ ] %n", reg->reg[j].str, reg->reg[j].value, &n) != 2) + break; + } + reg->reg_num = j; + + return 0; +} +#ifndef TXT_SQL_CONVERT +// アカウント変数の読み込み +int inter_accreg_init(void) { + char line[8192]; + FILE *fp; + int c = 0; + struct accreg *reg; + + accreg_db = idb_alloc(DB_OPT_RELEASE_DATA); + + if( (fp = fopen(accreg_txt, "r")) == NULL) + return 1; + while(fgets(line, sizeof(line), fp)) + { + reg = (struct accreg*)aCalloc(sizeof(struct accreg), 1); + if (reg == NULL) { + ShowFatalError("inter: accreg: out of memory!\n"); + exit(EXIT_FAILURE); + } + if (inter_accreg_fromstr(line, reg) == 0 && reg->account_id > 0) { + idb_put(accreg_db, reg->account_id, reg); + } else { + ShowError("inter: accreg: broken data [%s] line %d\n", accreg_txt, c); + aFree(reg); + } + c++; + } + fclose(fp); + + return 0; +} + +// アカウント変数のセーブ用 +int inter_accreg_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + struct accreg *reg = (struct accreg *)data; + + if (reg->reg_num > 0) { + inter_accreg_tostr(line,reg); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s\n", line); + } + + return 0; +} + +// アカウント変数のセーブ +int inter_accreg_save(void) { + FILE *fp; + int lock; + + if ((fp = lock_fopen(accreg_txt,&lock)) == NULL) { + ShowError("int_accreg: can't write [%s] !!! data is lost !!!\n", accreg_txt); + return 1; + } + accreg_db->foreach(accreg_db, inter_accreg_save_sub,fp); + lock_fclose(fp, accreg_txt, &lock); + + return 0; +} + +//-------------------------------------------------------- +#endif //TXT_SQL_CONVERT +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------*/ +static int inter_config_read(const char *cfgName) { + 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; + } + while(fgets(line, sizeof(line), fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "storage_txt") == 0) { + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if (strcmpi(w1, "party_txt") == 0) { + strncpy(party_txt, w2, sizeof(party_txt)); + } else if (strcmpi(w1, "pet_txt") == 0) { + strncpy(pet_txt, w2, sizeof(pet_txt)); + } else if (strcmpi(w1, "accreg_txt") == 0) { + strncpy(accreg_txt, w2, sizeof(accreg_txt)); + } else if (strcmpi(w1, "guild_txt") == 0) { + strncpy(guild_txt, w2, sizeof(guild_txt)); + } else if (strcmpi(w1, "castle_txt") == 0) { + strncpy(castle_txt, w2, sizeof(castle_txt)); + } else if (strcmpi(w1, "guild_storage_txt") == 0) { + strncpy(guild_storage_txt, w2, sizeof(guild_storage_txt)); +#ifndef TXT_SQL_CONVERT + } else if (strcmpi(w1, "homun_txt") == 0) { + strncpy(homun_txt, w2, sizeof(homun_txt)); + } else if (strcmpi(w1, "party_share_level") == 0) { + party_share_level = (unsigned int)atof(w2); + } else if (strcmpi(w1, "inter_log_filename") == 0) { + strncpy(inter_log_filename, w2, sizeof(inter_log_filename)); + } else if(strcmpi(w1,"log_inter")==0) { + log_inter = atoi(w2); + } else if(strcmpi(w1, "main_chat_nick")==0){ // Main chat nick [LuzZza] + strcpy(main_chat_nick, w2); +#endif //TXT_SQL_CONVERT + } else if (strcmpi(w1, "import") == 0) { + inter_config_read(w2); + } + } + fclose(fp); + + return 0; +} +#ifndef TXT_SQL_CONVERT +// ログ書き出し +int inter_log(char *fmt,...) { + FILE *logfp; + va_list ap; + + va_start(ap,fmt); + logfp = fopen(inter_log_filename, "a"); + if (logfp) { + vfprintf(logfp, fmt, ap); + fclose(logfp); + } + va_end(ap); + + return 0; +} + +// セーブ +int inter_save(void) { +#ifdef ENABLE_SC_SAVING + inter_status_save(); +#endif + inter_party_save(); + inter_guild_save(); + inter_storage_save(); + inter_guild_storage_save(); + inter_pet_save(); + inter_homun_save(); + inter_accreg_save(); + + return 0; +} +#endif //TXT_SQL_CONVERT +// 初期化 +int inter_init_txt(const char *file) { + inter_config_read(file); + +#ifndef TXT_SQL_CONVERT + wis_db = idb_alloc(DB_OPT_RELEASE_DATA); + + inter_party_init(); + inter_guild_init(); + inter_storage_init(); + inter_pet_init(); + inter_homun_init(); + inter_accreg_init(); +#endif //TXT_SQL_CONVERT + return 0; +} +#ifndef TXT_SQL_CONVERT +// finalize +void inter_final(void) { + accreg_db->destroy(accreg_db, NULL); + wis_db->destroy(wis_db, NULL); + + inter_party_final(); + inter_guild_final(); + inter_storage_final(); + inter_pet_final(); + inter_homun_final(); + + return; +} + +// マップサーバー接続 +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + +//-------------------------------------------------------- +// sended packets to map-server + +// GMメッセージ送信 +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; +} + +// Wisp/page transmission to one map-server +int mapif_wis_message2(struct WisData *wd, int fd) { + WFIFOHEAD(fd, 56+wd->len); + WFIFOW(fd, 0) = 0x3801; + WFIFOW(fd, 2) = 56 + wd->len; + WFIFOL(fd, 4) = wd->id; + memcpy(WFIFOP(fd, 8), wd->src, NAME_LENGTH); + memcpy(WFIFOP(fd,32), wd->dst, NAME_LENGTH); + memcpy(WFIFOP(fd,56), wd->msg, wd->len); + wd->count = 1; + WFIFOSET(fd,WFIFOW(fd,2)); + return 1; +} + +// Wisp/page transmission to all map-server +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; +} + +int mapif_wis_fail(int fd, char *src) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), src, 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); + return 0; + +} + +// Wisp/page transmission result to map-server +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; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(wd->fd, buf, 27); + + return 0; +} + +// Account registry transfer to map-server +static void mapif_account_reg(int fd, unsigned char *src) +{ + WBUFW(src,0)=0x3804; //NOTE: writing to RFIFO + mapif_sendallwos(fd, src, WBUFW(src,2)); +} + +// アカウント変数要求返信 +int mapif_account_reg_reply(int fd,int account_id, int char_id) +{ + struct accreg *reg = (struct accreg*)idb_get(accreg_db,account_id); + + WFIFOHEAD(fd, ACCOUNT_REG_NUM * 288+ 13); + WFIFOW(fd,0) = 0x3804; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 2; //Acc Reg + if (reg == NULL) { + WFIFOW(fd,2) = 13; + } else { + int i, p; + for (p=13,i = 0; i < reg->reg_num; i++) { + p+= sprintf((char*)WFIFOP(fd,p), "%s", reg->reg[i].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf((char*)WFIFOP(fd,p), "%s", reg->reg[i].value)+1; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +//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; + + WFIFOHEAD(fd, 7); + 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(void) { + 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 = (struct WisData*)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; +} + +//-------------------------------------------------------- +// received packets from map-server + +// GMメッセージ送信 +int mapif_parse_GMmessage(int fd) { + RFIFOHEAD(fd); + mapif_GMmessage(RFIFOP(fd,8), RFIFOW(fd,2), RFIFOL(fd,4), fd); + + return 0; +} + +static struct WisData* mapif_create_whisper(int fd, char* src, char* dst, char* mes, int meslen) +{ + static int wisid = 0; + struct WisData* wd = (struct WisData *)aCalloc(sizeof(struct WisData), 1); + if (wd == NULL){ + ShowFatalError("inter: WisRequest: out of memory !\n"); + return NULL; + } + wd->id = ++wisid; + wd->fd = fd; + wd->len= meslen; + memcpy(wd->src, src, NAME_LENGTH); + memcpy(wd->dst, dst, NAME_LENGTH); + memcpy(wd->msg, mes, meslen); + wd->tick = gettick(); + return wd; +} + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) +{ + struct mmo_charstatus* char_status; + struct WisData* wd; + char name[NAME_LENGTH]; + int fd2; + + 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; + } + + safestrncpy(name, (char*)RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex] + + // search if character exists before to ask all map-servers + char_status = search_character_byname(name); + if (char_status == NULL) + return mapif_wis_fail(fd, (char*)RFIFOP(fd, 4)); + + // Character exists. + // to be sure of the correct name, rewrite it + memset(name, 0, NAME_LENGTH); + strncpy(name, char_status->name, NAME_LENGTH); + // if source is destination, don't ask other servers. + if (strcmp((char*)RFIFOP(fd,4),name) == 0) + return mapif_wis_fail(fd, (char*)RFIFOP(fd, 4)); + + //Look for online character. + fd2 = search_character_online(char_status->account_id, char_status->char_id); + if (fd2 >= 0) { //Character online, send whisper. + wd = mapif_create_whisper(fd, (char*)RFIFOP(fd, 4), (char*)RFIFOP(fd,28), (char*)RFIFOP(fd,52), RFIFOW(fd,2)-52); + if (!wd) return 1; + idb_put(wis_db, wd->id, wd); + mapif_wis_message2(wd, fd2); + return 0; + } + //Not found. + return mapif_wis_fail(fd, (char*)RFIFOP(fd,4)); +} + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id, flag; + struct WisData *wd; + RFIFOHEAD(fd); + id = RFIFOL(fd,2); + flag = RFIFOB(fd,6); + wd = (struct WisData*)idb_get(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout or 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 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?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; +} + +static void* create_accreg(DBKey key, va_list args) { + struct accreg *reg; + reg = (struct accreg*)aCalloc(sizeof(struct accreg), 1); + reg->account_id = key.i; + return reg; +} + +// アカウント変数保存要求 +int mapif_parse_Registry(int fd) { + int j, p, len; + struct accreg *reg; + RFIFOHEAD(fd); + + switch (RFIFOB(fd,12)) { + case 3: //Character registry + return char_parse_Registry(RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,13), RFIFOW(fd,2)-13); + case 2: //Acc Reg + break; + case 1: //Acc Reg2, forward to login + return save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4); + default: //Error? + return 1; + } + reg = (struct accreg*)idb_ensure(accreg_db, RFIFOL(fd,4), create_accreg); + + for(j=0,p=13;j<ACCOUNT_REG_NUM && p<RFIFOW(fd,2);j++){ + sscanf((char*)RFIFOP(fd,p), "%31c%n",reg->reg[j].str,&len); + reg->reg[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf((char*)RFIFOP(fd,p), "%255c%n",reg->reg[j].value,&len); + reg->reg[j].value[len]='\0'; + p +=len+1; + } + reg->reg_num=j; + mapif_account_reg(fd, RFIFOP(fd,0)); // 他のMAPサーバーに送信 + + return 0; +} + +// Request the value of all registries. +int mapif_parse_RegistryRequest(int fd) +{ + RFIFOHEAD(fd); + //Load Char Registry + if (RFIFOB(fd,12)) + char_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + //Load Account Registry + if (RFIFOB(fd,11)) + mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + //Ask Login Server for Account2 values. + if (RFIFOB(fd,10)) + request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6)); + return 1; +} + +static void mapif_namechange_ack(int fd, int account_id, int char_id, int type, int flag, char *name){ + WFIFOHEAD(fd, NAME_LENGTH+13); + WFIFOW(fd, 0) =0x3806; + WFIFOL(fd, 2) =account_id; + WFIFOL(fd, 6) =char_id; + WFIFOB(fd,10) =type; + WFIFOB(fd,11) =flag; + memcpy(WFIFOP(fd, 12), name, NAME_LENGTH); + WFIFOSET(fd, NAME_LENGTH+13); +} + +int mapif_parse_NameChangeRequest(int fd) +{ + int account_id, char_id, type; + char* name; + int i; + + RFIFOHEAD(fd); + account_id = RFIFOL(fd, 2); + char_id = RFIFOL(fd, 6); + type = RFIFOB(fd, 10); + name = (char*)RFIFOP(fd, 11); + + // Check Authorised letters/symbols in the name + 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) { + mapif_namechange_ack(fd, account_id, char_id, type, 0, name); + return 0; + } + } 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) { + mapif_namechange_ack(fd, account_id, char_id, type, 0, name); + return 0; + } + } + //TODO: type holds the type of object to rename. + //If it were a player, it needs to have the guild information and db information + //updated here, because changing it on the map won't make it be saved [Skotlex] + + //name allowed. + mapif_namechange_ack(fd, account_id, char_id, type, 1, name); + return 0; +} + +//-------------------------------------------------------- + +// map server からの通信(1パケットのみ解析すること) +// エラーなら0(false)、処理できたなら1、 +// パケット長が足りなければ2をかえさなければならない +int inter_parse_frommap(int fd) { + int cmd, len; + RFIFOHEAD(fd); + cmd = RFIFOW(fd,0); + len = 0; + + // inter鯖管轄かを調べる + if (cmd < 0x3000 || cmd >= 0x3000 + ARRAYLENGTH(inter_recv_packet_length)) + 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; + case 0x3006: mapif_parse_NameChangeRequest(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; + if (inter_homun_parse_frommap(fd)) + break; + return 0; + } + RFIFOSKIP(fd, len); + return 1; +} + +// RFIFOのパケット長確認 +// 必要パケット長があればパケット長、まだ足りなければ0 +int inter_check_length(int fd, int length) { + if (length == -1) { // 可変パケット長 + RFIFOHEAD(fd); + if (RFIFOREST(fd) < 4) // パケット長が未着 + return 0; + length = RFIFOW(fd,2); + } + + if ((int)RFIFOREST(fd) < length) // パケットが未着 + return 0; + + return length; +} +#endif //TXT_SQL_CONVERT diff --git a/src/char/inter.h b/src/char/inter.h new file mode 100644 index 000000000..4752f600c --- /dev/null +++ b/src/char/inter.h @@ -0,0 +1,30 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTER_H_ +#define _INTER_H_ + +struct accreg; + +int inter_init_txt(const char *file); +void inter_final(void); +int inter_save(void); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); +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 unsigned int party_share_level; +extern char inter_log_filename[1024]; +extern char main_chat_nick[16]; + +//For TXT->SQL conversion +extern char accreg_txt[]; +int inter_accreg_fromstr(const char *str, struct accreg *reg); + +#endif /* _INTER_H_ */ |