diff options
author | Trojal <trojal@gmail.com> | 2013-01-10 20:09:39 -0800 |
---|---|---|
committer | shennetsind <ind@henn.et> | 2013-01-12 05:56:35 -0200 |
commit | c55855fcf627478f864c0f82a1a2f201fd407a38 (patch) | |
tree | b7f6d11b2058248d026f2d9944e8f4b6ac288d50 /src | |
parent | 51bfeb38eb139e97e0e1c096c85c15fba234f35b (diff) | |
parent | 38e583df21eccd9e4f31d38acaae32579c6f0d27 (diff) | |
download | hercules-c55855fcf627478f864c0f82a1a2f201fd407a38.tar.gz hercules-c55855fcf627478f864c0f82a1a2f201fd407a38.tar.bz2 hercules-c55855fcf627478f864c0f82a1a2f201fd407a38.tar.xz hercules-c55855fcf627478f864c0f82a1a2f201fd407a38.zip |
Test1, testing for the commit widget, need to edit something.
Test2, testing for the commit widget, need to edit something.
Signed-off-by: shennetsind <ind@henn.et>
Diffstat (limited to 'src')
179 files changed, 169681 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..94ccf4183 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,19 @@ + +# +# setup and static libraries +# +add_subdirectory( common ) +if( HAVE_common_sql ) + option( BUILD_SQL_SERVERS "build sql server executables" ON ) +else() + message( STATUS "Disabled sql server targets (requires common_sql)" ) +endif() + + +# +# targets +# +add_subdirectory( login ) +add_subdirectory( char ) +add_subdirectory( map ) +add_subdirectory( tool ) diff --git a/src/char/CMakeLists.txt b/src/char/CMakeLists.txt new file mode 100644 index 000000000..22b793bef --- /dev/null +++ b/src/char/CMakeLists.txt @@ -0,0 +1,60 @@ + +# +# setup +# +set( SQL_CHAR_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) + + +# +# char sql +# +if( BUILD_SQL_SERVERS ) +message( STATUS "Creating target char-server_sql" ) +set( SQL_CHAR_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/char.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_auction.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_elemental.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_guild.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_homun.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_mail.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_mercenary.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_party.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_pet.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_quest.h" + "${CMAKE_CURRENT_SOURCE_DIR}/int_storage.h" + "${CMAKE_CURRENT_SOURCE_DIR}/inter.h" + ) +set( SQL_CHAR_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/char.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_auction.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_elemental.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_guild.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_homun.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_mail.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_mercenary.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_party.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_pet.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_quest.c" + "${CMAKE_CURRENT_SOURCE_DIR}/int_storage.c" + "${CMAKE_CURRENT_SOURCE_DIR}/inter.c" + ) +set( DEPENDENCIES common_sql ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS}" ) +set( SOURCE_FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ${SQL_CHAR_HEADERS} ${SQL_CHAR_SOURCES} ) +source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ) +source_group( char FILES ${SQL_CHAR_HEADERS} ${SQL_CHAR_SOURCES} ) +include_directories( ${INCLUDE_DIRS} ) +add_executable( char-server_sql ${SOURCE_FILES} ) +add_dependencies( char-server_sql ${DEPENDENCIES} ) +target_link_libraries( char-server_sql ${LIBRARIES} ${DEPENDENCIES} ) +set_target_properties( char-server_sql PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +if( INSTALL_COMPONENT_RUNTIME ) + cpack_add_component( Runtime_charserver_sql DESCRIPTION "char-server (sql version)" DISPLAY_NAME "char-server_sql" GROUP Runtime ) + install( TARGETS char-server_sql + DESTINATION "." + COMPONENT Runtime_charserver_sql ) +endif( INSTALL_COMPONENT_RUNTIME ) +message( STATUS "Creating target char-server_sql - done" ) +endif( BUILD_SQL_SERVERS ) diff --git a/src/char/Makefile.in b/src/char/Makefile.in new file mode 100644 index 000000000..bfe9d1585 --- /dev/null +++ b/src/char/Makefile.in @@ -0,0 +1,76 @@ + +COMMON_H = $(shell ls ../common/*.h) + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +COMMON_SQL_OBJ = ../common/obj_sql/sql.o +COMMON_H = ../common/sql.h + +CHAR_OBJ = obj_sql/char.o obj_sql/inter.o obj_sql/int_party.o obj_sql/int_guild.o \ + obj_sql/int_storage.o obj_sql/int_pet.o obj_sql/int_homun.o obj_sql/int_mail.o obj_sql/int_auction.o obj_sql/int_quest.o obj_sql/int_mercenary.o obj_sql/int_elemental.o +CHAR_H = char.h inter.h int_party.h int_guild.h int_storage.h int_pet.h int_homun.h int_mail.h int_auction.h int_quest.h int_mercenary.h int_elemental.h + +HAVE_MYSQL=@HAVE_MYSQL@ +ifeq ($(HAVE_MYSQL),yes) + CHAR_SERVER_SQL_DEPENDS=obj_sql $(CHAR_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) +else + CHAR_SERVER_SQL_DEPENDS=needs_mysql +endif + +@SET_MAKE@ + +##################################################################### +.PHONY : all char-server_sql clean help + +all: char-server_sql + +char-server_sql: $(CHAR_SERVER_SQL_DEPENDS) + @echo " LD $@" + @@CC@ @LDFLAGS@ -o ../../char-server_sql@EXEEXT@ $(CHAR_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) @LIBS@ @MYSQL_LIBS@ + +clean: + @echo " CLEAN char" + @rm -rf *.o obj_sql ../../char-server_sql@EXEEXT@ + +help: + @echo "possible targets are 'char-server_sql' 'all' 'clean' 'help'" + @echo "'char-server_sql' - char server (SQL version)" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +needs_mysql: + @echo "MySQL not found or disabled by the configure script" + @exit 1 + +obj_sql: + @echo " MKDIR obj_sql" + @-mkdir obj_sql + +obj_sql/%.o: %.c $(CHAR_H) $(COMMON_H) $(COMMON_SQL_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +# missing object files +../common/obj_all/common.a: + @$(MAKE) -C ../common sql + +../common/obj_sql/common_sql.a: + @$(MAKE) -C ../common sql + +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/char/char.c b/src/char/char.c new file mode 100644 index 000000000..8bf2dee38 --- /dev/null +++ b/src/char/char.c @@ -0,0 +1,4800 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/core.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/mapindex.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "../common/utils.h" +#include "int_guild.h" +#include "int_homun.h" +#include "int_mercenary.h" +#include "int_elemental.h" +#include "int_party.h" +#include "int_storage.h" +#include "char.h" +#include "inter.h" + +#include <sys/types.h> +#include <time.h> +#include <signal.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" +#define SQL_CONF_NAME "conf/inter_athena.conf" + +char char_db[256] = "char"; +char scdata_db[256] = "sc_data"; +char cart_db[256] = "cart_inventory"; +char inventory_db[256] = "inventory"; +char charlog_db[256] = "charlog"; +char storage_db[256] = "storage"; +char interlog_db[256] = "interlog"; +char reg_db[256] = "global_reg_value"; +char skill_db[256] = "skill"; +char memo_db[256] = "memo"; +char guild_db[256] = "guild"; +char guild_alliance_db[256] = "guild_alliance"; +char guild_castle_db[256] = "guild_castle"; +char guild_expulsion_db[256] = "guild_expulsion"; +char guild_member_db[256] = "guild_member"; +char guild_position_db[256] = "guild_position"; +char guild_skill_db[256] = "guild_skill"; +char guild_storage_db[256] = "guild_storage"; +char party_db[256] = "party"; +char pet_db[256] = "pet"; +char mail_db[256] = "mail"; // MAIL SYSTEM +char auction_db[256] = "auction"; // Auctions System +char friend_db[256] = "friends"; +char hotkey_db[256] = "hotkey"; +char quest_db[256] = "quest"; +char homunculus_db[256] = "homunculus"; +char skill_homunculus_db[256] = "skill_homunculus"; +char mercenary_db[256] = "mercenary"; +char mercenary_owner_db[256] = "mercenary_owner"; +char ragsrvinfo_db[256] = "ragsrvinfo"; + +// show loading/saving messages +int save_log = 1; + +static DBMap* char_db_; // int char_id -> struct mmo_charstatus* + +char db_path[1024] = "db"; + +int db_use_sqldbs; + +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; + +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[NAME_LENGTH] = "Unknown"; // Name to use when the requested name cannot be determined +#define TRIM_CHARS "\255\xA0\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 allowed (or not) in a character name. by [Yor] + +int char_per_account = 0; //Maximum chars per account (default unlimited) [Sirius] +int char_del_level = 0; //From which level u can delete character [Lupus] +int char_del_delay = 86400; + +int log_char = 1; // loggin char or not [devil] +int log_inter = 1; // loggin inter or not [devil] + +// 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 group_id; // permission + uint32 version; + uint8 clienttype; + char new_name[NAME_LENGTH]; + char birthdate[10+1]; // YYYY-MM-DD +}; + +int max_connect_user = 0; +int gm_allow_group = -1; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 0; +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]; + +// check for exit signal +// 0 is saving complete +// other is char_id +unsigned int save_flag = 0; + +// Initial position (it's possible to set it in conf file) +struct point start_point = { 0, 53, 111 }; + +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) + int group_id; + unsigned changing_mapservers : 1; +}; + +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; // -2: unknown server, -1: not connected, 0+: id of 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, intptr_t data); +int delete_char_sql(int char_id); + +/** + * @see DBCreateData + */ +static DBData 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 = INVALID_TIMER; + return db_ptr2data(character); +} + +void set_char_charselect(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( character->server > -1 ) + if( server[character->server].users > 0 ) // Prevent this value from going negative. + server[character->server].users--; + + character->char_id = -1; + character->server = -1; + + if(character->waiting_disconnect != INVALID_TIMER) { + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = INVALID_TIMER; + } + + 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_online(int map_id, int char_id, int account_id) +{ + struct online_char_data* character; + struct mmo_charstatus *cp; + + //Update DB + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='1' WHERE `char_id`='%d' LIMIT 1", char_db, char_id) ) + Sql_ShowDebug(sql_handle); + + //Check to see for online conflicts + character = (struct online_char_data*)idb_ensure(online_char_db, account_id, create_online_char_data); + if( character->char_id != -1 && character->server > -1 && character->server != map_id ) + { + ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, map_id, account_id, char_id); + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 2); + } + + //Update state data + character->char_id = char_id; + character->server = map_id; + + if( character->server > -1 ) + server[character->server].users++; + + //Get rid of disconnect timer + if(character->waiting_disconnect != INVALID_TIMER) { + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = INVALID_TIMER; + } + + //Set char online in guild cache. If char is in memory, use the guild id on it, otherwise seek it. + cp = (struct mmo_charstatus*)idb_get(char_db_,char_id); + inter_guild_CharOnline(char_id, cp?cp->guild_id:-1); + + //Notify login server + 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 ( char_id == -1 ) + { + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='0' WHERE `account_id`='%d'", char_db, account_id) ) + Sql_ShowDebug(sql_handle); + } + else + { + struct mmo_charstatus* cp = (struct mmo_charstatus*)idb_get(char_db_,char_id); + inter_guild_CharOffline(char_id, cp?cp->guild_id:-1); + if (cp) + idb_remove(char_db_,char_id); + + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online`='0' WHERE `char_id`='%d' LIMIT 1", char_db, char_id) ) + Sql_ShowDebug(sql_handle); + } + + 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 ) + if( server[character->server].users > 0 ) // Prevent this value from going negative. + server[character->server].users--; + + if(character->waiting_disconnect != INVALID_TIMER){ + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = INVALID_TIMER; + } + + if(character->char_id == char_id) + { + character->char_id = -1; + character->server = -1; + } + + //FIXME? Why Kevin free'd the online information when the char was effectively in the map-server? + } + + //Remove char if 1- Set all offline, or 2- character is no longer connected to char-server. + if (login_fd > 0 && !session[login_fd]->flag.eof && (char_id == -1 || character == NULL || character->fd == -1)) + { + WFIFOHEAD(login_fd,6); + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + } +} + +/** + * @see DBApply + */ +static int char_db_setoffline(DBKey key, DBData *data, va_list ap) +{ + struct online_char_data* character = (struct online_char_data*)db_data2ptr(data); + int server = va_arg(ap, int); + if (server == -1) { + character->char_id = -1; + character->server = -1; + if(character->waiting_disconnect != INVALID_TIMER){ + delete_timer(character->waiting_disconnect, chardb_waiting_disconnect); + character->waiting_disconnect = INVALID_TIMER; + } + } else if (character->server == server) + character->server = -2; //In some map server that we aren't connected to. + return 0; +} + +/** + * @see DBApply + */ +static int char_db_kickoffline(DBKey key, DBData *data, va_list ap) +{ + struct online_char_data* character = (struct online_char_data*)db_data2ptr(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 appropriate. + if (character->server > -1) + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 1); + else if (character->waiting_disconnect == INVALID_TIMER) + 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); +} + +void set_all_offline_sql(void) +{ + //Set all players to 'OFFLINE' + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online` = '0'", char_db) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `online` = '0'", guild_member_db) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `connect_member` = '0'", guild_db) ) + Sql_ShowDebug(sql_handle); +} + +/** + * @see DBCreateData + */ +static DBData create_charstatus(DBKey key, va_list args) +{ + struct mmo_charstatus *cp; + cp = (struct mmo_charstatus *) aCalloc(1,sizeof(struct mmo_charstatus)); + cp->char_id = key.i; + return db_ptr2data(cp); +} + +int inventory_to_sql(const struct item items[], int max, int id); + +int mmo_char_tosql(int char_id, struct mmo_charstatus* p) +{ + int i = 0; + int count = 0; + int diff = 0; + char save_status[128]; //For displaying save information. [Skotlex] + struct mmo_charstatus *cp; + int errors = 0; //If there are any errors while saving, "cp" will not be updated at the end. + StringBuf buf; + + if (char_id!=p->char_id) return 0; + + cp = idb_ensure(char_db_, char_id, create_charstatus); + + StringBuf_Init(&buf); + memset(save_status, 0, sizeof(save_status)); + + //map inventory data + if( memcmp(p->inventory, cp->inventory, sizeof(p->inventory)) ) { + if (!inventory_to_sql(p->inventory, MAX_INVENTORY, p->char_id)) + strcat(save_status, " inventory"); + else + errors++; + } + + //map cart data + if( memcmp(p->cart, cp->cart, sizeof(p->cart)) ) { + if (!memitemdata_to_sql(p->cart, MAX_CART, p->char_id, TABLE_CART)) + strcat(save_status, " cart"); + else + errors++; + } + + //map storage data + if( memcmp(p->storage.items, cp->storage.items, sizeof(p->storage.items)) ) { + if (!memitemdata_to_sql(p->storage.items, MAX_STORAGE, p->account_id, TABLE_STORAGE)) + strcat(save_status, " storage"); + else + errors++; + } + + if ( + (p->base_exp != cp->base_exp) || (p->base_level != cp->base_level) || + (p->job_level != cp->job_level) || (p->job_exp != cp->job_exp) || + (p->zeny != cp->zeny) || + (p->last_point.map != cp->last_point.map) || + (p->last_point.x != cp->last_point.x) || (p->last_point.y != cp->last_point.y) || + (p->max_hp != cp->max_hp) || (p->hp != cp->hp) || + (p->max_sp != cp->max_sp) || (p->sp != cp->sp) || + (p->status_point != cp->status_point) || (p->skill_point != cp->skill_point) || + (p->str != cp->str) || (p->agi != cp->agi) || (p->vit != cp->vit) || + (p->int_ != cp->int_) || (p->dex != cp->dex) || (p->luk != cp->luk) || + (p->option != cp->option) || + (p->party_id != cp->party_id) || (p->guild_id != cp->guild_id) || + (p->pet_id != cp->pet_id) || (p->weapon != cp->weapon) || (p->hom_id != cp->hom_id) || + (p->ele_id != cp->ele_id) || (p->shield != cp->shield) || (p->head_top != cp->head_top) || + (p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) || (p->delete_date != cp->delete_date) || + (p->rename != cp->rename) || (p->robe != cp->robe) + ) + { //Save status + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `base_level`='%d', `job_level`='%d'," + "`base_exp`='%u', `job_exp`='%u', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d',`homun_id`='%d',`elemental_id`='%d'," + "`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `rename`='%d'," + "`delete_date`='%lu',`robe`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->party_id, p->guild_id, p->pet_id, p->hom_id, p->ele_id, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, + mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, p->rename, + (unsigned long)p->delete_date, // FIXME: platform-dependent size + p->robe, + p->account_id, p->char_id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } else + strcat(save_status, " status"); + } + + //Values that will seldom change (to speed up saving) + if ( + (p->hair != cp->hair) || (p->hair_color != cp->hair_color) || (p->clothes_color != cp->clothes_color) || + (p->class_ != cp->class_) || + (p->partner_id != cp->partner_id) || (p->father != cp->father) || + (p->mother != cp->mother) || (p->child != cp->child) || + (p->karma != cp->karma) || (p->manner != cp->manner) || + (p->fame != cp->fame) + ) + { + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `class`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d'," + "`partner_id`='%d', `father`='%d', `mother`='%d', `child`='%d'," + "`karma`='%d',`manner`='%d', `fame`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->class_, + p->hair, p->hair_color, p->clothes_color, + p->partner_id, p->father, p->mother, p->child, + p->karma, p->manner, p->fame, + p->account_id, p->char_id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } else + strcat(save_status, " status2"); + } + + /* Mercenary Owner */ + if( (p->mer_id != cp->mer_id) || + (p->arch_calls != cp->arch_calls) || (p->arch_faith != cp->arch_faith) || + (p->spear_calls != cp->spear_calls) || (p->spear_faith != cp->spear_faith) || + (p->sword_calls != cp->sword_calls) || (p->sword_faith != cp->sword_faith) ) + { + if (mercenary_owner_tosql(char_id, p)) + strcat(save_status, " mercenary"); + else + errors++; + } + + //memo points + if( memcmp(p->memo_point, cp->memo_point, sizeof(p->memo_point)) ) + { + char esc_mapname[NAME_LENGTH*2+1]; + + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", memo_db, p->char_id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + + //insert here. + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s`(`char_id`,`map`,`x`,`y`) VALUES ", memo_db); + for( i = 0, count = 0; i < MAX_MEMOPOINTS; ++i ) + { + if( p->memo_point[i].map ) + { + if( count ) + StringBuf_AppendStr(&buf, ","); + Sql_EscapeString(sql_handle, esc_mapname, mapindex_id2name(p->memo_point[i].map)); + StringBuf_Printf(&buf, "('%d', '%s', '%d', '%d')", char_id, esc_mapname, p->memo_point[i].x, p->memo_point[i].y); + ++count; + } + } + if( count ) + { + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + } + strcat(save_status, " memo"); + } + + //FIXME: is this neccessary? [ultramage] + for(i=0;i<MAX_SKILL;i++) + if ((p->skill[i].lv != 0) && (p->skill[i].id == 0)) + p->skill[i].id = i; // Fix skill tree + + + //skills + if( memcmp(p->skill, cp->skill, sizeof(p->skill)) ) + { + //`skill` (`char_id`, `id`, `lv`) + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", skill_db, p->char_id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s`(`char_id`,`id`,`lv`) VALUES ", skill_db); + //insert here. + for( i = 0, count = 0; i < MAX_SKILL; ++i ) + { + if( p->skill[i].id != 0 && p->skill[i].flag != SKILL_FLAG_TEMPORARY ) + { + if( p->skill[i].flag == SKILL_FLAG_PERMANENT && p->skill[i].lv == 0 ) + continue; + if( p->skill[i].flag != SKILL_FLAG_PERMANENT && (p->skill[i].flag - SKILL_FLAG_REPLACED_LV_0) == 0 ) + continue; + if( count ) + StringBuf_AppendStr(&buf, ","); + StringBuf_Printf(&buf, "('%d','%d','%d')", char_id, p->skill[i].id, (p->skill[i].flag == SKILL_FLAG_PERMANENT ? p->skill[i].lv : p->skill[i].flag - SKILL_FLAG_REPLACED_LV_0)); + ++count; + } + } + if( count ) + { + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + } + + strcat(save_status, " skills"); + } + + diff = 0; + for(i = 0; i < MAX_FRIENDS; i++){ + if(p->friends[i].char_id != cp->friends[i].char_id || + p->friends[i].account_id != cp->friends[i].account_id){ + diff = 1; + break; + } + } + + if(diff == 1) + { //Save friends + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", friend_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `friend_account`, `friend_id`) VALUES ", friend_db); + for( i = 0, count = 0; i < MAX_FRIENDS; ++i ) + { + if( p->friends[i].char_id > 0 ) + { + if( count ) + StringBuf_AppendStr(&buf, ","); + StringBuf_Printf(&buf, "('%d','%d','%d')", char_id, p->friends[i].account_id, p->friends[i].char_id); + count++; + } + } + if( count ) + { + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + } + strcat(save_status, " friends"); + } + +#ifdef HOTKEY_SAVING + // hotkeys + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "REPLACE INTO `%s` (`char_id`, `hotkey`, `type`, `itemskill_id`, `skill_lvl`) VALUES ", hotkey_db); + diff = 0; + for(i = 0; i < ARRAYLENGTH(p->hotkeys); i++){ + if(memcmp(&p->hotkeys[i], &cp->hotkeys[i], sizeof(struct hotkey))) + { + if( diff ) + StringBuf_AppendStr(&buf, ",");// not the first hotkey + StringBuf_Printf(&buf, "('%d','%u','%u','%u','%u')", char_id, (unsigned int)i, (unsigned int)p->hotkeys[i].type, p->hotkeys[i].id , (unsigned int)p->hotkeys[i].lv); + diff = 1; + } + } + if(diff) { + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } else + strcat(save_status, " hotkeys"); + } +#endif + StringBuf_Destroy(&buf); + if (save_status[0]!='\0' && save_log) + ShowInfo("Saved char %d - %s:%s.\n", char_id, p->name, save_status); + if (!errors) + memcpy(cp, p, sizeof(struct mmo_charstatus)); + return 0; +} + +/// Saves an array of 'item' entries into the specified table. +int memitemdata_to_sql(const struct item items[], int max, int id, int tableswitch) +{ + StringBuf buf; + SqlStmt* stmt; + int i; + int j; + const char* tablename; + const char* selectoption; + struct item item; // temp storage variable + bool* flag; // bit array for inventory matching + bool found; + int errors = 0; + + switch (tableswitch) { + case TABLE_INVENTORY: tablename = inventory_db; selectoption = "char_id"; break; + case TABLE_CART: tablename = cart_db; selectoption = "char_id"; break; + case TABLE_STORAGE: tablename = storage_db; selectoption = "account_id"; break; + case TABLE_GUILD_STORAGE: tablename = guild_storage_db; selectoption = "guild_id"; break; + default: + ShowError("Invalid table name!\n"); + return 1; + } + + + // The following code compares inventory with current database values + // and performs modification/deletion/insertion only on relevant rows. + // This approach is more complicated than a trivial delete&insert, but + // it significantly reduces cpu load on the database server. + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`"); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `%s`='%d'", tablename, selectoption, id); + + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_ERROR == SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); + return 1; + } + + SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &item.id, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &item.nameid, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &item.amount, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &item.equip, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR, &item.identify, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL); + for( j = 0; j < MAX_SLOTS; ++j ) + SqlStmt_BindColumn(stmt, 8+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL); + + // bit array indicating which inventory items have already been matched + flag = (bool*) aCalloc(max, sizeof(bool)); + + while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) + { + found = false; + // search for the presence of the item in the char's inventory + for( i = 0; i < max; ++i ) + { + // skip empty and already matched entries + if( items[i].nameid == 0 || flag[i] ) + continue; + + if( items[i].nameid == item.nameid + && items[i].card[0] == item.card[0] + && items[i].card[2] == item.card[2] + && items[i].card[3] == item.card[3] + ) { //They are the same item. + ARR_FIND( 0, MAX_SLOTS, j, items[i].card[j] != item.card[j] ); + if( j == MAX_SLOTS && + items[i].amount == item.amount && + items[i].equip == item.equip && + items[i].identify == item.identify && + items[i].refine == item.refine && + items[i].attribute == item.attribute && + items[i].expire_time == item.expire_time ) + ; //Do nothing. + else + { + // update all fields. + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u'", + tablename, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]); + StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id); + + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + } + + found = flag[i] = true; //Item dealt with, + break; //skip to next item in the db. + } + } + if( !found ) + {// Item not present in inventory, remove it. + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE from `%s` where `id`='%d' LIMIT 1", tablename, item.id) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + } + } + SqlStmt_Free(stmt); + + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`", tablename, selectoption); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_AppendStr(&buf, ") VALUES "); + + found = false; + // insert non-matched items into the db as new items + for( i = 0; i < max; ++i ) + { + // skip empty and already matched entries + if( items[i].nameid == 0 || flag[i] ) + continue; + + if( found ) + StringBuf_AppendStr(&buf, ","); + else + found = true; + + StringBuf_Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u', '%"PRIu64"'", + id, items[i].nameid, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].unique_id); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", '%d'", items[i].card[j]); + StringBuf_AppendStr(&buf, ")"); + + updateLastUid(items[i].unique_id); // Unique Non Stackable Item ID + } + dbUpdateUid(sql_handle); // Unique Non Stackable Item ID + + if( found && SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + errors++; + } + + StringBuf_Destroy(&buf); + aFree(flag); + + return errors; +} +/* pretty much a copy of memitemdata_to_sql except it handles inventory_db exclusively, + * - this is required because inventory db is the only one with the 'favorite' column. */ +int inventory_to_sql(const struct item items[], int max, int id) { + StringBuf buf; + SqlStmt* stmt; + int i; + int j; + struct item item; // temp storage variable + bool* flag; // bit array for inventory matching + bool found; + int errors = 0; + + + // The following code compares inventory with current database values + // and performs modification/deletion/insertion only on relevant rows. + // This approach is more complicated than a trivial delete&insert, but + // it significantly reduces cpu load on the database server. + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`"); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`='%d'", inventory_db, id); + + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_ERROR == SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); + return 1; + } + + SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &item.id, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &item.nameid, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &item.amount, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &item.equip, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR, &item.identify, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &item.favorite, 0, NULL, NULL); + for( j = 0; j < MAX_SLOTS; ++j ) + SqlStmt_BindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL); + + // bit array indicating which inventory items have already been matched + flag = (bool*) aCalloc(max, sizeof(bool)); + + while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) { + found = false; + // search for the presence of the item in the char's inventory + for( i = 0; i < max; ++i ) { + // skip empty and already matched entries + if( items[i].nameid == 0 || flag[i] ) + continue; + + if( items[i].nameid == item.nameid + && items[i].card[0] == item.card[0] + && items[i].card[2] == item.card[2] + && items[i].card[3] == item.card[3] + ) { //They are the same item. + ARR_FIND( 0, MAX_SLOTS, j, items[i].card[j] != item.card[j] ); + if( j == MAX_SLOTS && + items[i].amount == item.amount && + items[i].equip == item.equip && + items[i].identify == item.identify && + items[i].refine == item.refine && + items[i].attribute == item.attribute && + items[i].expire_time == item.expire_time && + items[i].favorite == item.favorite ) + ; //Do nothing. + else { + // update all fields. + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `favorite`='%d'", + inventory_db, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].favorite); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]); + StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id); + + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) { + Sql_ShowDebug(sql_handle); + errors++; + } + } + + found = flag[i] = true; //Item dealt with, + break; //skip to next item in the db. + } + } + if( !found ) {// Item not present in inventory, remove it. + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE from `%s` where `id`='%d' LIMIT 1", inventory_db, item.id) ) { + Sql_ShowDebug(sql_handle); + errors++; + } + } + } + SqlStmt_Free(stmt); + + StringBuf_Clear(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`", inventory_db); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_AppendStr(&buf, ") VALUES "); + + found = false; + // insert non-matched items into the db as new items + for( i = 0; i < max; ++i ) { + // skip empty and already matched entries + if( items[i].nameid == 0 || flag[i] ) + continue; + + if( found ) + StringBuf_AppendStr(&buf, ","); + else + found = true; + + StringBuf_Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u', '%d', '%"PRIu64"'", + id, items[i].nameid, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].favorite, items[i].unique_id); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", '%d'", items[i].card[j]); + StringBuf_AppendStr(&buf, ")"); + + updateLastUid(items[i].unique_id);// Unique Non Stackable Item ID + } + dbUpdateUid(sql_handle); + + if( found && SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) { + Sql_ShowDebug(sql_handle); + errors++; + } + + StringBuf_Destroy(&buf); + aFree(flag); + + return errors; +} + + +int mmo_char_tobuf(uint8* buf, struct mmo_charstatus* p); + +//===================================================================================================== +// Loads the basic character rooster for the given account. Returns total buffer used. +int mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) +{ + SqlStmt* stmt; + struct mmo_charstatus p; + int j = 0, i; + char last_map[MAP_NAME_LENGTH_EXT]; + + stmt = SqlStmt_Malloc(sql_handle); + if( stmt == NULL ) + { + SqlStmt_ShowDebug(stmt); + return 0; + } + memset(&p, 0, sizeof(p)); + + // read char data + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT " + "`char_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`," + "`status_point`,`skill_point`,`option`,`karma`,`manner`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`,`delete_date`," + "`robe`" + " FROM `%s` WHERE `account_id`='%d' AND `char_num` < '%d'", char_db, sd->account_id, MAX_CHARS) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &p.char_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_UCHAR, &p.slot, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_STRING, &p.name, sizeof(p.name), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_SHORT, &p.class_, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_UINT, &p.base_level, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_UINT, &p.job_level, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_UINT, &p.base_exp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &p.job_exp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_INT, &p.zeny, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_SHORT, &p.str, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 10, SQLDT_SHORT, &p.agi, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 11, SQLDT_SHORT, &p.vit, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 12, SQLDT_SHORT, &p.int_, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 13, SQLDT_SHORT, &p.dex, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 14, SQLDT_SHORT, &p.luk, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 15, SQLDT_INT, &p.max_hp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 16, SQLDT_INT, &p.hp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 17, SQLDT_INT, &p.max_sp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 18, SQLDT_INT, &p.sp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 19, SQLDT_UINT, &p.status_point, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 20, SQLDT_UINT, &p.skill_point, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 21, SQLDT_UINT, &p.option, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 22, SQLDT_UCHAR, &p.karma, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 23, SQLDT_SHORT, &p.manner, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 24, SQLDT_SHORT, &p.hair, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 25, SQLDT_SHORT, &p.hair_color, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 26, SQLDT_SHORT, &p.clothes_color, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 27, SQLDT_SHORT, &p.weapon, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 28, SQLDT_SHORT, &p.shield, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 29, SQLDT_SHORT, &p.head_top, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 30, SQLDT_SHORT, &p.head_mid, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 31, SQLDT_SHORT, &p.head_bottom, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 32, SQLDT_STRING, &last_map, sizeof(last_map), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 33, SQLDT_SHORT, &p.rename, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 34, SQLDT_UINT32, &p.delete_date, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 35, SQLDT_SHORT, &p.robe, 0, NULL, NULL) + ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return 0; + } + for( i = 0; i < MAX_CHARS && SQL_SUCCESS == SqlStmt_NextRow(stmt); i++ ) + { + p.last_point.map = mapindex_name2id(last_map); + sd->found_char[i] = p.char_id; + j += mmo_char_tobuf(WBUFP(buf, j), &p); + } + for( ; i < MAX_CHARS; i++ ) + sd->found_char[i] = -1; + + memset(sd->new_name,0,sizeof(sd->new_name)); + + SqlStmt_Free(stmt); + return j; +} + +//===================================================================================================== +int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything) +{ + int i,j; + char t_msg[128] = ""; + struct mmo_charstatus* cp; + StringBuf buf; + SqlStmt* stmt; + char last_map[MAP_NAME_LENGTH_EXT]; + char save_map[MAP_NAME_LENGTH_EXT]; + char point_map[MAP_NAME_LENGTH_EXT]; + struct point tmp_point; + struct item tmp_item; + struct s_skill tmp_skill; + struct s_friend tmp_friend; +#ifdef HOTKEY_SAVING + struct hotkey tmp_hotkey; + int hotkey_num; +#endif + + memset(p, 0, sizeof(struct mmo_charstatus)); + + if (save_log) ShowInfo("Char load request (%d)\n", char_id); + + stmt = SqlStmt_Malloc(sql_handle); + if( stmt == NULL ) + { + SqlStmt_ShowDebug(stmt); + return 0; + } + + // read char data + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT " + "`char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`," + "`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`," + "`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`," + "`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`robe`" + " FROM `%s` WHERE `char_id`=? LIMIT 1", char_db) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &p->char_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &p->account_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UCHAR, &p->slot, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_STRING, &p->name, sizeof(p->name), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_SHORT, &p->class_, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_UINT, &p->base_level, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_UINT, &p->job_level, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &p->base_exp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_UINT, &p->job_exp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_INT, &p->zeny, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 10, SQLDT_SHORT, &p->str, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 11, SQLDT_SHORT, &p->agi, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 12, SQLDT_SHORT, &p->vit, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 13, SQLDT_SHORT, &p->int_, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 14, SQLDT_SHORT, &p->dex, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 15, SQLDT_SHORT, &p->luk, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 16, SQLDT_INT, &p->max_hp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 17, SQLDT_INT, &p->hp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 18, SQLDT_INT, &p->max_sp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 19, SQLDT_INT, &p->sp, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 20, SQLDT_UINT, &p->status_point, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 21, SQLDT_UINT, &p->skill_point, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 22, SQLDT_UINT, &p->option, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 23, SQLDT_UCHAR, &p->karma, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 24, SQLDT_SHORT, &p->manner, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 25, SQLDT_INT, &p->party_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 26, SQLDT_INT, &p->guild_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 27, SQLDT_INT, &p->pet_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 28, SQLDT_INT, &p->hom_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 29, SQLDT_INT, &p->ele_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 30, SQLDT_SHORT, &p->hair, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 31, SQLDT_SHORT, &p->hair_color, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 32, SQLDT_SHORT, &p->clothes_color, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 33, SQLDT_SHORT, &p->weapon, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 34, SQLDT_SHORT, &p->shield, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 35, SQLDT_SHORT, &p->head_top, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 36, SQLDT_SHORT, &p->head_mid, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 37, SQLDT_SHORT, &p->head_bottom, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 38, SQLDT_STRING, &last_map, sizeof(last_map), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 39, SQLDT_SHORT, &p->last_point.x, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 40, SQLDT_SHORT, &p->last_point.y, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 41, SQLDT_STRING, &save_map, sizeof(save_map), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 42, SQLDT_SHORT, &p->save_point.x, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 43, SQLDT_SHORT, &p->save_point.y, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 44, SQLDT_INT, &p->partner_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 45, SQLDT_INT, &p->father, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 46, SQLDT_INT, &p->mother, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 47, SQLDT_INT, &p->child, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 48, SQLDT_INT, &p->fame, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 49, SQLDT_SHORT, &p->rename, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 50, SQLDT_UINT32, &p->delete_date, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 51, SQLDT_SHORT, &p->robe, 0, NULL, NULL) + ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return 0; + } + if( SQL_ERROR == SqlStmt_NextRow(stmt) ) + { + ShowError("Requested non-existant character id: %d!\n", char_id); + SqlStmt_Free(stmt); + return 0; + } + p->last_point.map = mapindex_name2id(last_map); + p->save_point.map = mapindex_name2id(save_map); + + strcat(t_msg, " status"); + + if (!load_everything) // For quick selection of data when displaying the char menu + { + SqlStmt_Free(stmt); + return 1; + } + + //read memo data + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `map`,`x`,`y` FROM `%s` WHERE `char_id`=? ORDER by `memo_id` LIMIT %d", memo_db, MAX_MEMOPOINTS) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &point_map, sizeof(point_map), NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &tmp_point.x, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &tmp_point.y, 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + for( i = 0; i < MAX_MEMOPOINTS && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + { + tmp_point.map = mapindex_name2id(point_map); + memcpy(&p->memo_point[i], &tmp_point, sizeof(tmp_point)); + } + strcat(t_msg, " memo"); + + //read inventory + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `expire_time`, `favorite`, `unique_id`) + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`"); + for( i = 0; i < MAX_SLOTS; ++i ) + StringBuf_Printf(&buf, ", `card%d`", i); + StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", inventory_db, MAX_INVENTORY); + + if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_item.id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &tmp_item.nameid, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &tmp_item.amount, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &tmp_item.equip, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR, &tmp_item.identify, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &tmp_item.refine, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &tmp_item.favorite, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + for( i = 0; i < MAX_SLOTS; ++i ) + if( SQL_ERROR == SqlStmt_BindColumn(stmt, 10+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + for( i = 0; i < MAX_INVENTORY && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + memcpy(&p->inventory[i], &tmp_item, sizeof(tmp_item)); + + strcat(t_msg, " inventory"); + + //read cart + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, expire_time`, `unique_id`) + StringBuf_Clear(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`"); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", cart_db, MAX_CART); + + if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_item.id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &tmp_item.nameid, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &tmp_item.amount, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &tmp_item.equip, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR, &tmp_item.identify, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &tmp_item.refine, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + for( i = 0; i < MAX_SLOTS; ++i ) + if( SQL_ERROR == SqlStmt_BindColumn(stmt, 9+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + for( i = 0; i < MAX_CART && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + memcpy(&p->cart[i], &tmp_item, sizeof(tmp_item)); + strcat(t_msg, " cart"); + + //read storage + storage_fromsql(p->account_id, &p->storage); + strcat(t_msg, " storage"); + + //read skill + //`skill` (`char_id`, `id`, `lv`) + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `id`, `lv` FROM `%s` WHERE `char_id`=? LIMIT %d", skill_db, MAX_SKILL) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_USHORT, &tmp_skill.id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_USHORT, &tmp_skill.lv, 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + tmp_skill.flag = SKILL_FLAG_PERMANENT; + + for( i = 0; i < MAX_SKILL && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + { + if( tmp_skill.id < ARRAYLENGTH(p->skill) ) + memcpy(&p->skill[tmp_skill.id], &tmp_skill, sizeof(tmp_skill)); + else + ShowWarning("mmo_char_fromsql: ignoring invalid skill (id=%u,lv=%u) of character %s (AID=%d,CID=%d)\n", tmp_skill.id, tmp_skill.lv, p->name, p->account_id, p->char_id); + } + strcat(t_msg, " skills"); + + //read friends + //`friends` (`char_id`, `friend_account`, `friend_id`) + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT c.`account_id`, c.`char_id`, c.`name` FROM `%s` c LEFT JOIN `%s` f ON f.`friend_account` = c.`account_id` AND f.`friend_id` = c.`char_id` WHERE f.`char_id`=? LIMIT %d", char_db, friend_db, MAX_FRIENDS) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_friend.account_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &tmp_friend.char_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_STRING, &tmp_friend.name, sizeof(tmp_friend.name), NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + for( i = 0; i < MAX_FRIENDS && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + memcpy(&p->friends[i], &tmp_friend, sizeof(tmp_friend)); + strcat(t_msg, " friends"); + +#ifdef HOTKEY_SAVING + //read hotkeys + //`hotkey` (`char_id`, `hotkey`, `type`, `itemskill_id`, `skill_lvl` + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `hotkey`, `type`, `itemskill_id`, `skill_lvl` FROM `%s` WHERE `char_id`=?", hotkey_db) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &hotkey_num, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_UCHAR, &tmp_hotkey.type, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UINT, &tmp_hotkey.id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &tmp_hotkey.lv, 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) + { + if( hotkey_num >= 0 && hotkey_num < MAX_HOTKEYS ) + memcpy(&p->hotkeys[hotkey_num], &tmp_hotkey, sizeof(tmp_hotkey)); + else + ShowWarning("mmo_char_fromsql: ignoring invalid hotkey (hotkey=%d,type=%u,id=%u,lv=%u) of character %s (AID=%d,CID=%d)\n", hotkey_num, tmp_hotkey.type, tmp_hotkey.id, tmp_hotkey.lv, p->name, p->account_id, p->char_id); + } + strcat(t_msg, " hotkeys"); +#endif + + /* Mercenary Owner DataBase */ + mercenary_owner_fromsql(char_id, p); + strcat(t_msg, " mercenary"); + + + if (save_log) ShowInfo("Loaded char (%d - %s): %s\n", char_id, p->name, t_msg); //ok. all data load successfuly! + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); + + cp = idb_ensure(char_db_, char_id, create_charstatus); + memcpy(cp, p, sizeof(struct mmo_charstatus)); + return 1; +} + +//========================================================================================================== +int mmo_char_sql_init(void) +{ + char_db_= idb_alloc(DB_OPT_RELEASE_DATA); + + ShowStatus("Characters per Account: '%d'.\n", char_per_account); + + //the 'set offline' part is now in check_login_conn ... + //if the server connects to loginserver + //it will dc all off players + //and send the loginserver the new state.... + + // Force all users offline in sql when starting char-server + // (useful when servers crashs and don't clean the database) + set_all_offline_sql(); + + return 0; +} + +//----------------------------------- +// Function to change chararcter's names +//----------------------------------- +int rename_char_sql(struct char_session_data *sd, int char_id) +{ + struct mmo_charstatus char_dat; + char esc_name[NAME_LENGTH*2+1]; + + if( sd->new_name[0] == 0 ) // Not ready for rename + return 2; + + if( !mmo_char_fromsql(char_id, &char_dat, false) ) // Only the short data is needed. + return 2; + + if( char_dat.rename == 0 ) + return 1; + + Sql_EscapeStringLen(sql_handle, esc_name, sd->new_name, strnlen(sd->new_name, NAME_LENGTH)); + + // check if the char exist + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT 1 FROM `%s` WHERE `name` LIKE '%s' LIMIT 1", char_db, esc_name) ) + { + Sql_ShowDebug(sql_handle); + return 4; + } + + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `name` = '%s', `rename` = '%d' WHERE `char_id` = '%d'", char_db, esc_name, --char_dat.rename, char_id) ) + { + Sql_ShowDebug(sql_handle); + return 3; + } + + // Change character's name into guild_db. + if( char_dat.guild_id ) + inter_guild_charname_changed(char_dat.guild_id, sd->account_id, char_id, sd->new_name); + + safestrncpy(char_dat.name, sd->new_name, NAME_LENGTH); + memset(sd->new_name,0,sizeof(sd->new_name)); + + // log change + if( log_char ) + { + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '0', '0', '0', '0', '0', '0', '0', '0')", + charlog_db, "change char name", sd->account_id, char_dat.slot, esc_name) ) + Sql_ShowDebug(sql_handle); + } + + return 0; +} + +int check_char_name(char * name, char * esc_name) +{ + int i; + + // check length of character name + if( name[0] == '\0' ) + return -2; // empty character name + /** + * The client does not allow you to create names with less than 4 characters, however, + * the use of WPE can bypass this, and this fixes the exploit. + **/ + if( strlen( name ) < 4 ) + return -2; + // 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; + } + if( name_ignoring_case ) { + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT 1 FROM `%s` WHERE BINARY `name` = '%s' LIMIT 1", char_db, esc_name) ) { + Sql_ShowDebug(sql_handle); + return -2; + } + } else { + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT 1 FROM `%s` WHERE `name` = '%s' LIMIT 1", char_db, esc_name) ) { + Sql_ShowDebug(sql_handle); + return -2; + } + } + if( Sql_NumRows(sql_handle) > 0 ) + return -1; // name already exists + + return 0; +} + +//----------------------------------- +// Function to create a new character +//----------------------------------- +#if PACKETVER >= 20120307 +int make_new_char_sql(struct char_session_data* sd, char* name_, int slot, int hair_color, int hair_style) { + int str = 1, agi = 1, vit = 1, int_ = 1, dex = 1, luk = 1; +#else +int make_new_char_sql(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) { +#endif + + char name[NAME_LENGTH]; + char esc_name[NAME_LENGTH*2+1]; + int char_id, flag; + + safestrncpy(name, name_, NAME_LENGTH); + normalize_name(name,TRIM_CHARS); + Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); + + flag = check_char_name(name,esc_name); + if( flag < 0 ) + return flag; + + //check other inputs +#if PACKETVER >= 20120307 + if(slot >= MAX_CHARS) +#else + if((slot >= MAX_CHARS) // slots + || (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 +#endif + return -2; // invalid input + + + // check the number of already existing chars in this account + if( char_per_account != 0 ) { + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT 1 FROM `%s` WHERE `account_id` = '%d'", char_db, sd->account_id) ) + Sql_ShowDebug(sql_handle); + if( Sql_NumRows(sql_handle) >= char_per_account ) + return -2; // character account limit exceeded + } + + // check char slot + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT 1 FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' LIMIT 1", char_db, sd->account_id, slot) ) + Sql_ShowDebug(sql_handle); + if( Sql_NumRows(sql_handle) > 0 ) + return -2; // slot already in use + + // validation success, log result + if (log_char) { + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db, "make new char", sd->account_id, slot, esc_name, str, agi, vit, int_, dex, luk, hair_style, hair_color) ) + Sql_ShowDebug(sql_handle); + } +#if PACKETVER >= 20120307 + //Insert the new char entry to the database + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`account_id`, `char_num`, `name`, `zeny`, `status_point`,`str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%s', '%d', '%d','%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, sd->account_id , slot, esc_name, start_zeny, 48, str, agi, vit, int_, dex, luk, + (40 * (100 + vit)/100) , (40 * (100 + vit)/100 ), (11 * (100 + int_)/100), (11 * (100 + int_)/100), hair_style, hair_color, + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y) ) + { + Sql_ShowDebug(sql_handle); + return -2; //No, stop the procedure! + } +#else + //Insert the new char entry to the database + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`account_id`, `char_num`, `name`, `zeny`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, sd->account_id , slot, esc_name, start_zeny, str, agi, vit, int_, dex, luk, + (40 * (100 + vit)/100) , (40 * (100 + vit)/100 ), (11 * (100 + int_)/100), (11 * (100 + int_)/100), hair_style, hair_color, + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y) ) + { + Sql_ShowDebug(sql_handle); + return -2; //No, stop the procedure! + } +#endif + //Retrieve the newly auto-generated char id + char_id = (int)Sql_LastInsertId(sql_handle); + //Give the char the default items + if (start_weapon > 0) { //add Start Weapon (Knife?) + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `identify`) VALUES ('%d', '%d', '%d', '%d')", inventory_db, char_id, start_weapon, 1, 1) ) + Sql_ShowDebug(sql_handle); + } + if (start_armor > 0) { //Add default armor (cotton shirt?) + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `identify`) VALUES ('%d', '%d', '%d', '%d')", inventory_db, char_id, start_armor, 1, 1) ) + Sql_ShowDebug(sql_handle); + } + + ShowInfo("Created char: account: %d, char: %d, slot: %d, name: %s\n", sd->account_id, char_id, slot, name); + return char_id; +} + +/*----------------------------------------------------------------------------------------------------------*/ +/* Divorce Players */ +/*----------------------------------------------------------------------------------------------------------*/ +int divorce_char_sql(int partner_id1, int partner_id2) +{ + unsigned char buf[64]; + + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `partner_id`='0' WHERE `char_id`='%d' OR `char_id`='%d' LIMIT 2", char_db, partner_id1, partner_id2) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE (`nameid`='%d' OR `nameid`='%d') AND (`char_id`='%d' OR `char_id`='%d') LIMIT 2", inventory_db, WEDDING_RING_M, WEDDING_RING_F, partner_id1, partner_id2) ) + Sql_ShowDebug(sql_handle); + + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = partner_id1; + WBUFL(buf,6) = partner_id2; + mapif_sendall(buf,10); + + return 0; +} + +/*----------------------------------------------------------------------------------------------------------*/ +/* Delete char - davidsiaw */ +/*----------------------------------------------------------------------------------------------------------*/ +/* Returns 0 if successful + * Returns < 0 for error + */ +int delete_char_sql(int char_id) +{ + char name[NAME_LENGTH]; + char esc_name[NAME_LENGTH*2+1]; //Name needs be escaped. + int account_id, party_id, guild_id, hom_id, base_level, partner_id, father_id, mother_id, elemental_id; + char *data; + size_t len; + + if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `name`,`account_id`,`party_id`,`guild_id`,`base_level`,`homun_id`,`partner_id`,`father`,`mother`,`elemental_id` FROM `%s` WHERE `char_id`='%d'", char_db, char_id)) + Sql_ShowDebug(sql_handle); + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + ShowError("delete_char_sql: Unable to fetch character data, deletion aborted.\n"); + Sql_FreeResult(sql_handle); + return -1; + } + + Sql_GetData(sql_handle, 0, &data, &len); safestrncpy(name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 1, &data, NULL); account_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); party_id = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); guild_id = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); base_level = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); hom_id = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); partner_id = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); father_id = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); mother_id = atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); + elemental_id = atoi(data); + + Sql_EscapeStringLen(sql_handle, esc_name, name, min(len, NAME_LENGTH)); + Sql_FreeResult(sql_handle); + + //check for config char del condition [Lupus] + // TODO: Move this out to packet processing (0x68/0x1fb). + if( ( char_del_level > 0 && base_level >= char_del_level ) + || ( char_del_level < 0 && base_level <= -char_del_level ) + ) { + ShowInfo("Char deletion aborted: %s, BaseLevel: %i\n", name, base_level); + return -1; + } + + /* Divorce [Wizputer] */ + if( partner_id ) + divorce_char_sql(char_id, partner_id); + + /* De-addopt [Zephyrus] */ + if( father_id || mother_id ) + { // Char is Baby + unsigned char buf[64]; + + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `child`='0' WHERE `char_id`='%d' OR `char_id`='%d'", char_db, father_id, mother_id) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '410'AND (`char_id`='%d' OR `char_id`='%d')", skill_db, father_id, mother_id) ) + Sql_ShowDebug(sql_handle); + + WBUFW(buf,0) = 0x2b25; + WBUFL(buf,2) = father_id; + WBUFL(buf,6) = mother_id; + WBUFL(buf,10) = char_id; // Baby + mapif_sendall(buf,14); + } + + //Make the character leave the party [Skotlex] + if (party_id) + inter_party_leave(party_id, account_id, char_id); + + /* delete char's pet */ + //Delete the hatched pet if you have one... + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d' AND `incuvate` = '0'", pet_db, char_id) ) + Sql_ShowDebug(sql_handle); + + //Delete all pets that are stored in eggs (inventory + cart) + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` JOIN `%s` ON `pet_id` = `card1`|`card2`<<16 WHERE `%s`.char_id = '%d' AND card0 = -256", pet_db, pet_db, inventory_db, inventory_db, char_id) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` JOIN `%s` ON `pet_id` = `card1`|`card2`<<16 WHERE `%s`.char_id = '%d' AND card0 = -256", pet_db, pet_db, cart_db, cart_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* remove homunculus */ + if( hom_id ) + mapif_homunculus_delete(hom_id); + + /* remove elemental */ + if (elemental_id) + mapif_elemental_delete(elemental_id); + + /* remove mercenary data */ + mercenary_owner_delete(char_id); + + /* delete char's friends list */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", friend_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete char from other's friend list */ + //NOTE: Won't this cause problems for people who are already online? [Skotlex] + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `friend_id` = '%d'", friend_db, char_id) ) + Sql_ShowDebug(sql_handle); + +#ifdef HOTKEY_SAVING + /* delete hotkeys */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", hotkey_db, char_id) ) + Sql_ShowDebug(sql_handle); +#endif + + /* delete inventory */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", inventory_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete cart inventory */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", cart_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete memo areas */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", memo_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete character registry */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'", reg_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete skills */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", skill_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* delete mails (only received) */ + if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `dest_id`='%d'", mail_db, char_id)) + Sql_ShowDebug(sql_handle); + +#ifdef ENABLE_SC_SAVING + /* status changes */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, account_id, char_id) ) + Sql_ShowDebug(sql_handle); +#endif + + if (log_char) { + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`time`, `account_id`,`char_num`,`char_msg`,`name`) VALUES (NOW(), '%d', '%d', 'Deleted char (CID %d)', '%s')", + charlog_db, account_id, 0, char_id, esc_name) ) + Sql_ShowDebug(sql_handle); + } + + /* delete character */ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", char_db, char_id) ) + Sql_ShowDebug(sql_handle); + + /* No need as we used inter_guild_leave [Skotlex] + // Also delete info from guildtables. + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", guild_member_db, char_id) ) + Sql_ShowDebug(sql_handle); + */ + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `guild_id` FROM `%s` WHERE `char_id` = '%d'", guild_db, char_id) ) + Sql_ShowDebug(sql_handle); + else if( Sql_NumRows(sql_handle) > 0 ) + mapif_parse_BreakGuild(0,guild_id); + else if( guild_id ) + inter_guild_leave(guild_id, account_id, char_id);// Leave your guild. + return 0; +} + +//--------------------------------------------------------------------- +// 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 < ARRAYLENGTH(server); 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 +#define MAX_CHAR_BUF 144 //Max size (for WFIFOHEAD calls) +int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p) +{ + unsigned short offset = 0; + uint8* buf; + + if( buffer == NULL || p == NULL ) + return 0; + + buf = WBUFP(buffer,0); + WBUFL(buf,0) = p->char_id; + WBUFL(buf,4) = min(p->base_exp, INT32_MAX); + WBUFL(buf,8) = p->zeny; + WBUFL(buf,12) = min(p->job_exp, INT32_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, INT16_MAX); + WBUFL(buf,42) = p->hp; + WBUFL(buf,46) = p->max_hp; + offset+=4; + buf = WBUFP(buffer,offset); + WBUFW(buf,46) = min(p->sp, INT16_MAX); + WBUFW(buf,48) = min(p->max_sp, INT16_MAX); + WBUFW(buf,50) = DEFAULT_WALK_SPEED; // p->speed; + WBUFW(buf,52) = p->class_; + WBUFW(buf,54) = p->hair; + + //When the weapon is sent and your option is riding, the client crashes on login!? + WBUFW(buf,56) = p->option&(0x20|0x80000|0x100000|0x200000|0x400000|0x800000|0x1000000|0x2000000|0x4000000|0x8000000) ? 0 : p->weapon; + + WBUFW(buf,58) = p->base_level; + WBUFW(buf,60) = min(p->skill_point, INT16_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, UINT8_MAX); + WBUFB(buf,99) = min(p->agi, UINT8_MAX); + WBUFB(buf,100) = min(p->vit, UINT8_MAX); + WBUFB(buf,101) = min(p->int_, UINT8_MAX); + WBUFB(buf,102) = min(p->dex, UINT8_MAX); + WBUFB(buf,103) = min(p->luk, UINT8_MAX); + WBUFW(buf,104) = p->slot; + WBUFW(buf,106) = ( p->rename > 0 ) ? 0 : 1; + offset += 2; +#if (PACKETVER >= 20100720 && PACKETVER <= 20100727) || PACKETVER >= 20100803 + mapindex_getmapname_ext(mapindex_id2name(p->last_point.map), (char*)WBUFP(buf,108)); + offset += MAP_NAME_LENGTH_EXT; +#endif +#if PACKETVER >= 20100803 + WBUFL(buf,124) = TOL(p->delete_date); + offset += 4; +#endif +#if PACKETVER >= 20110111 + WBUFL(buf,128) = p->robe; + offset += 4; +#endif +#if PACKETVER != 20111116 //2011-11-16 wants 136, ask gravity. + #if PACKETVER >= 20110928 + WBUFL(buf,132) = 0; // change slot feature (0 = disabled, otherwise enabled) + offset += 4; + #endif + #if PACKETVER >= 20111025 + WBUFL(buf,136) = ( p->rename > 0 ) ? 1 : 0; // (0 = disabled, otherwise displays "Add-Ons" sidebar) + offset += 4; + #endif +#endif + + return 106+offset; +} + +//---------------------------------------- +// Function to send characters to a player +//---------------------------------------- +int mmo_char_send006b(int fd, struct char_session_data* sd) +{ + int j, offset = 0; +#if PACKETVER >= 20100413 + offset += 3; +#endif + + if (save_log) + ShowInfo("Loading Char Data ("CL_BOLD"%d"CL_RESET")\n",sd->account_id); + + j = 24 + offset; // offset + WFIFOHEAD(fd,j + MAX_CHARS*MAX_CHAR_BUF); + WFIFOW(fd,0) = 0x6b; +#if PACKETVER >= 20100413 + WFIFOB(fd,4) = MAX_CHARS; // Max slots. + WFIFOB(fd,5) = MAX_CHARS; // Available slots. + WFIFOB(fd,6) = MAX_CHARS; // Premium slots. +#endif + memset(WFIFOP(fd,4 + offset), 0, 20); // unknown bytes + j+=mmo_chars_fromsql(sd, WFIFOP(fd,j)); + WFIFOW(fd,2) = j; // packet len + WFIFOSET(fd,j); + + return 0; +} + +int char_married(int pl1, int pl2) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `partner_id` FROM `%s` WHERE `char_id` = '%d'", char_db, pl1) ) + Sql_ShowDebug(sql_handle); + else if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); + if( pl2 == atoi(data) ) + { + Sql_FreeResult(sql_handle); + return 1; + } + } + Sql_FreeResult(sql_handle); + return 0; +} + +int char_child(int parent_id, int child_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `child` FROM `%s` WHERE `char_id` = '%d'", char_db, parent_id) ) + Sql_ShowDebug(sql_handle); + else if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); + if( child_id == atoi(data) ) + { + Sql_FreeResult(sql_handle); + return 1; + } + } + Sql_FreeResult(sql_handle); + return 0; +} + +int char_family(int cid1, int cid2, int cid3) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`partner_id`,`child` FROM `%s` WHERE `char_id` IN ('%d','%d','%d')", char_db, cid1, cid2, cid3) ) + Sql_ShowDebug(sql_handle); + else while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + int charid; + int partnerid; + int childid; + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); charid = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); partnerid = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); childid = atoi(data); + + if( (cid1 == charid && ((cid2 == partnerid && cid3 == childid ) || (cid2 == childid && cid3 == partnerid))) || + (cid1 == partnerid && ((cid2 == charid && cid3 == childid ) || (cid2 == childid && cid3 == charid ))) || + (cid1 == childid && ((cid2 == charid && cid3 == partnerid) || (cid2 == partnerid && cid3 == charid ))) ) + { + Sql_FreeResult(sql_handle); + return childid; + } + } + Sql_FreeResult(sql_handle); + return 0; +} + +//---------------------------------------------------------------------- +// Force disconnection of an online player (with account value) by [Yor] +//---------------------------------------------------------------------- +void disconnect_player(int account_id) +{ + int i; + struct char_session_data* sd; + + // disconnect player if online on char-server + ARR_FIND( 0, fd_max, i, session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == account_id ); + if( i < fd_max ) + set_eof(i); +} + +static void char_auth_ok(int fd, struct char_session_data *sd) +{ + struct online_char_data* character; + + if( (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 == INVALID_TIMER) + character->waiting_disconnect = add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0); + WFIFOHEAD(fd,3); + 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. + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + return; + } + character->fd = fd; + } + + if (login_fd > 0) { + // request account data + 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_charselect(sd->account_id); + + // continues when account data is received... +} + +int send_accounts_tologin(int tid, unsigned int tick, int id, intptr_t data); +void mapif_server_reset(int id); + + +/// Resets all the data. +void loginif_reset(void) +{ + int id; + // TODO kick everyone out and reset everything or wait for connect and try to reaquire locks [FlavioJS] + for( id = 0; id < ARRAYLENGTH(server); ++id ) + mapif_server_reset(id); + flush_fifos(); + exit(EXIT_FAILURE); +} + + +/// Checks the conditions for the server to stop. +/// Releases the cookie when all characters are saved. +/// If all the conditions are met, it stops the core loop. +void loginif_check_shutdown(void) +{ + if( runflag != CHARSERVER_ST_SHUTDOWN ) + return; + runflag = CORE_ST_STOP; +} + + +/// Called when the connection to Login Server is disconnected. +void loginif_on_disconnect(void) +{ + ShowWarning("Connection to Login Server lost.\n\n"); +} + + +/// Called when all the connection steps are completed. +void loginif_on_ready(void) +{ + int i; + + loginif_check_shutdown(); + + //Send online accounts to login server. + send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0); + + // if no map-server already connected, display a message... + ARR_FIND( 0, ARRAYLENGTH(server), i, server[i].fd > 0 && server[i].map[0] ); + if( i == ARRAYLENGTH(server) ) + ShowStatus("Awaiting maps from map-server.\n"); +} + + +int parse_fromlogin(int fd) { + struct char_session_data* sd = NULL; + int i; + + // only process data from the login-server + if( fd != login_fd ) { + ShowDebug("parse_fromlogin: Disconnecting invalid session #%d (is not the login-server)\n", fd); + do_close(fd); + return 0; + } + + if( session[fd]->flag.eof ) { + do_close(fd); + login_fd = -1; + loginif_on_disconnect(); + return 0; + } else if ( session[fd]->flag.ping ) {/* we've reached stall time */ + if( DIFF_TICK(last_tick, session[fd]->rdata_tick) > (stall_time * 2) ) {/* we can't wait any longer */ + set_eof(fd); + return 0; + } else if( session[fd]->flag.ping != 2 ) { /* we haven't sent ping out yet */ + WFIFOHEAD(fd,2);// sends a ping packet to login server (will receive pong 0x2718) + WFIFOW(fd,0) = 0x2719; + WFIFOSET(fd,2); + + session[fd]->flag.ping = 2; + } + } + + 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"); + ShowError("Also, please make sure your login db has the correct communication username/passwords and the gender of the account is S.\n"); + ShowError("The communication passwords are set in map_athena.conf and char_athena.conf\n"); + set_eof(fd); + return 0; + } else { + ShowStatus("Connected to login-server (connection #%d).\n", fd); + loginif_on_ready(); + } + RFIFOSKIP(fd,3); + break; + + // acknowledgement of account authentication request + case 0x2713: + if (RFIFOREST(fd) < 25) + return 0; + { + int account_id = RFIFOL(fd,2); + uint32 login_id1 = RFIFOL(fd,6); + uint32 login_id2 = RFIFOL(fd,10); + uint8 sex = RFIFOB(fd,14); + uint8 result = RFIFOB(fd,15); + int request_id = RFIFOL(fd,16); + uint32 version = RFIFOL(fd,20); + uint8 clienttype = RFIFOB(fd,24); + RFIFOSKIP(fd,25); + + if( session_isActive(request_id) && (sd=(struct char_session_data*)session[request_id]->session_data) && + !sd->auth && sd->account_id == account_id && sd->login_id1 == login_id1 && sd->login_id2 == login_id2 && sd->sex == sex ) + { + int client_fd = request_id; + sd->version = version; + sd->clienttype = clienttype; + switch( result ) + { + case 0:// ok + char_auth_ok(client_fd, sd); + break; + case 1:// auth failed + WFIFOHEAD(client_fd,3); + WFIFOW(client_fd,0) = 0x6c; + WFIFOB(client_fd,2) = 0;// rejected from server + WFIFOSET(client_fd,3); + break; + } + } + } + break; + + case 0x2717: // account data + if (RFIFOREST(fd) < 62) + return 0; + + // find the authenticated session with this account id + ARR_FIND( 0, fd_max, i, session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->auth && sd->account_id == RFIFOL(fd,2) ); + if( i < fd_max ) + { + int server_id; + memcpy(sd->email, RFIFOP(fd,6), 40); + sd->expiration_time = (time_t)RFIFOL(fd,46); + sd->group_id = RFIFOB(fd,50); + safestrncpy(sd->birthdate, (const char*)RFIFOP(fd,51), sizeof(sd->birthdate)); + ARR_FIND( 0, ARRAYLENGTH(server), server_id, server[server_id].fd > 0 && server[server_id].map[0] ); + // continued from char_auth_ok... + if( server_id == ARRAYLENGTH(server) || //server not online, bugreport:2359 + ( max_connect_user && count_users() >= max_connect_user && sd->group_id != gm_allow_group ) ) { + // refuse connection (over populated) + WFIFOHEAD(i,3); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } else { + // send characters to player + mmo_char_send006b(i, sd); +#if PACKETVER >= 20110309 + // PIN code system, disabled + WFIFOHEAD(i, 12); + WFIFOW(i, 0) = 0x08B9; + WFIFOW(i, 2) = 0; + WFIFOW(i, 4) = 0; + WFIFOL(i, 6) = sd->account_id; + WFIFOW(i, 10) = 0; + WFIFOSET(i, 12); +#endif + } + } + RFIFOSKIP(fd,62); + break; + + // login-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + session[fd]->flag.ping = 0; + break; + + // changesex reply + case 0x2723: + if (RFIFOREST(fd) < 7) + return 0; + { + unsigned char buf[7]; + + int acc = RFIFOL(fd,2); + int sex = RFIFOB(fd,6); + RFIFOSKIP(fd,7); + + if( acc > 0 ) + {// TODO: Is this even possible? + int char_id[MAX_CHARS]; + int class_[MAX_CHARS]; + int guild_id[MAX_CHARS]; + int num; + char* data; + + struct auth_node* node = (struct auth_node*)idb_get(auth_db, acc); + if( node != NULL ) + node->sex = sex; + + // get characters + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`class`,`guild_id` FROM `%s` WHERE `account_id` = '%d'", char_db, acc) ) + Sql_ShowDebug(sql_handle); + for( i = 0; i < MAX_CHARS && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + Sql_GetData(sql_handle, 0, &data, NULL); char_id[i] = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); class_[i] = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); guild_id[i] = atoi(data); + } + num = i; + for( i = 0; i < num; ++i ) + { + if( class_[i] == JOB_BARD || class_[i] == JOB_DANCER || + class_[i] == JOB_CLOWN || class_[i] == JOB_GYPSY || + class_[i] == JOB_BABY_BARD || class_[i] == JOB_BABY_DANCER || + class_[i] == JOB_MINSTREL || class_[i] == JOB_WANDERER || + class_[i] == JOB_MINSTREL_T || class_[i] == JOB_WANDERER_T || + class_[i] == JOB_BABY_MINSTREL || class_[i] == JOB_BABY_WANDERER || + class_[i] == JOB_KAGEROU || class_[i] == JOB_OBORO ) + { + // job modification + if( class_[i] == JOB_BARD || class_[i] == JOB_DANCER ) + class_[i] = (sex ? JOB_BARD : JOB_DANCER); + else if( class_[i] == JOB_CLOWN || class_[i] == JOB_GYPSY ) + class_[i] = (sex ? JOB_CLOWN : JOB_GYPSY); + else if( class_[i] == JOB_BABY_BARD || class_[i] == JOB_BABY_DANCER ) + class_[i] = (sex ? JOB_BABY_BARD : JOB_BABY_DANCER); + else if( class_[i] == JOB_MINSTREL || class_[i] == JOB_WANDERER ) + class_[i] = (sex ? JOB_MINSTREL : JOB_WANDERER); + else if( class_[i] == JOB_MINSTREL_T || class_[i] == JOB_WANDERER_T ) + class_[i] = (sex ? JOB_MINSTREL_T : JOB_WANDERER_T); + else if( class_[i] == JOB_BABY_MINSTREL || class_[i] == JOB_BABY_WANDERER ) + class_[i] = (sex ? JOB_BABY_MINSTREL : JOB_BABY_WANDERER); + else if( class_[i] == JOB_KAGEROU || class_[i] == JOB_OBORO ) + class_[i] = (sex ? JOB_KAGEROU : JOB_OBORO); + } + + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `class`='%d', `weapon`='0', `shield`='0', `head_top`='0', `head_mid`='0', `head_bottom`='0' WHERE `char_id`='%d'", char_db, class_[i], char_id[i]) ) + Sql_ShowDebug(sql_handle); + + if( guild_id[i] )// If there is a guild, update the guild_member data [Skotlex] + inter_guild_sex_changed(guild_id[i], acc, char_id[i], sex); + } + Sql_FreeResult(sql_handle); + + // disconnect player if online on char-server + disconnect_player(acc); + } + + // notify all mapservers about this change + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + mapif_sendall(buf, 7); + } + break; + + // reply to an account_reg2 registry request + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + + { //Receive account_reg2 registry, forward to map servers. + unsigned char buf[13+ACCOUNT_REG2_NUM*sizeof(struct global_reg)]; + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex] + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); + } + break; + + // State change of account/ban notification (from login-server) + 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; + + // 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 == INVALID_TIMER) + 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 // still moving to the map-server + set_char_offline(-1, aid); + } + } + idb_remove(auth_db, aid);// reject auth attempts from map-server + } + break; + + // ip address update signal from login server + 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 ip, too. + + 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); + // notify login server about the change + 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", command); + set_eof(fd); + return 0; + } + } + + RFIFOFLUSH(fd); + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, intptr_t data); +int send_accounts_tologin(int tid, unsigned int tick, int id, intptr_t data); + +void do_init_loginif(void) +{ + // 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); + + // 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 +} + +void do_final_loginif(void) +{ + if( login_fd != -1 ) + { + do_close(login_fd); + login_fd = -1; + } +} + +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; +} + +void char_read_fame_list(void) +{ + int i; + char* data; + size_t len; + + // 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 + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND (`class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d') ORDER BY `fame` DESC LIMIT 0,%d", char_db, JOB_BLACKSMITH, JOB_WHITESMITH, JOB_BABY_BLACKSMITH, JOB_MECHANIC, JOB_MECHANIC_T, JOB_BABY_MECHANIC, fame_list_size_smith) ) + Sql_ShowDebug(sql_handle); + for( i = 0; i < fame_list_size_smith && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + // char_id + Sql_GetData(sql_handle, 0, &data, NULL); + smith_fame_list[i].id = atoi(data); + // fame + Sql_GetData(sql_handle, 1, &data, &len); + smith_fame_list[i].fame = atoi(data); + // name + Sql_GetData(sql_handle, 2, &data, &len); + memcpy(smith_fame_list[i].name, data, min(len, NAME_LENGTH)); + } + // Build Alchemist ranking list + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND (`class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d' OR `class`='%d') ORDER BY `fame` DESC LIMIT 0,%d", char_db, JOB_ALCHEMIST, JOB_CREATOR, JOB_BABY_ALCHEMIST, JOB_GENETIC, JOB_GENETIC_T, JOB_BABY_GENETIC, fame_list_size_chemist) ) + Sql_ShowDebug(sql_handle); + for( i = 0; i < fame_list_size_chemist && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + // char_id + Sql_GetData(sql_handle, 0, &data, NULL); + chemist_fame_list[i].id = atoi(data); + // fame + Sql_GetData(sql_handle, 1, &data, &len); + chemist_fame_list[i].fame = atoi(data); + // name + Sql_GetData(sql_handle, 2, &data, &len); + memcpy(chemist_fame_list[i].name, data, min(len, NAME_LENGTH)); + } + // Build Taekwon ranking list + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND (`class`='%d') ORDER BY `fame` DESC LIMIT 0,%d", char_db, JOB_TAEKWON, fame_list_size_taekwon) ) + Sql_ShowDebug(sql_handle); + for( i = 0; i < fame_list_size_taekwon && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + // char_id + Sql_GetData(sql_handle, 0, &data, NULL); + taekwon_fame_list[i].id = atoi(data); + // fame + Sql_GetData(sql_handle, 1, &data, &len); + taekwon_fame_list[i].fame = atoi(data); + // name + Sql_GetData(sql_handle, 2, &data, &len); + memcpy(taekwon_fame_list[i].name, data, min(len, NAME_LENGTH)); + } + Sql_FreeResult(sql_handle); +} + +// 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[8]; + 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) +{ + char* data; + size_t len; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `name` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) ) + Sql_ShowDebug(sql_handle); + else if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + Sql_GetData(sql_handle, 0, &data, &len); + safestrncpy(name, data, NAME_LENGTH); + return 1; + } + else + { + safestrncpy(name, unknown_char_name, NAME_LENGTH); + } + return 0; +} + +int search_mapserver(unsigned short map, uint32 ip, uint16 port); + + +/// Initializes a server structure. +void mapif_server_init(int id) +{ + memset(&server[id], 0, sizeof(server[id])); + server[id].fd = -1; +} + + +/// Destroys a server structure. +void mapif_server_destroy(int id) +{ + if( server[id].fd == -1 ) + { + do_close(server[id].fd); + server[id].fd = -1; + } +} + + +/// Resets all the data related to a server. +void mapif_server_reset(int id) +{ + int i,j; + unsigned char buf[16384]; + int fd = server[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)); + } + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `index`='%d'", ragsrvinfo_db, server[id].fd) ) + Sql_ShowDebug(sql_handle); + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server. + mapif_server_destroy(id); + mapif_server_init(id); +} + + +/// Called when the connection to a Map Server is disconnected. +void mapif_on_disconnect(int id) +{ + ShowStatus("Map-server #%d has disconnected.\n", id); + mapif_server_reset(id); +} + + +int parse_frommap(int fd) +{ + int i, j; + int id; + + ARR_FIND( 0, ARRAYLENGTH(server), id, server[id].fd == fd ); + if( id == ARRAYLENGTH(server) ) + {// not a map server + ShowDebug("parse_frommap: Disconnecting invalid session #%d (is not a map-server)\n", fd); + do_close(fd); + return 0; + } + if( session[fd]->flag.eof ) + { + do_close(fd); + server[id].fd = -1; + mapif_on_disconnect(id); + return 0; + } + + while(RFIFOREST(fd) >= 2) + { + switch(RFIFOW(fd,0)) + { + + 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); + + // 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); + } 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 < ARRAYLENGTH(server); x++) { + if (server[x].fd > 0 && x != id) { + WFIFOHEAD(fd,10 +4*ARRAYLENGTH(server[x].map)); + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = htonl(server[x].ip); + WFIFOW(fd,8) = htons(server[x].port); + j = 0; + for(i = 0; i < ARRAYLENGTH(server[x].map); 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; + aid = RFIFOL(fd,2); + cid = RFIFOL(fd,6); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT type, tick, val1, val2, val3, val4 from `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", + scdata_db, aid, cid) ) + { + Sql_ShowDebug(sql_handle); + break; + } + if( Sql_NumRows(sql_handle) > 0 ) + { + struct status_change_data scdata; + int count; + char* data; + + WFIFOHEAD(fd,14+50*sizeof(struct status_change_data)); + WFIFOW(fd,0) = 0x2b1d; + WFIFOL(fd,4) = aid; + WFIFOL(fd,8) = cid; + for( count = 0; count < 50 && SQL_SUCCESS == Sql_NextRow(sql_handle); ++count ) + { + Sql_GetData(sql_handle, 0, &data, NULL); scdata.type = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); scdata.tick = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); scdata.val1 = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); scdata.val2 = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); scdata.val3 = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); scdata.val4 = atoi(data); + memcpy(WFIFOP(fd, 14+count*sizeof(struct status_change_data)), &scdata, sizeof(struct status_change_data)); + } + if (count >= 50) + ShowWarning("Too many status changes for %d:%d, some of them were not loaded.\n", aid, cid); + if (count > 0) + { + WFIFOW(fd,2) = 14 + count*sizeof(struct status_change_data); + WFIFOW(fd,12) = count; + WFIFOSET(fd,WFIFOW(fd,2)); + + //Clear the data once loaded. + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, aid, cid) ) + Sql_ShowDebug(sql_handle); + } + } + Sql_FreeResult(sql_handle); +#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. + int aid, cid; + struct online_char_data* character; + + 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++) { + aid = RFIFOL(fd,6+i*8); + cid = RFIFOL(fd,6+i*8+4); + character = idb_ensure(online_char_db, aid, create_online_char_data); + if( character->server > -1 && character->server != id ) + { + ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, id, aid, cid); + mapif_disconnectplayer(server[character->server].fd, character->account_id, character->char_id, 2); + } + character->server = id; + character->char_id = cid; + } + //If any chars remain in -2, they will be cleaned in the cleanup timer. + RFIFOSKIP(fd,RFIFOW(fd,2)); + } + break; + + case 0x2b01: // Receive character data from map-server for saving + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int aid = RFIFOL(fd,4), cid = RFIFOL(fd,8), size = RFIFOW(fd,2); + struct online_char_data* character; + + if (size - 13 != sizeof(struct mmo_charstatus)) + { + ShowError("parse_from_map (save-char): Size mismatch! %d != %d\n", size-13, sizeof(struct mmo_charstatus)); + RFIFOSKIP(fd,size); + break; + } + //Check account only if this ain't final save. Final-save goes through because of the char-map reconnect + if (RFIFOB(fd,12) || ( + (character = (struct online_char_data*)idb_get(online_char_db, aid)) != NULL && + character->char_id == cid)) + { + struct mmo_charstatus char_dat; + memcpy(&char_dat, RFIFOP(fd,13), sizeof(struct mmo_charstatus)); + mmo_char_tosql(cid, &char_dat); + } else { //This may be valid on char-server reconnection, when re-sending characters that already logged off. + ShowError("parse_from_map (save-char): Received data for non-existant/offline character (%d:%d).\n", aid, cid); + set_char_online(id, cid, aid); + } + + if (RFIFOB(fd,12)) + { //Flag, set character offline after saving. [Skotlex] + set_char_offline(cid, aid); + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x2b21; //Save ack only needed on final save. + WFIFOL(fd,2) = aid; + WFIFOL(fd,6) = cid; + WFIFOSET(fd,10); + } + RFIFOSKIP(fd,size); + } + 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); + + if( runflag != CHARSERVER_ST_RUNNING ) + { + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 0;// not ok + WFIFOSET(fd,7); + } + else + { + // 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) + //node->gmlevel = 0; + idb_put(auth_db, account_id, node); + + //Set char to "@ char select" in online db [Kevin] + set_char_charselect(account_id); + + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 1;// ok + WFIFOSET(fd,7); + } + } + break; + + case 0x2b05: // request "change map server" + if (RFIFOREST(fd) < 39) + return 0; + { + int map_id, map_fd = -1; + struct online_char_data* data; + struct mmo_charstatus* char_data; + struct mmo_charstatus char_dat; + + map_id = search_mapserver(RFIFOW(fd,18), ntohl(RFIFOL(fd,24)), ntohs(RFIFOW(fd,28))); //Locate mapserver by ip and port. + if (map_id >= 0) + map_fd = server[map_id].fd; + //Char should just had been saved before this packet, so this should be safe. [Skotlex] + char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14)); + if (char_data == NULL) { //Really shouldn't happen. + mmo_char_fromsql(RFIFOL(fd,14), &char_dat, true); + char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14)); + } + + if( runflag == CHARSERVER_ST_RUNNING && + session_isActive(map_fd) && + char_data ) + { //Send the map server the auth of this player. + struct auth_node* node; + + //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); + + // create temporary auth entry + CREATE(node, struct auth_node, 1); + node->account_id = RFIFOL(fd,2); + node->char_id = RFIFOL(fd,14); + node->login_id1 = RFIFOL(fd,6); + node->login_id2 = RFIFOL(fd,10); + node->sex = RFIFOB(fd,30); + node->expiration_time = 0; // FIXME (this thing isn't really supported we could as well purge it instead of fixing) + node->ip = ntohl(RFIFOL(fd,31)); + node->group_id = RFIFOL(fd,35); + node->changing_mapservers = 1; + idb_put(auth_db, RFIFOL(fd,2), node); + + 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,39); + } + break; + + case 0x2b07: // Remove RFIFOL(fd,6) (friend_id) from RFIFOL(fd,2) (char_id) friend list [Ind] + if (RFIFOREST(fd) < 10) + return 0; + { + int char_id, friend_id; + char_id = RFIFOL(fd,2); + friend_id = RFIFOL(fd,6); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d' AND `friend_id`='%d' LIMIT 1", + friend_db, char_id, friend_id) ) { + Sql_ShowDebug(sql_handle); + break; + } + RFIFOSKIP(fd,10); + } + 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 esc_name[NAME_LENGTH*2+1]; + + 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); + + Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name` FROM `%s` WHERE `name` = '%s'", char_db, esc_name) ) + Sql_ShowDebug(sql_handle); + else + if( Sql_NumRows(sql_handle) == 0 ) + { + result = 1; // 1-player not found + } + else + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + Sql_ShowDebug(sql_handle); + //FIXME: set proper result value? + else + { + char name[NAME_LENGTH]; + int account_id; + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name)); + + if( login_fd <= 0 ) + result = 3; // 3-login-server offline + //FIXME: need to move this check to login server [ultramage] +// 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; + } + } + + Sql_FreeResult(sql_handle); + + // send answer if a player ask, not if the server ask + if( acc != -1 && type != 5) { // Don't send answer for changesex + 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; + + // Divorce chars + case 0x2b11: + if( RFIFOREST(fd) < 10 ) + return 0; + + divorce_char_sql(RFIFOL(fd,2), RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + case 0x2b16: // Receive rates [Wizputer] + if( RFIFOREST(fd) < 14 ) + return 0; + { + char esc_server_name[sizeof(server_name)*2+1]; + + Sql_EscapeString(sql_handle, esc_server_name, server_name); + + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` SET `index`='%d',`name`='%s',`exp`='%d',`jexp`='%d',`drop`='%d'", + ragsrvinfo_db, fd, esc_server_name, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)) ) + Sql_ShowDebug(sql_handle); + RFIFOSKIP(fd,14); + } + 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; + + aid = RFIFOL(fd, 4); + cid = RFIFOL(fd, 8); + count = RFIFOW(fd, 12); + + if( count > 0 ) + { + struct status_change_data data; + StringBuf buf; + int i; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`account_id`, `char_id`, `type`, `tick`, `val1`, `val2`, `val3`, `val4`) VALUES ", scdata_db); + for( i = 0; i < count; ++i ) + { + memcpy (&data, RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data)); + if( i > 0 ) + StringBuf_AppendStr(&buf, ", "); + StringBuf_Printf(&buf, "('%d','%d','%hu','%d','%d','%d','%d','%d')", aid, cid, + data.type, data.tick, data.val1, data.val2, data.val3, data.val4); + } + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + StringBuf_Destroy(&buf); + } +#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 0x2b26: // auth request from map-server + if (RFIFOREST(fd) < 19) + return 0; + + { + int account_id; + int char_id; + int login_id1; + char sex; + uint32 ip; + struct auth_node* node; + struct mmo_charstatus* cd; + struct mmo_charstatus char_dat; + + account_id = RFIFOL(fd,2); + char_id = RFIFOL(fd,6); + login_id1 = RFIFOL(fd,10); + sex = RFIFOB(fd,14); + ip = ntohl(RFIFOL(fd,15)); + RFIFOSKIP(fd,19); + + node = (struct auth_node*)idb_get(auth_db, account_id); + cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id); + if( cd == NULL ) + { //Really shouldn't happen. + mmo_char_fromsql(char_id, &char_dat, true); + cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id); + } + if( runflag == CHARSERVER_ST_RUNNING && + cd != NULL && + node != NULL && + node->account_id == account_id && + node->char_id == char_id && + node->login_id1 == login_id1 && + node->sex == sex /*&& + node->ip == ip*/ ) + {// auth ok + cd->sex = sex; + + WFIFOHEAD(fd,25 + sizeof(struct mmo_charstatus)); + WFIFOW(fd,0) = 0x2afd; + WFIFOW(fd,2) = 25 + sizeof(struct mmo_charstatus); + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = node->login_id1; + WFIFOL(fd,12) = node->login_id2; + WFIFOL(fd,16) = (uint32)node->expiration_time; // FIXME: will wrap to negative after "19-Jan-2038, 03:14:07 AM GMT" + WFIFOL(fd,20) = node->group_id; + WFIFOB(fd,24) = node->changing_mapservers; + memcpy(WFIFOP(fd,25), cd, sizeof(struct mmo_charstatus)); + WFIFOSET(fd, WFIFOW(fd,2)); + + // only use the auth once and mark user online + idb_remove(auth_db, account_id); + set_char_online(id, char_id, account_id); + } + else + {// auth failed + WFIFOHEAD(fd,19); + WFIFOW(fd,0) = 0x2b27; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + WFIFOL(fd,10) = login_id1; + WFIFOB(fd,14) = sex; + WFIFOL(fd,15) = htonl(ip); + WFIFOSET(fd,19); + } + } + 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; + + case 0x3008: + if( RFIFOREST(fd) < RFIFOW(fd,4) ) + return 0;/* packet wasn't fully received yet (still fragmented) */ + else { + int sfd;/* stat server fd */ + RFIFOSKIP(fd, 2);/* we skip first 2 bytes which are the 0x3008, so we end up with a buffer equal to the one we send */ + + if( (sfd = make_connection(host2ip("stats.rathena.org"),(uint16)25421,true) ) == -1 ) { + RFIFOSKIP(fd, RFIFOW(fd,2) );/* skip this packet */ + break;/* connection not possible, we drop the report */ + } + + session[sfd]->flag.server = 1;/* to ensure we won't drop our own packet */ + + WFIFOHEAD(sfd, RFIFOW(fd,2) ); + + memcpy((char*)WFIFOP(sfd,0), (char*)RFIFOP(fd, 0), RFIFOW(fd,2)); + + WFIFOSET(sfd, RFIFOW(fd,2) ); + + flush_fifo(sfd); + + do_close(sfd); + + RFIFOSKIP(fd, RFIFOW(fd,2) );/* skip this packet */ + } + 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; +} + +void do_init_mapif(void) +{ + int i; + for( i = 0; i < ARRAYLENGTH(server); ++i ) + mapif_server_init(i); +} + +void do_final_mapif(void) +{ + int i; + for( i = 0; i < ARRAYLENGTH(server); ++i ) + mapif_server_destroy(i); +} + +// 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 < ARRAYLENGTH(server); 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; +} + +// Initialization process (currently only initialization 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].map_ip; + } else { + ShowInfo("Subnet check [%u.%u.%u.%u]: "CL_CYAN"WAN"CL_RESET"\n", CONVIP(ip)); + return 0; + } +} + + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 3 (0x719): A database error occurred. +/// 4 (0x71a): To delete a character you must withdraw from the guild. +/// 5 (0x71b): To delete a character you must withdraw from the party. +/// Any (0x718): An unknown error has occurred. +void char_delete2_ack(int fd, int char_id, uint32 result, time_t delete_date) +{// HC: <0828>.W <char id>.L <Msg:0-5>.L <deleteDate>.L + WFIFOHEAD(fd,14); + WFIFOW(fd,0) = 0x828; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOL(fd,10) = TOL(delete_date); + WFIFOSET(fd,14); +} + + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 2 (0x71c): Due to system settings can not be deleted. +/// 3 (0x719): A database error occurred. +/// 4 (0x71d): Deleting not yet possible time. +/// 5 (0x71e): Date of birth do not match. +/// Any (0x718): An unknown error has occurred. +void char_delete2_accept_ack(int fd, int char_id, uint32 result) +{// HC: <082a>.W <char id>.L <Msg:0-5>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82a; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +/// @param result +/// 1 (0x718): none/success, (if char id not in deletion process): An unknown error has occurred. +/// 2 (0x719): A database error occurred. +/// Any (0x718): An unknown error has occurred. +void char_delete2_cancel_ack(int fd, int char_id, uint32 result) +{// HC: <082c>.W <char id>.L <Msg:1-2>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82c; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +static void char_delete2_req(int fd, struct char_session_data* sd) +{// CH: <0827>.W <char id>.L + int char_id, i; + char* data; + time_t delete_date; + + char_id = RFIFOL(fd,2); + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `delete_date` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) || SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + Sql_GetData(sql_handle, 0, &data, NULL); delete_date = strtoul(data, NULL, 10); + + if( delete_date ) {// character already queued for deletion + char_delete2_ack(fd, char_id, 0, 0); + return; + } + +/* + // Aegis imposes these checks probably to avoid dead member + // entries in guilds/parties, otherwise they are not required. + // TODO: Figure out how these are enforced during waiting. + if( guild_id ) + {// character in guild + char_delete2_ack(fd, char_id, 4, 0); + return; + } + + if( party_id ) + {// character in party + char_delete2_ack(fd, char_id, 5, 0); + return; + } +*/ + + // success + delete_date = time(NULL)+char_del_delay; + + if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='%lu' WHERE `char_id`='%d'", char_db, (unsigned long)delete_date, char_id) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + char_delete2_ack(fd, char_id, 1, delete_date); +} + + +static void char_delete2_accept(int fd, struct char_session_data* sd) +{// CH: <0829>.W <char id>.L <birth date:YYMMDD>.6B + char birthdate[8+1]; + int char_id, i, k; + unsigned int base_level; + char* data; + time_t delete_date; + + char_id = RFIFOL(fd,2); + + ShowInfo(CL_RED"Request Char Deletion: "CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, char_id); + + // construct "YY-MM-DD" + birthdate[0] = RFIFOB(fd,6); + birthdate[1] = RFIFOB(fd,7); + birthdate[2] = '-'; + birthdate[3] = RFIFOB(fd,8); + birthdate[4] = RFIFOB(fd,9); + birthdate[5] = '-'; + birthdate[6] = RFIFOB(fd,10); + birthdate[7] = RFIFOB(fd,11); + birthdate[8] = 0; + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `base_level`,`delete_date` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) || SQL_SUCCESS != Sql_NextRow(sql_handle) ) + {// data error + Sql_ShowDebug(sql_handle); + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + Sql_GetData(sql_handle, 0, &data, NULL); base_level = (unsigned int)strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 1, &data, NULL); delete_date = strtoul(data, NULL, 10); + + if( !delete_date || delete_date>time(NULL) ) + {// not queued or delay not yet passed + char_delete2_accept_ack(fd, char_id, 4); + return; + } + + if( strcmp(sd->birthdate+2, birthdate) ) // +2 to cut off the century + {// birth date is wrong + char_delete2_accept_ack(fd, char_id, 5); + return; + } + + if( ( char_del_level > 0 && base_level >= (unsigned int)char_del_level ) || ( char_del_level < 0 && base_level <= (unsigned int)(-char_del_level) ) ) + {// character level config restriction + char_delete2_accept_ack(fd, char_id, 2); + return; + } + + // success + if( delete_char_sql(char_id) < 0 ) + { + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + // refresh character list cache + for(k = i; k < MAX_CHARS-1; k++) + { + sd->found_char[k] = sd->found_char[k+1]; + } + sd->found_char[MAX_CHARS-1] = -1; + + char_delete2_accept_ack(fd, char_id, 1); +} + + +static void char_delete2_cancel(int fd, struct char_session_data* sd) +{// CH: <082b>.W <char id>.L + int char_id, i; + + char_id = RFIFOL(fd,2); + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_cancel_ack(fd, char_id, 2); + return; + } + + // there is no need to check, whether or not the character was + // queued for deletion, as the client prints an error message by + // itself, if it was not the case (@see char_delete2_cancel_ack) + if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='0' WHERE `char_id`='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_cancel_ack(fd, char_id, 2); + return; + } + + char_delete2_cancel_ack(fd, char_id, 1); +} + + +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 ) + { // already authed client + struct online_char_data* data = (struct online_char_data*)idb_get(online_char_db, sd->account_id); + if( data != NULL && data->fd == fd) + data->fd = -1; + if( data == NULL || data->server == -1) //If it is not in any server, send it offline. [Skotlex] + set_char_offline(-1,sd->account_id); + } + 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 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; + } + + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = (struct char_session_data*)session[fd]->session_data; + 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); + + if( runflag != CHARSERVER_ST_RUNNING ) + { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x6c; + WFIFOB(fd,2) = 0;// rejected from server + WFIFOSET(fd,3); + break; + } + + // 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,23); + 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); + WFIFOL(login_fd,19) = fd; + WFIFOSET(login_fd,23); + } 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 char_dat; + struct mmo_charstatus *cd; + char* data; + int char_id; + uint32 subnet_map_ip; + struct auth_node* node; + + int slot = RFIFOB(fd,2); + RFIFOSKIP(fd,3); + + if ( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d'", char_db, sd->account_id, slot) + || SQL_SUCCESS != Sql_NextRow(sql_handle) + || SQL_SUCCESS != Sql_GetData(sql_handle, 0, &data, NULL) ) + { //Not found?? May be forged packet. + Sql_ShowDebug(sql_handle); + Sql_FreeResult(sql_handle); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x6c; + WFIFOB(fd,2) = 0; // rejected from server + WFIFOSET(fd,3); + break; + } + + char_id = atoi(data); + Sql_FreeResult(sql_handle); + mmo_char_fromsql(char_id, &char_dat, true); + + //Have to switch over to the DB instance otherwise data won't propagate [Kevin] + cd = (struct mmo_charstatus *)idb_get(char_db_, char_id); + cd->sex = sd->sex; + + if (log_char) { + char esc_name[NAME_LENGTH*2+1]; + + Sql_EscapeStringLen(sql_handle, esc_name, char_dat.name, strnlen(char_dat.name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", + charlog_db, sd->account_id, slot, esc_name) ) + Sql_ShowDebug(sql_handle); + } + ShowInfo("Selected char: (Account %d: %d - %s)\n", sd->account_id, slot, char_dat.name); + + // searching map server + i = search_mapserver(cd->last_point.map, -1, -1); + + // if map is not found, we check major cities + if (i < 0 || !cd->last_point.map) { + unsigned short j; + //First check that there's actually a map server online. + ARR_FIND( 0, ARRAYLENGTH(server), j, server[j].fd >= 0 && server[j].map[0] ); + if (j == ARRAYLENGTH(server)) { + 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)); + subnet_map_ip = lan_subnetcheck(ipl); // Advanced subnet check [LuzZza] + 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); + + // 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->group_id = sd->group_id; + node->ip = ipl; + idb_put(auth_db, sd->account_id, node); + + set_char_online(-2,node->char_id,sd->account_id); + + } + break; + + // create new char +#if PACKETVER >= 20120307 + // S 0970 <name>.24B <slot>.B <hair color>.W <hair style>.W + case 0x970: + FIFOSD_CHECK(31); +#else + // 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); +#endif + + if( !char_new ) //turn character creation on/off [Kevin] + i = -2; + else +#if PACKETVER >= 20120307 + i = make_new_char_sql(sd, (char*)RFIFOP(fd,2),RFIFOB(fd,26),RFIFOW(fd,27),RFIFOW(fd,29)); +#else + i = make_new_char_sql(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)); +#endif + + //'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) = 0xFF; break; + case -3: WFIFOB(fd,2) = 0x01; break; + } + WFIFOSET(fd,3); + } + else + { + int len; + // retrieve data + struct mmo_charstatus char_dat; + mmo_char_fromsql(i, &char_dat, false); //Only the short data is needed. + + // send to player + WFIFOHEAD(fd,2+MAX_CHAR_BUF); + WFIFOW(fd,0) = 0x6d; + len = 2 + mmo_char_tobuf(WFIFOP(fd,2), &char_dat); + 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; // the char_id of the new char + } +#if PACKETVER >= 20120307 + RFIFOSKIP(fd,31); +#else + RFIFOSKIP(fd,37); +#endif + 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); + + 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,( cmd == 0x68) ? 46 : 56); + + // Check if e-mail is correct + if(strcmpi(email, sd->email) && //email does not matches and + ( + strcmp("a@a.com", sd->email) || //it is not default email, or + (strcmp("a@a.com", email) && strcmp("", email)) //email sent does not matches default + )) { //Fail + 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] == 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; + } + + // 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; + + /* Delete character */ + if(delete_char_sql(cid)<0){ + //can't delete the char + //either SQL error or can't delete by some CONFIG conditions + //del fail + WFIFOHEAD(fd,3); + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + break; + } + /* 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); + { + int i, aid = RFIFOL(fd,2), cid =RFIFOL(fd,6); + char name[NAME_LENGTH]; + char esc_name[NAME_LENGTH*2+1]; + safestrncpy(name, (char *)RFIFOP(fd,10), NAME_LENGTH); + RFIFOSKIP(fd,34); + + if( aid != sd->account_id ) + break; + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == cid ); + if( i == MAX_CHARS ) + break; + + normalize_name(name,TRIM_CHARS); + Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); + if( !check_char_name(name,esc_name) ) + { + i = 1; + safestrncpy(sd->new_name, name, NAME_LENGTH); + } + else + i = 0; + + WFIFOHEAD(fd, 4); + WFIFOW(fd,0) = 0x28e; + WFIFOW(fd,2) = i; + WFIFOSET(fd,4); + } + break; + //Confirm change name. + // 0x28f <char_id>.L + case 0x28f: + // 0: Sucessfull + // 1: This character's name has already been changed. You cannot change a character's name more than once. + // 2: User information is not correct. + // 3: You have failed to change this character's name. + // 4: Another user is using this character name, so please select another one. + FIFOSD_CHECK(6); + { + int i; + int cid = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == cid ); + if( i == MAX_CHARS ) + break; + i = rename_char_sql(sd, cid); + + WFIFOHEAD(fd, 4); + WFIFOW(fd,0) = 0x290; + WFIFOW(fd,2) = i; + WFIFOSET(fd,4); + } + break; + + // captcha code request (not implemented) + // R 07e5 <?>.w <aid>.l + case 0x7e5: + WFIFOHEAD(fd,5); + WFIFOW(fd,0) = 0x7e9; + WFIFOW(fd,2) = 5; + WFIFOB(fd,4) = 1; + WFIFOSET(fd,5); + RFIFOSKIP(fd,8); + break; + + // captcha code check (not implemented) + // R 07e7 <len>.w <aid>.l <code>.b10 <?>.b14 + case 0x7e7: + WFIFOHEAD(fd,5); + WFIFOW(fd,0) = 0x7e9; + WFIFOW(fd,2) = 5; + WFIFOB(fd,4) = 1; + WFIFOSET(fd,5); + RFIFOSKIP(fd,32); + break; + + // deletion timer request + case 0x827: + FIFOSD_CHECK(6); + char_delete2_req(fd, sd); + RFIFOSKIP(fd,6); + break; + + // deletion accept request + case 0x829: + FIFOSD_CHECK(12); + char_delete2_accept(fd, sd); + RFIFOSKIP(fd,12); + break; + + // deletion cancel request + case 0x82b: + FIFOSD_CHECK(6); + char_delete2_cancel(fd, sd); + RFIFOSKIP(fd,6); + 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, ARRAYLENGTH(server), i, server[i].fd <= 0 ); + if( runflag != CHARSERVER_ST_RUNNING || + i == ARRAYLENGTH(server) || + strcmp(l_user, userid) != 0 || + strcmp(l_pass, passwd) != 0 ) + { + 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); + } + + RFIFOSKIP(fd,60); + } + return 0; // avoid processing of followup packets here + + // 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(const char* command) +{ + ShowNotice("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("To shutdown the server:\n"); + ShowInfo(" 'shutdown|exit|quit|end'\n"); + ShowInfo("To know if server is alive:\n"); + ShowInfo(" 'alive|status'\n"); + } + + return 0; +} + +int mapif_sendall(unsigned char *buf, unsigned int len) +{ + int i, c; + + c = 0; + for(i = 0; i < ARRAYLENGTH(server); i++) { + int fd; + if ((fd = server[i].fd) > 0) { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) +{ + int i, c; + + c = 0; + for(i = 0; i < ARRAYLENGTH(server); 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; +} + +int mapif_send(int fd, unsigned char *buf, unsigned int len) +{ + if (fd >= 0) { + int i; + ARR_FIND( 0, ARRAYLENGTH(server), i, fd == server[i].fd ); + if( i < ARRAYLENGTH(server) ) + { + 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, intptr_t 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); + + return 0; +} + +/** + * Load this character's account id into the 'online accounts' packet + * @see DBApply + */ +static int send_accounts_tologin_sub(DBKey key, DBData *data, va_list ap) +{ + struct online_char_data* character = db_data2ptr(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, intptr_t 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, intptr_t 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, false); + 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); + WFIFOW(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; +} + +//------------------------------------------------ +//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, intptr_t 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 = INVALID_TIMER; + set_char_offline(character->char_id, character->account_id); + } + return 0; +} + +/** + * @see DBApply + */ +static int online_data_cleanup_sub(DBKey key, DBData *data, va_list ap) +{ + struct online_char_data *character= db_data2ptr(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, intptr_t 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; + } + + 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++; + } + } + + if( subnet_count > 1 ) /* only useful if there is more than 1 */ + ShowStatus("Read information about %d subnetworks.\n", subnet_count); + + fclose(fp); + return 0; +} + +void sql_config_read(const char* cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE* fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("File not found: %s\n", cfgName); + return; + } + + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if(!strcmpi(w1,"char_db")) + safestrncpy(char_db, w2, sizeof(char_db)); + else if(!strcmpi(w1,"scdata_db")) + safestrncpy(scdata_db, w2, sizeof(scdata_db)); + else if(!strcmpi(w1,"cart_db")) + safestrncpy(cart_db, w2, sizeof(cart_db)); + else if(!strcmpi(w1,"inventory_db")) + safestrncpy(inventory_db, w2, sizeof(inventory_db)); + else if(!strcmpi(w1,"charlog_db")) + safestrncpy(charlog_db, w2, sizeof(charlog_db)); + else if(!strcmpi(w1,"storage_db")) + safestrncpy(storage_db, w2, sizeof(storage_db)); + else if(!strcmpi(w1,"reg_db")) + safestrncpy(reg_db, w2, sizeof(reg_db)); + else if(!strcmpi(w1,"skill_db")) + safestrncpy(skill_db, w2, sizeof(skill_db)); + else if(!strcmpi(w1,"interlog_db")) + safestrncpy(interlog_db, w2, sizeof(interlog_db)); + else if(!strcmpi(w1,"memo_db")) + safestrncpy(memo_db, w2, sizeof(memo_db)); + else if(!strcmpi(w1,"guild_db")) + safestrncpy(guild_db, w2, sizeof(guild_db)); + else if(!strcmpi(w1,"guild_alliance_db")) + safestrncpy(guild_alliance_db, w2, sizeof(guild_alliance_db)); + else if(!strcmpi(w1,"guild_castle_db")) + safestrncpy(guild_castle_db, w2, sizeof(guild_castle_db)); + else if(!strcmpi(w1,"guild_expulsion_db")) + safestrncpy(guild_expulsion_db, w2, sizeof(guild_expulsion_db)); + else if(!strcmpi(w1,"guild_member_db")) + safestrncpy(guild_member_db, w2, sizeof(guild_member_db)); + else if(!strcmpi(w1,"guild_skill_db")) + safestrncpy(guild_skill_db, w2, sizeof(guild_skill_db)); + else if(!strcmpi(w1,"guild_position_db")) + safestrncpy(guild_position_db, w2, sizeof(guild_position_db)); + else if(!strcmpi(w1,"guild_storage_db")) + safestrncpy(guild_storage_db, w2, sizeof(guild_storage_db)); + else if(!strcmpi(w1,"party_db")) + safestrncpy(party_db, w2, sizeof(party_db)); + else if(!strcmpi(w1,"pet_db")) + safestrncpy(pet_db, w2, sizeof(pet_db)); + else if(!strcmpi(w1,"mail_db")) + safestrncpy(mail_db, w2, sizeof(mail_db)); + else if(!strcmpi(w1,"auction_db")) + safestrncpy(auction_db, w2, sizeof(auction_db)); + else if(!strcmpi(w1,"friend_db")) + safestrncpy(friend_db, w2, sizeof(friend_db)); + else if(!strcmpi(w1,"hotkey_db")) + safestrncpy(hotkey_db, w2, sizeof(hotkey_db)); + else if(!strcmpi(w1,"quest_db")) + safestrncpy(quest_db,w2,sizeof(quest_db)); + else if(!strcmpi(w1,"homunculus_db")) + safestrncpy(homunculus_db,w2,sizeof(homunculus_db)); + else if(!strcmpi(w1,"skill_homunculus_db")) + safestrncpy(skill_homunculus_db,w2,sizeof(skill_homunculus_db)); + else if(!strcmpi(w1,"mercenary_db")) + safestrncpy(mercenary_db,w2,sizeof(mercenary_db)); + else if(!strcmpi(w1,"mercenary_owner_db")) + safestrncpy(mercenary_owner_db,w2,sizeof(mercenary_owner_db)); + //support the import command, just like any other config + else if(!strcmpi(w1,"import")) + sql_config_read(w2); + } + fclose(fp); + ShowInfo("Done reading %s.\n", cfgName); +} + +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; + } + + 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) { + safestrncpy(timestamp_format, w2, sizeof(timestamp_format)); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = atoi(w2); + if( msg_silent ) /* only bother if its actually enabled */ + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + } else if(strcmpi(w1,"stdout_with_ansisequence")==0){ + stdout_with_ansisequence = config_switch(w2); + } else if (strcmpi(w1, "userid") == 0) { + safestrncpy(userid, w2, sizeof(userid)); + } else if (strcmpi(w1, "passwd") == 0) { + safestrncpy(passwd, w2, sizeof(passwd)); + } else if (strcmpi(w1, "server_name") == 0) { + safestrncpy(server_name, w2, sizeof(server_name)); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + safestrncpy(wisp_server_name, w2, sizeof(wisp_server_name)); + } + } else if (strcmpi(w1, "login_ip") == 0) { + char ip_str[16]; + login_ip = host2ip(w2); + if (login_ip) { + safestrncpy(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){ + safestrncpy(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) { + safestrncpy(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, "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_group") == 0) { + gm_allow_group = atoi(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.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) { + safestrncpy(unknown_char_name, w2, sizeof(unknown_char_name)); + unknown_char_name[NAME_LENGTH-1] = '\0'; + } 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) { + safestrncpy(char_name_letters, w2, sizeof(char_name_letters)); + } else if (strcmpi(w1, "chars_per_account") == 0) { //maxchars per account [Sirius] + char_per_account = atoi(w2); + if( char_per_account == 0 || char_per_account > MAX_CHARS ) { + if( char_per_account > MAX_CHARS ) + ShowWarning("Max chars per account '%d' exceeded limit. Defaulting to '%d'.\n", char_per_account, MAX_CHARS); + char_per_account = MAX_CHARS; + } + } else if (strcmpi(w1, "char_del_level") == 0) { //disable/enable char deletion by its level condition [Lupus] + char_del_level = atoi(w2); + } else if (strcmpi(w1, "char_del_delay") == 0) { + char_del_delay = atoi(w2); + } else if(strcmpi(w1,"db_path")==0) { + safestrncpy(db_path, w2, sizeof(db_path)); + } 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); + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", cfgName); + return 0; +} + +void do_final(void) +{ + ShowStatus("Terminating...\n"); + + set_all_offline(-1); + set_all_offline_sql(); + + inter_final(); + + flush_fifos(); + + do_final_mapif(); + do_final_loginif(); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s`", ragsrvinfo_db) ) + Sql_ShowDebug(sql_handle); + + char_db_->destroy(char_db_, NULL); + online_char_db->destroy(online_char_db, NULL); + auth_db->destroy(auth_db, NULL); + + if( char_fd != -1 ) + { + do_close(char_fd); + char_fd = -1; + } + + Sql_Free(sql_handle); + mapindex_final(); + + ShowStatus("Finished.\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; +} + + +/// Called when a terminate signal is received. +void do_shutdown(void) +{ + if( runflag != CHARSERVER_ST_SHUTDOWN ) + { + int id; + runflag = CHARSERVER_ST_SHUTDOWN; + ShowStatus("Shutting down...\n"); + // TODO proper shutdown procedure; wait for acks?, kick all characters, ... [FlavoJS] + for( id = 0; id < ARRAYLENGTH(server); ++id ) + mapif_server_reset(id); + loginif_check_shutdown(); + flush_fifos(); + runflag = CORE_ST_STOP; + } +} + + +int do_init(int argc, char **argv) +{ + //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); + sql_config_read(SQL_CONF_NAME); + + if (strcmp(userid, "s1")==0 && strcmp(passwd, "p1")==0) { + ShowWarning("Using the default user/password s1/p1 is NOT RECOMMENDED.\n"); + ShowNotice("Please edit your 'login' table 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"); + } + + inter_init_sql((argc > 2) ? argv[2] : inter_cfgName); // inter server configuration + + auth_db = idb_alloc(DB_OPT_RELEASE_DATA); + online_char_db = idb_alloc(DB_OPT_RELEASE_DATA); + mmo_char_sql_init(); + char_read_fame_list(); //Read fame lists. + + 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) { + safestrncpy(login_ip_str, ip_str, sizeof(login_ip_str)); + login_ip = str2ip(login_ip_str); + } + if (!char_ip) { + safestrncpy(char_ip_str, ip_str, sizeof(char_ip_str)); + char_ip = str2ip(char_ip_str); + } + } + + do_init_loginif(); + do_init_mapif(); + + // 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); + + // Timer to clear (online_char_db) + add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect"); + + // Online Data timers (checking if char still connected) + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 1000, online_data_cleanup, 0, 0, 600 * 1000); + + if( console ) + { + //##TODO invoke a CONSOLE_START plugin event + } + + //Cleaning the tables for NULL entrys @ startup [Sirius] + //Chardb clean + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '0'", char_db) ) + Sql_ShowDebug(sql_handle); + + //guilddb clean + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_lv` = '0' AND `max_member` = '0' AND `exp` = '0' AND `next_exp` = '0' AND `average_lv` = '0'", guild_db) ) + Sql_ShowDebug(sql_handle); + + //guildmemberdb clean + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '0' AND `account_id` = '0' AND `char_id` = '0'", guild_member_db) ) + Sql_ShowDebug(sql_handle); + + set_defaultparse(parse_char); + char_fd = make_listen_bind(bind_ip, char_port); + ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port); + + if( runflag != CORE_ST_STOP ) + { + shutdown_callback = do_shutdown; + runflag = CHARSERVER_ST_RUNNING; + } + + return 0; +} diff --git a/src/char/char.h b/src/char/char.h new file mode 100644 index 000000000..e16350cb3 --- /dev/null +++ b/src/char/char.h @@ -0,0 +1,83 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CHAR_SQL_H_ +#define _CHAR_SQL_H_ + +#include "../config/core.h" +#include "../common/core.h" // CORE_ST_LAST + +enum E_CHARSERVER_ST +{ + CHARSERVER_ST_RUNNING = CORE_ST_LAST, + CHARSERVER_ST_SHUTDOWN, + CHARSERVER_ST_LAST +}; + +struct mmo_charstatus; + +#define MAX_MAP_SERVERS 30 + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +enum { + TABLE_INVENTORY, + TABLE_CART, + TABLE_STORAGE, + TABLE_GUILD_STORAGE, +}; + +int memitemdata_to_sql(const struct item items[], int max, int id, int tableswitch); + +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +int char_married(int pl1,int pl2); +int char_child(int parent_id, int child_id); +int char_family(int pl1,int pl2,int pl3); + +int request_accreg2(int account_id, int char_id); +int save_accreg2(unsigned char* buf, int len); + +extern int char_name_option; +extern char char_name_letters[]; +extern bool char_gm_read; +extern int autosave_interval; +extern int save_log; +extern char db_path[]; +extern char char_db[256]; +extern char scdata_db[256]; +extern char cart_db[256]; +extern char inventory_db[256]; +extern char charlog_db[256]; +extern char storage_db[256]; +extern char interlog_db[256]; +extern char reg_db[256]; +extern char skill_db[256]; +extern char memo_db[256]; +extern char guild_db[256]; +extern char guild_alliance_db[256]; +extern char guild_castle_db[256]; +extern char guild_expulsion_db[256]; +extern char guild_member_db[256]; +extern char guild_position_db[256]; +extern char guild_skill_db[256]; +extern char guild_storage_db[256]; +extern char party_db[256]; +extern char pet_db[256]; +extern char mail_db[256]; +extern char auction_db[256]; +extern char quest_db[256]; +extern char homunculus_db[256]; +extern char skill_homunculus_db[256]; +extern char mercenary_db[256]; +extern char mercenary_owner_db[256]; +extern char ragsrvinfo_db[256]; + +extern int db_use_sqldbs; // added for sql item_db read for char server [Valaris] + +extern int guild_exp_rate; +extern int log_inter; + +#endif /* _CHAR_SQL_H_ */ diff --git a/src/char/int_auction.c b/src/char/int_auction.c new file mode 100644 index 000000000..34aed5bf7 --- /dev/null +++ b/src/char/int_auction.c @@ -0,0 +1,495 @@ +// 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/db.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/sql.h" +#include "../common/timer.h" +#include "char.h" +#include "inter.h" +#include "int_mail.h" +#include "int_auction.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static DBMap* auction_db_ = NULL; // int auction_id -> struct auction_data* + +void auction_delete(struct auction_data *auction); +static int auction_end_timer(int tid, unsigned int tick, int id, intptr_t data); + +static int auction_count(int char_id, bool buy) +{ + int i = 0; + struct auction_data *auction; + DBIterator *iter = db_iterator(auction_db_); + + for( auction = dbi_first(iter); dbi_exists(iter); auction = dbi_next(iter) ) + { + if( (buy && auction->buyer_id == char_id) || (!buy && auction->seller_id == char_id) ) + i++; + } + dbi_destroy(iter); + + return i; +} + +void auction_save(struct auction_data *auction) +{ + int j; + StringBuf buf; + SqlStmt* stmt; + + if( !auction ) + return; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "UPDATE `%s` SET `seller_id` = '%d', `seller_name` = ?, `buyer_id` = '%d', `buyer_name` = ?, `price` = '%d', `buynow` = '%d', `hours` = '%d', `timestamp` = '%lu', `nameid` = '%d', `item_name` = ?, `type` = '%d', `refine` = '%d', `attribute` = '%d'", + auction_db, auction->seller_id, auction->buyer_id, auction->price, auction->buynow, auction->hours, (unsigned long)auction->timestamp, auction->item.nameid, auction->type, auction->item.refine, auction->item.attribute); + for( j = 0; j < MAX_SLOTS; j++ ) + StringBuf_Printf(&buf, ", `card%d` = '%d'", j, auction->item.card[j]); + StringBuf_Printf(&buf, " WHERE `auction_id` = '%d'", auction->auction_id); + + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_SUCCESS != SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, auction->seller_name, strnlen(auction->seller_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, auction->buyer_name, strnlen(auction->buyer_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, auction->item_name, strnlen(auction->item_name, ITEM_NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + } + + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); +} + +unsigned int auction_create(struct auction_data *auction) +{ + int j; + StringBuf buf; + SqlStmt* stmt; + + if( !auction ) + return false; + + auction->timestamp = time(NULL) + (auction->hours * 3600); + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`seller_id`,`seller_name`,`buyer_id`,`buyer_name`,`price`,`buynow`,`hours`,`timestamp`,`nameid`,`item_name`,`type`,`refine`,`attribute`,`unique_id`", auction_db); + for( j = 0; j < MAX_SLOTS; j++ ) + StringBuf_Printf(&buf, ",`card%d`", j); + StringBuf_Printf(&buf, ") VALUES ('%d',?,'%d',?,'%d','%d','%d','%lu','%d',?,'%d','%d','%d','%"PRIu64"'", + auction->seller_id, auction->buyer_id, auction->price, auction->buynow, auction->hours, (unsigned long)auction->timestamp, auction->item.nameid, auction->type, auction->item.refine, auction->item.attribute, auction->item.unique_id); + for( j = 0; j < MAX_SLOTS; j++ ) + StringBuf_Printf(&buf, ",'%d'", auction->item.card[j]); + StringBuf_AppendStr(&buf, ")"); + + //Unique Non Stackable Item ID + updateLastUid(auction->item.unique_id); + dbUpdateUid(sql_handle); + + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_SUCCESS != SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, auction->seller_name, strnlen(auction->seller_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, auction->buyer_name, strnlen(auction->buyer_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, auction->item_name, strnlen(auction->item_name, ITEM_NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + auction->auction_id = 0; + } + else + { + struct auction_data *auction_; + unsigned int tick = auction->hours * 3600000; + + auction->item.amount = 1; + auction->item.identify = 1; + auction->item.expire_time = 0; + + auction->auction_id = (unsigned int)SqlStmt_LastInsertId(stmt); + auction->auction_end_timer = add_timer( gettick() + tick , auction_end_timer, auction->auction_id, 0); + ShowInfo("New Auction %u | time left %u ms | By %s.\n", auction->auction_id, tick, auction->seller_name); + + CREATE(auction_, struct auction_data, 1); + memcpy(auction_, auction, sizeof(struct auction_data)); + idb_put(auction_db_, auction_->auction_id, auction_); + } + + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); + + return auction->auction_id; +} + +static void mapif_Auction_message(int char_id, unsigned char result) +{ + unsigned char buf[74]; + + WBUFW(buf,0) = 0x3854; + WBUFL(buf,2) = char_id; + WBUFL(buf,6) = result; + mapif_sendall(buf,7); +} + +static int auction_end_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct auction_data *auction; + if( (auction = (struct auction_data *)idb_get(auction_db_, id)) != NULL ) + { + if( auction->buyer_id ) + { + mail_sendmail(0, "Auction Manager", auction->buyer_id, auction->buyer_name, "Auction", "Thanks, you won the auction!.", 0, &auction->item); + mapif_Auction_message(auction->buyer_id, 6); // You have won the auction + mail_sendmail(0, "Auction Manager", auction->seller_id, auction->seller_name, "Auction", "Payment for your auction!.", auction->price, NULL); + } + else + mail_sendmail(0, "Auction Manager", auction->seller_id, auction->seller_name, "Auction", "No buyers have been found for your auction.", 0, &auction->item); + + ShowInfo("Auction End: id %u.\n", auction->auction_id); + + auction->auction_end_timer = INVALID_TIMER; + auction_delete(auction); + } + + return 0; +} + +void auction_delete(struct auction_data *auction) +{ + unsigned int auction_id = auction->auction_id; + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `auction_id` = '%d'", auction_db, auction_id) ) + Sql_ShowDebug(sql_handle); + + if( auction->auction_end_timer != INVALID_TIMER ) + delete_timer(auction->auction_end_timer, auction_end_timer); + + idb_remove(auction_db_, auction_id); +} + +void inter_auctions_fromsql(void) +{ + int i; + struct auction_data *auction; + struct item *item; + char *data; + StringBuf buf; + unsigned int tick = gettick(), endtick; + time_t now = time(NULL); + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `auction_id`,`seller_id`,`seller_name`,`buyer_id`,`buyer_name`," + "`price`,`buynow`,`hours`,`timestamp`,`nameid`,`item_name`,`type`,`refine`,`attribute`,`unique_id`"); + for( i = 0; i < MAX_SLOTS; i++ ) + StringBuf_Printf(&buf, ",`card%d`", i); + StringBuf_Printf(&buf, " FROM `%s` ORDER BY `auction_id` DESC", auction_db); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + + StringBuf_Destroy(&buf); + + while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + CREATE(auction, struct auction_data, 1); + Sql_GetData(sql_handle, 0, &data, NULL); auction->auction_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); auction->seller_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); safestrncpy(auction->seller_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 3, &data, NULL); auction->buyer_id = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); safestrncpy(auction->buyer_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 5, &data, NULL); auction->price = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); auction->buynow = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); auction->hours = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); auction->timestamp = atoi(data); + + item = &auction->item; + Sql_GetData(sql_handle, 9, &data, NULL); item->nameid = atoi(data); + Sql_GetData(sql_handle,10, &data, NULL); safestrncpy(auction->item_name, data, ITEM_NAME_LENGTH); + Sql_GetData(sql_handle,11, &data, NULL); auction->type = atoi(data); + + Sql_GetData(sql_handle,12, &data, NULL); item->refine = atoi(data); + Sql_GetData(sql_handle,13, &data, NULL); item->attribute = atoi(data); + Sql_GetData(sql_handle,14, &data, NULL); item->unique_id = strtoull(data, NULL, 10); + + item->identify = 1; + item->amount = 1; + item->expire_time = 0; + + for( i = 0; i < MAX_SLOTS; i++ ) + { + Sql_GetData(sql_handle, 14 + i, &data, NULL); + item->card[i] = atoi(data); + } + + if( auction->timestamp > now ) + endtick = ((unsigned int)(auction->timestamp - now) * 1000) + tick; + else + endtick = tick + 10000; // 10 Second's to process ended auctions + + auction->auction_end_timer = add_timer(endtick, auction_end_timer, auction->auction_id, 0); + idb_put(auction_db_, auction->auction_id, auction); + } + + Sql_FreeResult(sql_handle); +} + +static void mapif_Auction_sendlist(int fd, int char_id, short count, short pages, unsigned char *buf) +{ + int len = (sizeof(struct auction_data) * count) + 12; + + WFIFOHEAD(fd, len); + WFIFOW(fd,0) = 0x3850; + WFIFOW(fd,2) = len; + WFIFOL(fd,4) = char_id; + WFIFOW(fd,8) = count; + WFIFOW(fd,10) = pages; + memcpy(WFIFOP(fd,12), buf, len - 12); + WFIFOSET(fd,len); +} + +static void mapif_parse_Auction_requestlist(int fd) +{ + char searchtext[NAME_LENGTH]; + int char_id = RFIFOL(fd,4), len = sizeof(struct auction_data); + int price = RFIFOL(fd,10); + short type = RFIFOW(fd,8), page = max(1,RFIFOW(fd,14)); + unsigned char buf[5 * sizeof(struct auction_data)]; + DBIterator *iter = db_iterator(auction_db_); + struct auction_data *auction; + short i = 0, j = 0, pages = 1; + + memcpy(searchtext, RFIFOP(fd,16), NAME_LENGTH); + + for( auction = dbi_first(iter); dbi_exists(iter); auction = dbi_next(iter) ) + { + if( (type == 0 && auction->type != IT_ARMOR && auction->type != IT_PETARMOR) || + (type == 1 && auction->type != IT_WEAPON) || + (type == 2 && auction->type != IT_CARD) || + (type == 3 && auction->type != IT_ETC) || + (type == 4 && !strstr(auction->item_name, searchtext)) || + (type == 5 && auction->price > price) || + (type == 6 && auction->seller_id != char_id) || + (type == 7 && auction->buyer_id != char_id) ) + continue; + + i++; + if( i > 5 ) + { // Counting Pages of Total Results (5 Results per Page) + pages++; + i = 1; // First Result of This Page + } + + if( page != pages ) + continue; // This is not the requested Page + + memcpy(WBUFP(buf, j * len), auction, len); + j++; // Found Results + } + dbi_destroy(iter); + + mapif_Auction_sendlist(fd, char_id, j, pages, buf); +} + +static void mapif_Auction_register(int fd, struct auction_data *auction) +{ + int len = sizeof(struct auction_data) + 4; + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x3851; + WFIFOW(fd,2) = len; + memcpy(WFIFOP(fd,4), auction, sizeof(struct auction_data)); + WFIFOSET(fd,len); +} + +static void mapif_parse_Auction_register(int fd) +{ + struct auction_data auction; + if( RFIFOW(fd,2) != sizeof(struct auction_data) + 4 ) + return; + + memcpy(&auction, RFIFOP(fd,4), sizeof(struct auction_data)); + if( auction_count(auction.seller_id, false) < 5 ) + auction.auction_id = auction_create(&auction); + + mapif_Auction_register(fd, &auction); +} + +static void mapif_Auction_cancel(int fd, int char_id, unsigned char result) +{ + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x3852; + WFIFOL(fd,2) = char_id; + WFIFOB(fd,6) = result; + WFIFOSET(fd,7); +} + +static void mapif_parse_Auction_cancel(int fd) +{ + int char_id = RFIFOL(fd,2), auction_id = RFIFOL(fd,6); + struct auction_data *auction; + + if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL ) + { + mapif_Auction_cancel(fd, char_id, 1); // Bid Number is Incorrect + return; + } + + if( auction->seller_id != char_id ) + { + mapif_Auction_cancel(fd, char_id, 2); // You cannot end the auction + return; + } + + if( auction->buyer_id > 0 ) + { + mapif_Auction_cancel(fd, char_id, 3); // An auction with at least one bidder cannot be canceled + return; + } + + mail_sendmail(0, "Auction Manager", auction->seller_id, auction->seller_name, "Auction", "Auction canceled.", 0, &auction->item); + auction_delete(auction); + + mapif_Auction_cancel(fd, char_id, 0); // The auction has been canceled +} + +static void mapif_Auction_close(int fd, int char_id, unsigned char result) +{ + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x3853; + WFIFOL(fd,2) = char_id; + WFIFOB(fd,6) = result; + WFIFOSET(fd,7); +} + +static void mapif_parse_Auction_close(int fd) +{ + int char_id = RFIFOL(fd,2), auction_id = RFIFOL(fd,6); + struct auction_data *auction; + + if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL ) + { + mapif_Auction_close(fd, char_id, 2); // Bid Number is Incorrect + return; + } + + if( auction->seller_id != char_id ) + { + mapif_Auction_close(fd, char_id, 1); // You cannot end the auction + return; + } + + if( auction->buyer_id == 0 ) + { + mapif_Auction_close(fd, char_id, 1); // You cannot end the auction + return; + } + + // Send Money to Seller + mail_sendmail(0, "Auction Manager", auction->seller_id, auction->seller_name, "Auction", "Auction closed.", auction->price, NULL); + // Send Item to Buyer + mail_sendmail(0, "Auction Manager", auction->buyer_id, auction->buyer_name, "Auction", "Auction winner.", 0, &auction->item); + mapif_Auction_message(auction->buyer_id, 6); // You have won the auction + auction_delete(auction); + + mapif_Auction_close(fd, char_id, 0); // You have ended the auction +} + +static void mapif_Auction_bid(int fd, int char_id, int bid, unsigned char result) +{ + WFIFOHEAD(fd,11); + WFIFOW(fd,0) = 0x3855; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = bid; // To Return Zeny + WFIFOB(fd,10) = result; + WFIFOSET(fd,11); +} + +static void mapif_parse_Auction_bid(int fd) +{ + int char_id = RFIFOL(fd,4), bid = RFIFOL(fd,12); + unsigned int auction_id = RFIFOL(fd,8); + struct auction_data *auction; + + if( (auction = (struct auction_data *)idb_get(auction_db_, auction_id)) == NULL || auction->price >= bid || auction->seller_id == char_id ) + { + mapif_Auction_bid(fd, char_id, bid, 0); // You have failed to bid in the auction + return; + } + + if( auction_count(char_id, true) > 4 && bid < auction->buynow && auction->buyer_id != char_id ) + { + mapif_Auction_bid(fd, char_id, bid, 9); // You cannot place more than 5 bids at a time + return; + } + + if( auction->buyer_id > 0 ) + { // Send Money back to the previous Buyer + if( auction->buyer_id != char_id ) + { + mail_sendmail(0, "Auction Manager", auction->buyer_id, auction->buyer_name, "Auction", "Someone has placed a higher bid.", auction->price, NULL); + mapif_Auction_message(auction->buyer_id, 7); // You have failed to win the auction + } + else + mail_sendmail(0, "Auction Manager", auction->buyer_id, auction->buyer_name, "Auction", "You have placed a higher bid.", auction->price, NULL); + } + + auction->buyer_id = char_id; + safestrncpy(auction->buyer_name, (char*)RFIFOP(fd,16), NAME_LENGTH); + auction->price = bid; + + if( bid >= auction->buynow ) + { // Automatic won the auction + mapif_Auction_bid(fd, char_id, bid - auction->buynow, 1); // You have successfully bid in the auction + + mail_sendmail(0, "Auction Manager", auction->buyer_id, auction->buyer_name, "Auction", "You have won the auction.", 0, &auction->item); + mapif_Auction_message(char_id, 6); // You have won the auction + mail_sendmail(0, "Auction Manager", auction->seller_id, auction->seller_name, "Auction", "Payment for your auction!.", auction->buynow, NULL); + + auction_delete(auction); + return; + } + + auction_save(auction); + + mapif_Auction_bid(fd, char_id, 0, 1); // You have successfully bid in the auction +} + +/*========================================== + * Packets From Map Server + *------------------------------------------*/ +int inter_auction_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)) + { + case 0x3050: mapif_parse_Auction_requestlist(fd); break; + case 0x3051: mapif_parse_Auction_register(fd); break; + case 0x3052: mapif_parse_Auction_cancel(fd); break; + case 0x3053: mapif_parse_Auction_close(fd); break; + case 0x3055: mapif_parse_Auction_bid(fd); break; + default: + return 0; + } + return 1; +} + +int inter_auction_sql_init(void) +{ + auction_db_ = idb_alloc(DB_OPT_RELEASE_DATA); + inter_auctions_fromsql(); + + return 0; +} + +void inter_auction_sql_final(void) +{ + auction_db_->destroy(auction_db_,NULL); + + return; +} diff --git a/src/char/int_auction.h b/src/char/int_auction.h new file mode 100644 index 000000000..bf26b152c --- /dev/null +++ b/src/char/int_auction.h @@ -0,0 +1,12 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_AUCTION_SQL_H_ +#define _INT_AUCTION_SQL_H_ + +int inter_auction_parse_frommap(int fd); + +int inter_auction_sql_init(void); +void inter_auction_sql_final(void); + +#endif /* _INT_AUCTION_SQL_H_ */ diff --git a/src/char/int_elemental.c b/src/char/int_elemental.c new file mode 100644 index 000000000..3c2f6672d --- /dev/null +++ b/src/char/int_elemental.c @@ -0,0 +1,163 @@ +// 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/strlib.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/utils.h" +#include "../common/sql.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +bool mapif_elemental_save(struct s_elemental* ele) { + bool flag = true; + + if( ele->elemental_id == 0 ) { // Create new DB entry + if( SQL_ERROR == Sql_Query(sql_handle, + "INSERT INTO `elemental` (`char_id`,`class`,`mode`,`hp`,`sp`,`max_hp`,`max_sp`,`atk1`,`atk2`,`matk`,`aspd`,`def`,`mdef`,`flee`,`hit`,`life_time`)" + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%u')", + ele->char_id, ele->class_, ele->mode, ele->hp, ele->sp, ele->max_hp, ele->max_sp, ele->atk, ele->atk2, ele->matk, ele->amotion, ele->def, ele->mdef, ele->flee, ele->hit, ele->life_time) ) + { + Sql_ShowDebug(sql_handle); + flag = false; + } + else + ele->elemental_id = (int)Sql_LastInsertId(sql_handle); + } else if( SQL_ERROR == Sql_Query(sql_handle, + "UPDATE `elemental` SET `char_id` = '%d', `class` = '%d', `mode` = '%d', `hp` = '%d', `sp` = '%d'," + "`max_hp` = '%d', `max_sp` = '%d', `atk1` = '%d', `atk2` = '%d', `matk` = '%d', `aspd` = '%d', `def` = '%d'," + "`mdef` = '%d', `flee` = '%d', `hit` = '%d', `life_time` = '%u' WHERE `ele_id` = '%d'", + ele->char_id, ele->class_, ele->mode, ele->hp, ele->sp, ele->max_hp, ele->max_sp, ele->atk, ele->atk2, + ele->matk, ele->amotion, ele->def, ele->mdef, ele->flee, ele->hit, ele->life_time, ele->elemental_id) ) + { // Update DB entry + Sql_ShowDebug(sql_handle); + flag = false; + } + return flag; +} + +bool mapif_elemental_load(int ele_id, int char_id, struct s_elemental *ele) { + char* data; + + memset(ele, 0, sizeof(struct s_elemental)); + ele->elemental_id = ele_id; + ele->char_id = char_id; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `class`, `mode`, `hp`, `sp`, `max_hp`, `max_sp`, `atk1`, `atk2`, `matk`, `aspd`," + "`def`, `mdef`, `flee`, `hit`, `life_time` FROM `elemental` WHERE `ele_id` = '%d' AND `char_id` = '%d'", + ele_id, char_id) ) { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) { + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, NULL); ele->class_ = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); ele->mode = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); ele->hp = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); ele->sp = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); ele->max_hp = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); ele->max_sp = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); ele->atk = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); ele->atk2 = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); ele->matk = atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); ele->amotion = atoi(data); + Sql_GetData(sql_handle, 10, &data, NULL); ele->def = atoi(data); + Sql_GetData(sql_handle, 11, &data, NULL); ele->mdef = atoi(data); + Sql_GetData(sql_handle, 12, &data, NULL); ele->flee = atoi(data); + Sql_GetData(sql_handle, 13, &data, NULL); ele->hit = atoi(data); + Sql_GetData(sql_handle, 14, &data, NULL); ele->life_time = atoi(data); + Sql_FreeResult(sql_handle); + if( save_log ) + ShowInfo("Elemental loaded (%d - %d).\n", ele->elemental_id, ele->char_id); + + return true; +} + +bool mapif_elemental_delete(int ele_id) { + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `elemental` WHERE `ele_id` = '%d'", ele_id) ) { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +static void mapif_elemental_send(int fd, struct s_elemental *ele, unsigned char flag) { + int size = sizeof(struct s_elemental) + 5; + + WFIFOHEAD(fd,size); + WFIFOW(fd,0) = 0x387c; + WFIFOW(fd,2) = size; + WFIFOB(fd,4) = flag; + memcpy(WFIFOP(fd,5),ele,sizeof(struct s_elemental)); + WFIFOSET(fd,size); +} + +static void mapif_parse_elemental_create(int fd, struct s_elemental* ele) { + bool result = mapif_elemental_save(ele); + mapif_elemental_send(fd, ele, result); +} + +static void mapif_parse_elemental_load(int fd, int ele_id, int char_id) { + struct s_elemental ele; + bool result = mapif_elemental_load(ele_id, char_id, &ele); + mapif_elemental_send(fd, &ele, result); +} + +static void mapif_elemental_deleted(int fd, unsigned char flag) { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x387d; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,3); +} + +static void mapif_parse_elemental_delete(int fd, int ele_id) { + bool result = mapif_elemental_delete(ele_id); + mapif_elemental_deleted(fd, result); +} + +static void mapif_elemental_saved(int fd, unsigned char flag) { + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x387e; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,3); +} + +static void mapif_parse_elemental_save(int fd, struct s_elemental* ele) { + bool result = mapif_elemental_save(ele); + mapif_elemental_saved(fd, result); +} + +void inter_elemental_sql_init(void) { + return; +} +void inter_elemental_sql_final(void) { + return; +} + +/*========================================== + * Inter Packets + *------------------------------------------*/ +int inter_elemental_parse_frommap(int fd) { + unsigned short cmd = RFIFOW(fd,0); + + switch( cmd ) { + case 0x307c: mapif_parse_elemental_create(fd, (struct s_elemental*)RFIFOP(fd,4)); break; + case 0x307d: mapif_parse_elemental_load(fd, (int)RFIFOL(fd,2), (int)RFIFOL(fd,6)); break; + case 0x307e: mapif_parse_elemental_delete(fd, (int)RFIFOL(fd,2)); break; + case 0x307f: mapif_parse_elemental_save(fd, (struct s_elemental*)RFIFOP(fd,4)); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_elemental.h b/src/char/int_elemental.h new file mode 100644 index 000000000..7eb5c2958 --- /dev/null +++ b/src/char/int_elemental.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_ELEMENTAL_SQL_H_ +#define _INT_ELEMENTAL_SQL_H_ + +struct s_elemental; + +void inter_elemental_sql_init(void); +void inter_elemental_sql_final(void); +int inter_elemental_parse_frommap(int fd); + +bool mapif_elemental_delete(int ele_id); + +#endif /* _INT_ELEMENTAL_SQL_H_ */ diff --git a/src/char/int_guild.c b/src/char/int_guild.c new file mode 100644 index 000000000..9090bc007 --- /dev/null +++ b/src/char/int_guild.c @@ -0,0 +1,1872 @@ +// 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/showmsg.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "char.h" +#include "inter.h" +#include "int_guild.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#define GS_MEMBER_UNMODIFIED 0x00 +#define GS_MEMBER_MODIFIED 0x01 +#define GS_MEMBER_NEW 0x02 + +#define GS_POSITION_UNMODIFIED 0x00 +#define GS_POSITION_MODIFIED 0x01 + +// LSB = 0 => Alliance, LSB = 1 => Opposition +#define GUILD_ALLIANCE_TYPE_MASK 0x01 +#define GUILD_ALLIANCE_REMOVE 0x08 + +static const char dataToHex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + +//Guild cache +static DBMap* guild_db_; // int guild_id -> struct guild* +static DBMap *castle_db; + +static unsigned int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes); +int mapif_guild_broken(int guild_id,int flag); +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(int key,void *data,va_list ap); +int inter_guild_tosql(struct guild *g,int flag); + +static int guild_save_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + static int last_id = 0; //To know in which guild we were. + int state = 0; //0: Have not reached last guild. 1: Reached last guild, ready for save. 2: Some guild saved, don't do further saving. + DBIterator *iter = db_iterator(guild_db_); + DBKey key; + struct guild* g; + + if( last_id == 0 ) //Save the first guild in the list. + state = 1; + + for( g = db_data2ptr(iter->first(iter, &key)); dbi_exists(iter); g = db_data2ptr(iter->next(iter, &key)) ) + { + if( state == 0 && g->guild_id == last_id ) + state++; //Save next guild in the list. + else + if( state == 1 && g->save_flag&GS_MASK ) + { + inter_guild_tosql(g, g->save_flag&GS_MASK); + g->save_flag &= ~GS_MASK; + + //Some guild saved. + last_id = g->guild_id; + state++; + } + + if( g->save_flag == GS_REMOVE ) + {// Nothing to save, guild is ready for removal. + if (save_log) + ShowInfo("Guild Unloaded (%d - %s)\n", g->guild_id, g->name); + db_remove(guild_db_, key); + } + } + dbi_destroy(iter); + + if( state != 2 ) //Reached the end of the guild db without saving. + last_id = 0; //Reset guild saved, return to beginning. + + state = guild_db_->size(guild_db_); + if( state < 1 ) state = 1; //Calculate the time slot for the next save. + add_timer(tick + autosave_interval/state, guild_save_timer, 0, 0); + return 0; +} + +int inter_guild_removemember_tosql(int account_id, int char_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE from `%s` where `account_id` = '%d' and `char_id` = '%d'", guild_member_db, account_id, char_id) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `guild_id` = '0' WHERE `char_id` = '%d'", char_db, char_id) ) + Sql_ShowDebug(sql_handle); + return 0; +} + +// Save guild into sql +int inter_guild_tosql(struct guild *g,int flag) +{ + // Table guild (GS_BASIC_MASK) + // GS_EMBLEM `emblem_len`,`emblem_id`,`emblem_data` + // GS_CONNECT `connect_member`,`average_lv` + // GS_MES `mes1`,`mes2` + // GS_LEVEL `guild_lv`,`max_member`,`exp`,`next_exp`,`skill_point` + // GS_BASIC `name`,`master`,`char_id` + + // GS_MEMBER `guild_member` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`name`) + // GS_POSITION `guild_position` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) + // GS_ALLIANCE `guild_alliance` (`guild_id`,`opposition`,`alliance_id`,`name`) + // GS_EXPULSION `guild_expulsion` (`guild_id`,`account_id`,`name`,`mes`) + // GS_SKILL `guild_skill` (`guild_id`,`id`,`lv`) + + // temporary storage for str convertion. They must be twice the size of the + // original string to ensure no overflows will occur. [Skotlex] + char t_info[256]; + char esc_name[NAME_LENGTH*2+1]; + char esc_master[NAME_LENGTH*2+1]; + char new_guild = 0; + int i=0; + + if (g->guild_id<=0 && g->guild_id != -1) return 0; + +#ifdef NOISY + ShowInfo("Save guild request ("CL_BOLD"%d"CL_RESET" - flag 0x%x).",g->guild_id, flag); +#endif + + Sql_EscapeStringLen(sql_handle, esc_name, g->name, strnlen(g->name, NAME_LENGTH)); + Sql_EscapeStringLen(sql_handle, esc_master, g->master, strnlen(g->master, NAME_LENGTH)); + *t_info = '\0'; + + // Insert a new guild the guild + if (flag&GS_BASIC && g->guild_id == -1) + { + strcat(t_info, " guild_create"); + + // Create a new guild + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` " + "(`name`,`master`,`guild_lv`,`max_member`,`average_lv`,`char_id`) " + "VALUES ('%s', '%s', '%d', '%d', '%d', '%d')", + guild_db, esc_name, esc_master, g->guild_lv, g->max_member, g->average_lv, g->member[0].char_id) ) + { + Sql_ShowDebug(sql_handle); + if (g->guild_id == -1) + return 0; //Failed to create guild! + } + else + { + g->guild_id = (int)Sql_LastInsertId(sql_handle); + new_guild = 1; + } + } + + // If we need an update on an existing guild or more update on the new guild + if (((flag & GS_BASIC_MASK) && !new_guild) || ((flag & (GS_BASIC_MASK & ~GS_BASIC)) && new_guild)) + { + StringBuf buf; + bool add_comma = false; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "UPDATE `%s` SET ", guild_db); + + if (flag & GS_EMBLEM) + { + char emblem_data[sizeof(g->emblem_data)*2+1]; + char* pData = emblem_data; + + strcat(t_info, " emblem"); + // Convert emblem_data to hex + //TODO: why not use binary directly? [ultramage] + for(i=0; i<g->emblem_len; i++){ + *pData++ = dataToHex[(g->emblem_data[i] >> 4) & 0x0F]; + *pData++ = dataToHex[g->emblem_data[i] & 0x0F]; + } + *pData = 0; + StringBuf_Printf(&buf, "`emblem_len`=%d, `emblem_id`=%d, `emblem_data`='%s'", g->emblem_len, g->emblem_id, emblem_data); + add_comma = true; + } + if (flag & GS_BASIC) + { + strcat(t_info, " basic"); + if( add_comma ) + StringBuf_AppendStr(&buf, ", "); + else + add_comma = true; + StringBuf_Printf(&buf, "`name`='%s', `master`='%s', `char_id`=%d", esc_name, esc_master, g->member[0].char_id); + } + if (flag & GS_CONNECT) + { + strcat(t_info, " connect"); + if( add_comma ) + StringBuf_AppendStr(&buf, ", "); + else + add_comma = true; + StringBuf_Printf(&buf, "`connect_member`=%d, `average_lv`=%d", g->connect_member, g->average_lv); + } + if (flag & GS_MES) + { + char esc_mes1[sizeof(g->mes1)*2+1]; + char esc_mes2[sizeof(g->mes2)*2+1]; + + strcat(t_info, " mes"); + if( add_comma ) + StringBuf_AppendStr(&buf, ", "); + else + add_comma = true; + Sql_EscapeStringLen(sql_handle, esc_mes1, g->mes1, strnlen(g->mes1, sizeof(g->mes1))); + Sql_EscapeStringLen(sql_handle, esc_mes2, g->mes2, strnlen(g->mes2, sizeof(g->mes2))); + StringBuf_Printf(&buf, "`mes1`='%s', `mes2`='%s'", esc_mes1, esc_mes2); + } + if (flag & GS_LEVEL) + { + strcat(t_info, " level"); + if( add_comma ) + StringBuf_AppendStr(&buf, ", "); + else + add_comma = true; + StringBuf_Printf(&buf, "`guild_lv`=%d, `skill_point`=%d, `exp`=%"PRIu64", `next_exp`=%u, `max_member`=%d", g->guild_lv, g->skill_point, g->exp, g->next_exp, g->max_member); + } + StringBuf_Printf(&buf, " WHERE `guild_id`=%d", g->guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "%s", StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + StringBuf_Destroy(&buf); + } + + if (flag&GS_MEMBER) + { + struct guild_member *m; + + strcat(t_info, " members"); + // Update only needed players + for(i=0;i<g->max_member;i++){ + m = &g->member[i]; + if (!m->modified) + continue; + if(m->account_id) { + //Since nothing references guild member table as foreign keys, it's safe to use REPLACE INTO + Sql_EscapeStringLen(sql_handle, esc_name, m->name, strnlen(m->name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`name`) " + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%"PRIu64"','%d','%d','%d','%s')", + guild_member_db, g->guild_id, m->account_id, m->char_id, + m->hair, m->hair_color, m->gender, + m->class_, m->lv, m->exp, m->exp_payper, m->online, m->position, esc_name) ) + Sql_ShowDebug(sql_handle); + if (m->modified&GS_MEMBER_NEW || new_guild == 1) + { + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `guild_id` = '%d' WHERE `char_id` = '%d'", + char_db, g->guild_id, m->char_id) ) + Sql_ShowDebug(sql_handle); + } + m->modified = GS_MEMBER_UNMODIFIED; + } + } + } + + if (flag&GS_POSITION){ + strcat(t_info, " positions"); + //printf("- Insert guild %d to guild_position\n",g->guild_id); + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p = &g->position[i]; + if (!p->modified) + continue; + Sql_EscapeStringLen(sql_handle, esc_name, p->name, strnlen(p->name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) VALUES ('%d','%d','%s','%d','%d')", + guild_position_db, g->guild_id, i, esc_name, p->mode, p->exp_mode) ) + Sql_ShowDebug(sql_handle); + p->modified = GS_POSITION_UNMODIFIED; + } + } + + if (flag&GS_ALLIANCE) + { + // Delete current alliances + // NOTE: no need to do it on both sides since both guilds in memory had + // their info changed, not to mention this would also mess up oppositions! + // [Skotlex] + //if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'", guild_alliance_db, g->guild_id, g->guild_id) ) + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id`='%d'", guild_alliance_db, g->guild_id) ) + { + Sql_ShowDebug(sql_handle); + } + else + { + //printf("- Insert guild %d to guild_alliance\n",g->guild_id); + for(i=0;i<MAX_GUILDALLIANCE;i++) + { + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0) + { + Sql_EscapeStringLen(sql_handle, esc_name, a->name, strnlen(a->name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, g->guild_id, a->opposition, a->guild_id, esc_name) ) + Sql_ShowDebug(sql_handle); + } + } + } + } + + if (flag&GS_EXPULSION){ + strcat(t_info, " expulsions"); + //printf("- Insert guild %d to guild_expulsion\n",g->guild_id); + for(i=0;i<MAX_GUILDEXPULSION;i++){ + struct guild_expulsion *e=&g->expulsion[i]; + if(e->account_id>0){ + char esc_mes[sizeof(e->mes)*2+1]; + + Sql_EscapeStringLen(sql_handle, esc_name, e->name, strnlen(e->name, NAME_LENGTH)); + Sql_EscapeStringLen(sql_handle, esc_mes, e->mes, strnlen(e->mes, sizeof(e->mes))); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`guild_id`,`account_id`,`name`,`mes`) " + "VALUES ('%d','%d','%s','%s')", guild_expulsion_db, g->guild_id, e->account_id, esc_name, esc_mes) ) + Sql_ShowDebug(sql_handle); + } + } + } + + if (flag&GS_SKILL){ + strcat(t_info, " skills"); + //printf("- Insert guild %d to guild_skill\n",g->guild_id); + for(i=0;i<MAX_GUILDSKILL;i++){ + if (g->skill[i].id>0 && g->skill[i].lv>0){ + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`guild_id`,`id`,`lv`) VALUES ('%d','%d','%d')", + guild_skill_db, g->guild_id, g->skill[i].id, g->skill[i].lv) ) + Sql_ShowDebug(sql_handle); + } + } + } + + if (save_log) + ShowInfo("Saved guild (%d - %s):%s\n",g->guild_id,g->name,t_info); + return 1; +} + +// Read guild from sql +struct guild * inter_guild_fromsql(int guild_id) +{ + struct guild *g; + char* data; + size_t len; + char* p; + int i; + + if( guild_id <= 0 ) + return NULL; + + g = (struct guild*)idb_get(guild_db_, guild_id); + if( g ) + return g; + +#ifdef NOISY + ShowInfo("Guild load request (%d)...\n", guild_id); +#endif + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT g.`name`,c.`name`,g.`guild_lv`,g.`connect_member`,g.`max_member`,g.`average_lv`,g.`exp`,g.`next_exp`,g.`skill_point`,g.`mes1`,g.`mes2`,g.`emblem_len`,g.`emblem_id`,g.`emblem_data` " + "FROM `%s` g LEFT JOIN `%s` c ON c.`char_id` = g.`char_id` WHERE g.`guild_id`='%d'", guild_db, char_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + return NULL; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + return NULL;// Guild does not exists. + + CREATE(g, struct guild, 1); + + g->guild_id = guild_id; + Sql_GetData(sql_handle, 0, &data, &len); memcpy(g->name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 1, &data, &len); memcpy(g->master, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 2, &data, NULL); g->guild_lv = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); g->connect_member = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); g->max_member = atoi(data); + if( g->max_member > MAX_GUILD ) + { // Fix reduction of MAX_GUILD [PoW] + ShowWarning("Guild %d:%s specifies higher capacity (%d) than MAX_GUILD (%d)\n", guild_id, g->name, g->max_member, MAX_GUILD); + g->max_member = MAX_GUILD; + } + Sql_GetData(sql_handle, 5, &data, NULL); g->average_lv = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); g->exp = strtoull(data, NULL, 10); + Sql_GetData(sql_handle, 7, &data, NULL); g->next_exp = (unsigned int)strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 8, &data, NULL); g->skill_point = atoi(data); + Sql_GetData(sql_handle, 9, &data, &len); memcpy(g->mes1, data, min(len, sizeof(g->mes1))); + Sql_GetData(sql_handle, 10, &data, &len); memcpy(g->mes2, data, min(len, sizeof(g->mes2))); + Sql_GetData(sql_handle, 11, &data, &len); g->emblem_len = atoi(data); + Sql_GetData(sql_handle, 12, &data, &len); g->emblem_id = atoi(data); + Sql_GetData(sql_handle, 13, &data, &len); + // convert emblem data from hexadecimal to binary + //TODO: why not store it in the db as binary directly? [ultramage] + for( i = 0, p = g->emblem_data; i < g->emblem_len; ++i, ++p ) + { + if( *data >= '0' && *data <= '9' ) + *p = *data - '0'; + else if( *data >= 'a' && *data <= 'f' ) + *p = *data - 'a' + 10; + else if( *data >= 'A' && *data <= 'F' ) + *p = *data - 'A' + 10; + *p <<= 4; + ++data; + + if( *data >= '0' && *data <= '9' ) + *p |= *data - '0'; + else if( *data >= 'a' && *data <= 'f' ) + *p |= *data - 'a' + 10; + else if( *data >= 'A' && *data <= 'F' ) + *p |= *data - 'A' + 10; + ++data; + } + + // load guild member info + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`name` " + "FROM `%s` WHERE `guild_id`='%d' ORDER BY `position`", guild_member_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + aFree(g); + return NULL; + } + for( i = 0; i < g->max_member && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + struct guild_member* m = &g->member[i]; + + Sql_GetData(sql_handle, 0, &data, NULL); m->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); m->char_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); m->hair = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); m->hair_color = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); m->gender = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); m->class_ = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); m->lv = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); m->exp = strtoull(data, NULL, 10); + Sql_GetData(sql_handle, 8, &data, NULL); m->exp_payper = (unsigned int)atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); m->online = atoi(data); + Sql_GetData(sql_handle, 10, &data, NULL); m->position = atoi(data); + if( m->position >= MAX_GUILDPOSITION ) // Fix reduction of MAX_GUILDPOSITION [PoW] + m->position = MAX_GUILDPOSITION - 1; + Sql_GetData(sql_handle, 11, &data, &len); memcpy(m->name, data, min(len, NAME_LENGTH)); + m->modified = GS_MEMBER_UNMODIFIED; + } + + //printf("- Read guild_position %d from sql \n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'", guild_position_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + aFree(g); + return NULL; + } + while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + int position; + struct guild_position* p; + + Sql_GetData(sql_handle, 0, &data, NULL); position = atoi(data); + if( position < 0 || position >= MAX_GUILDPOSITION ) + continue;// invalid position + p = &g->position[position]; + Sql_GetData(sql_handle, 1, &data, &len); memcpy(p->name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 2, &data, NULL); p->mode = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); p->exp_mode = atoi(data); + p->modified = GS_POSITION_UNMODIFIED; + } + + //printf("- Read guild_alliance %d from sql \n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'", guild_alliance_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + aFree(g); + return NULL; + } + for( i = 0; i < MAX_GUILDALLIANCE && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + struct guild_alliance* a = &g->alliance[i]; + + Sql_GetData(sql_handle, 0, &data, NULL); a->opposition = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); a->guild_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, &len); memcpy(a->name, data, min(len, NAME_LENGTH)); + } + + //printf("- Read guild_expulsion %d from sql \n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name`,`mes` FROM `%s` WHERE `guild_id`='%d'", guild_expulsion_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + aFree(g); + return NULL; + } + for( i = 0; i < MAX_GUILDEXPULSION && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + struct guild_expulsion *e = &g->expulsion[i]; + + Sql_GetData(sql_handle, 0, &data, NULL); e->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, &len); memcpy(e->name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 2, &data, &len); memcpy(e->mes, data, min(len, sizeof(e->mes))); + } + + //printf("- Read guild_skill %d from sql \n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`", guild_skill_db, guild_id) ) + { + Sql_ShowDebug(sql_handle); + aFree(g); + return NULL; + } + + for(i = 0; i < MAX_GUILDSKILL; i++) + { //Skill IDs must always be initialized. [Skotlex] + g->skill[i].id = i + GD_SKILLBASE; + } + + while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + int id; + Sql_GetData(sql_handle, 0, &data, NULL); id = atoi(data) - GD_SKILLBASE; + if( id < 0 && id >= MAX_GUILDSKILL ) + continue;// invalid guild skill + Sql_GetData(sql_handle, 1, &data, NULL); g->skill[id].lv = atoi(data); + } + Sql_FreeResult(sql_handle); + + idb_put(guild_db_, guild_id, g); //Add to cache + g->save_flag |= GS_REMOVE; //But set it to be removed, in case it is not needed for long. + + if (save_log) + ShowInfo("Guild loaded (%d - %s)\n", guild_id, g->name); + + return g; +} + +// `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`) +int inter_guildcastle_tosql(struct guild_castle *gc) +{ + StringBuf buf; + int i; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "REPLACE INTO `%s` SET `castle_id`='%d', `guild_id`='%d', `economy`='%d', `defense`='%d', " + "`triggerE`='%d', `triggerD`='%d', `nextTime`='%d', `payTime`='%d', `createTime`='%d', `visibleC`='%d'", + guild_castle_db, gc->castle_id, gc->guild_id, gc->economy, gc->defense, + gc->triggerE, gc->triggerD, gc->nextTime, gc->payTime, gc->createTime, gc->visibleC); + for (i = 0; i < MAX_GUARDIANS; ++i) + StringBuf_Printf(&buf, ", `visibleG%d`='%d'", i, gc->guardian[i].visible); + + if (SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf))) + Sql_ShowDebug(sql_handle); + else if(save_log) + ShowInfo("Saved guild castle (%d)\n", gc->castle_id); + + StringBuf_Destroy(&buf); + return 0; +} + +// Read guild_castle from SQL +static struct guild_castle* inter_guildcastle_fromsql(int castle_id) +{ + char *data; + int i; + StringBuf buf; + struct guild_castle *gc = idb_get(castle_db, castle_id); + + if (gc != NULL) + return gc; + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, " + "`triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`"); + for (i = 0; i < MAX_GUARDIANS; ++i) + StringBuf_Printf(&buf, ", `visibleG%d`", i); + StringBuf_Printf(&buf, " FROM `%s` WHERE `castle_id`='%d'", guild_castle_db, castle_id); + if (SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf))) { + Sql_ShowDebug(sql_handle); + StringBuf_Destroy(&buf); + return NULL; + } + StringBuf_Destroy(&buf); + + CREATE(gc, struct guild_castle, 1); + gc->castle_id = castle_id; + + if (SQL_SUCCESS == Sql_NextRow(sql_handle)) { + Sql_GetData(sql_handle, 1, &data, NULL); gc->guild_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); gc->economy = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); gc->defense = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); gc->triggerE = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); gc->triggerD = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); gc->nextTime = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); gc->payTime = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); gc->createTime = atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); gc->visibleC = atoi(data); + for (i = 10; i < 10+MAX_GUARDIANS; i++) { + Sql_GetData(sql_handle, i, &data, NULL); gc->guardian[i-10].visible = atoi(data); + } + } + Sql_FreeResult(sql_handle); + + idb_put(castle_db, castle_id, gc); + + if (save_log) + ShowInfo("Loaded guild castle (%d - guild %d)\n", castle_id, gc->guild_id); + + return gc; +} + + +// Read exp_guild.txt +static bool exp_guild_parse_row(char* split[], int column, int current) +{ + int exp = atoi(split[0]); + + if (exp < 0 || exp >= INT_MAX) { + ShowError("exp_guild: Invalid exp %d at line %d\n", exp, current); + return false; + } + + guild_exp[current] = exp; + return true; +} + + +int inter_guild_CharOnline(int char_id, int guild_id) +{ + struct guild *g; + int i; + + if (guild_id == -1) { + //Get guild_id from the database + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT guild_id FROM `%s` WHERE char_id='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); + guild_id = atoi(data); + } + else + { + guild_id = 0; + } + Sql_FreeResult(sql_handle); + } + if (guild_id == 0) + return 0; //No guild... + + g = inter_guild_fromsql(guild_id); + if(!g) { + ShowError("Character %d's guild %d not found!\n", char_id, guild_id); + return 0; + } + + //Member has logged in before saving, tell saver not to delete + if(g->save_flag & GS_REMOVE) + g->save_flag &= ~GS_REMOVE; + + //Set member online + ARR_FIND( 0, g->max_member, i, g->member[i].char_id == char_id ); + if( i < g->max_member ) + { + g->member[i].online = 1; + g->member[i].modified = GS_MEMBER_MODIFIED; + } + + return 1; +} + +int inter_guild_CharOffline(int char_id, int guild_id) +{ + struct guild *g=NULL; + int online_count, i; + + if (guild_id == -1) + { + //Get guild_id from the database + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT guild_id FROM `%s` WHERE char_id='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); + guild_id = atoi(data); + } + else + { + guild_id = 0; + } + Sql_FreeResult(sql_handle); + } + if (guild_id == 0) + return 0; //No guild... + + //Character has a guild, set character offline and check if they were the only member online + g = inter_guild_fromsql(guild_id); + if (g == NULL) //Guild not found? + return 0; + + //Set member offline + ARR_FIND( 0, g->max_member, i, g->member[i].char_id == char_id ); + if( i < g->max_member ) + { + g->member[i].online = 0; + g->member[i].modified = GS_MEMBER_MODIFIED; + } + + online_count = 0; + for( i = 0; i < g->max_member; i++ ) + if( g->member[i].online ) + online_count++; + + // Remove guild from memory if no players online + if( online_count == 0 ) + g->save_flag |= GS_REMOVE; + + return 1; +} + +// Initialize guild sql +int inter_guild_sql_init(void) +{ + //Initialize the guild cache + guild_db_= idb_alloc(DB_OPT_RELEASE_DATA); + castle_db = idb_alloc(DB_OPT_RELEASE_DATA); + + //Read exp file + sv_readdb("db/"DBPATH, "exp_guild.txt", ',', 1, 1, 100, exp_guild_parse_row); + + add_timer_func_list(guild_save_timer, "guild_save_timer"); + add_timer(gettick() + 10000, guild_save_timer, 0, 0); + return 0; +} + +/** + * @see DBApply + */ +static int guild_db_final(DBKey key, DBData *data, va_list ap) +{ + struct guild *g = db_data2ptr(data); + if (g->save_flag&GS_MASK) { + inter_guild_tosql(g, g->save_flag&GS_MASK); + return 1; + } + return 0; +} + +void inter_guild_sql_final(void) +{ + guild_db_->destroy(guild_db_, guild_db_final); + db_destroy(castle_db); + return; +} + +// Get guild_id by its name. Returns 0 if not found, -1 on error. +int search_guildname(char *str) +{ + int guild_id; + char esc_name[NAME_LENGTH*2+1]; + + Sql_EscapeStringLen(sql_handle, esc_name, str, safestrnlen(str, NAME_LENGTH)); + //Lookup guilds with the same name + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT guild_id FROM `%s` WHERE name='%s'", guild_db, esc_name) ) + { + Sql_ShowDebug(sql_handle); + return -1; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); + guild_id = atoi(data); + } + else + { + guild_id = 0; + } + Sql_FreeResult(sql_handle); + return guild_id; +} + +// Check if guild is empty +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 + + //Let the calling function handle the guild removal in case they need + //to do something else with it before freeing the data. [Skotlex] + return true; +} + +unsigned int guild_nextexp(int level) +{ + if (level == 0) + return 1; + if (level < 100 && level > 0) // Change by hack + return guild_exp[level-1]; + + return 0; +} + +int guild_checkskill(struct guild *g,int id) +{ + int idx = id - GD_SKILLBASE; + + if(idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + return g->skill[idx].lv; +} + +int guild_calcinfo(struct guild *g) +{ + int i,c; + unsigned int nextexp; + struct guild before = *g; // Save guild current values + + if(g->guild_lv<=0) + g->guild_lv = 1; + nextexp = guild_nextexp(g->guild_lv); + + // Consume guild exp and increase guild level + 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); + } + + // Save next exp step + g->next_exp = nextexp; + + // Set the max number of members, Guild Extention skill - currently adds 6 to max per skill lv. + g->max_member = 16 + guild_checkskill(g, GD_EXTENSION) * 6; + 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; + } + + // Compute the guild average level level + g->average_lv=0; + g->connect_member=0; + for(i=c=0;i<g->max_member;i++) + { + if(g->member[i].account_id>0) + { + if (g->member[i].lv >= 0) + { + g->average_lv+=g->member[i].lv; + c++; + } + else + { + ShowWarning("Guild %d:%s, member %d:%s has an invalid level %d\n", g->guild_id, g->name, g->member[i].char_id, g->member[i].name, g->member[i].lv); + } + + if(g->member[i].online) + g->connect_member++; + } + } + if(c) + g->average_lv /= c; + + // Check if guild stats has change + if(g->max_member != before.max_member || g->guild_lv != before.guild_lv || g->skill_point != before.skill_point ) + { + g->save_flag |= GS_LEVEL; + mapif_guild_info(-1,g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// Packet sent to 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("int_guild: Guild created (%d - %s)\n",g->guild_id,g->name); + } else + WFIFOL(fd,6)=0; + + WFIFOSET(fd,10); + return 0; +} + +// Guild not found +int mapif_guild_noinfo(int fd,int guild_id) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3831; + WBUFW(buf,2)=8; + WBUFL(buf,4)=guild_id; + ShowWarning("int_guild: info not found %d\n",guild_id); + if(fd<0) + mapif_sendall(buf,8); + else + mapif_send(fd,buf,8); + return 0; +} + +// Send guild info +int mapif_guild_info(int fd,struct guild *g) +{ + unsigned char buf[8+sizeof(struct guild)]; + WBUFW(buf,0)=0x3831; + WBUFW(buf,2)=4+sizeof(struct guild); + memcpy(buf+4,g,sizeof(struct guild)); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); + return 0; +} + +// ACK member add +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; +} + +// ACK member leave +int mapif_guild_withdraw(int guild_id,int account_id,int char_id,int flag, const char *name, const char *mes) +{ + unsigned char buf[55+NAME_LENGTH]; + 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); + ShowInfo("int_guild: guild withdraw (%d - %d: %s - %s)\n",guild_id,account_id,name,mes); + return 0; +} + +// Send short member's info +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; +} + +// Send guild broken +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("int_guild: Guild broken (%d)\n",guild_id); + return 0; +} + +// Send guild message +int mapif_guild_message(int guild_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + if (len > 500) + len = 500; + 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; +} + +// Send basic info +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len) +{ + unsigned char buf[2048]; + if (len > 2038) + len = 2038; + 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; +} + +// Send member info +int mapif_guild_memberinfochanged(int guild_id,int account_id,int char_id, int type,const void *data,int len) +{ + unsigned char buf[2048]; + if (len > 2030) + len = 2030; + 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; +} + +// ACK guild skill up +int mapif_guild_skillupack(int guild_id,uint16 skill_id,int account_id) +{ + unsigned char buf[14]; + WBUFW(buf, 0)=0x383c; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=skill_id; + WBUFL(buf,10)=account_id; + mapif_sendall(buf,14); + return 0; +} + +// ACK guild alliance +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[19+2*NAME_LENGTH]; + 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); + return 0; +} + +// Send a guild position desc +int mapif_guild_position(struct guild *g,int idx) +{ + unsigned char buf[12 + sizeof(struct guild_position)]; + 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; +} + +// Send the guild notice +int mapif_guild_notice(struct guild *g) +{ + unsigned char buf[256]; + WBUFW(buf,0)=0x383e; + WBUFL(buf,2)=g->guild_id; + memcpy(WBUFP(buf,6),g->mes1,MAX_GUILDMES1); + memcpy(WBUFP(buf,66),g->mes2,MAX_GUILDMES2); + mapif_sendall(buf,186); + return 0; +} + +// Send emblem data +int mapif_guild_emblem(struct guild *g) +{ + unsigned char buf[12 + sizeof(g->emblem_data)]; + 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 fd, int sz, int *castle_ids) +{ + struct guild_castle *gc = NULL; + int num = (sz - 4) / sizeof(int); + int len = 4 + num * sizeof(*gc); + int i; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = 0x3840; + WFIFOW(fd, 2) = len; + for (i = 0; i < num; i++) { + gc = inter_guildcastle_fromsql(*(castle_ids++)); + memcpy(WFIFOP(fd, 4 + i * sizeof(*gc)), gc, sizeof(*gc)); + } + WFIFOSET(fd, len); + return 0; +} + +//------------------------------------------------------------------- +// Packet received from map server + + +// Guild creation request +int mapif_parse_CreateGuild(int fd,int account_id,char *name,struct guild_member *master) +{ + struct guild *g; + int i=0; +#ifdef NOISY + ShowInfo("Creating Guild (%s)\n", name); +#endif + if(search_guildname(name) != 0){ + ShowInfo("int_guild: guild with same name exists [%s]\n",name); + mapif_guild_created(fd,account_id,NULL); + return 0; + } + // 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 *)aMalloc(sizeof(struct guild)); + memset(g,0,sizeof(struct guild)); + + memcpy(g->name,name,NAME_LENGTH); + memcpy(g->master,master->name,NAME_LENGTH); + memcpy(&g->member[0],master,sizeof(struct guild_member)); + g->member[0].modified = GS_MEMBER_MODIFIED; + + // Set default positions + g->position[0].mode=0x11; + strcpy(g->position[0].name,"GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name,"Newbie"); + g->position[0].modified = g->position[MAX_GUILDPOSITION-1].modified = GS_POSITION_MODIFIED; + for(i=1;i<MAX_GUILDPOSITION-1;i++) { + sprintf(g->position[i].name,"Position %d",i+1); + g->position[i].modified = GS_POSITION_MODIFIED; + } + + // Initialize guild property + 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; + g->guild_id= -1; //Request to create guild. + + // Create the guild + if (!inter_guild_tosql(g,GS_BASIC|GS_POSITION|GS_SKILL|GS_MEMBER)) { + //Failed to Create guild.... + ShowError("Failed to create Guild %s (Guild Master: %s)\n", g->name, g->master); + mapif_guild_created(fd,account_id,NULL); + aFree(g); + return 0; + } + ShowInfo("Created Guild %d - %s (Guild Master: %s)\n", g->guild_id, g->name, g->master); + + //Add to cache + idb_put(guild_db_, g->guild_id, g); + + // Report to client + mapif_guild_created(fd,account_id,g); + mapif_guild_info(fd,g); + + if(log_inter) + inter_log("guild %s (id=%d) created by master %s (id=%d)\n", + name, g->guild_id, master->name, master->account_id ); + + return 0; +} + +// Return guild info to client +int mapif_parse_GuildInfo(int fd,int guild_id) +{ + struct guild * g = inter_guild_fromsql(guild_id); //We use this because on start-up the info of castle-owned guilds is requied. [Skotlex] + if(g) + { + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + } + else + mapif_guild_noinfo(fd,guild_id); // Failed to load info + return 0; +} + +// Add member to guild +int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m) +{ + struct guild * g; + int i; + + g = inter_guild_fromsql(guild_id); + if(g==NULL){ + // Failed to add + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; + } + + // Find an empty slot + for(i=0;i<g->max_member;i++) + { + if(g->member[i].account_id==0) + { + memcpy(&g->member[i],m,sizeof(struct guild_member)); + g->member[i].modified = (GS_MEMBER_NEW | GS_MEMBER_MODIFIED); + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,0); + if (!guild_calcinfo(g)) //Send members if it was not invoked. + mapif_guild_info(-1,g); + + g->save_flag |= GS_MEMBER; + if (g->save_flag&GS_REMOVE) + g->save_flag&=~GS_REMOVE; + return 0; + } + } + + // Failed to add + 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 = inter_guild_fromsql(guild_id); + if( g == NULL ) + { + // Unknown guild, just update the player + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'", char_db, account_id, char_id) ) + Sql_ShowDebug(sql_handle); + // mapif_guild_withdraw(guild_id,account_id,char_id,flag,g->member[i].name,mes); + 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 ) + { // Write expulsion reason + // Find an empty slot + ARR_FIND( 0, MAX_GUILDEXPULSION, j, g->expulsion[j].account_id == 0 ); + if( j == MAX_GUILDEXPULSION ) + { + // Expulsion list is full, flush the oldest one + 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_withdraw(guild_id,account_id,char_id,flag,g->member[i].name,mes); + inter_guild_removemember_tosql(g->member[i].account_id,g->member[i].char_id); + + memset(&g->member[i],0,sizeof(struct guild_member)); + + if( guild_check_empty(g) ) + mapif_parse_BreakGuild(-1,guild_id); //Break the guild. + else { + //Update member info. + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + g->save_flag |= GS_EXPULSION; + } + + return 0; +} + +// Change member info +int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id,int account_id,int char_id,int online,int lv,int class_) +{ + // Could speed up by manipulating only guild_member + struct guild * g; + int i,sum,c; + int prev_count, prev_alv; + + g = inter_guild_fromsql(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_; + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfoshort(g,i); + } + + prev_count = g->connect_member; + prev_alv = g->average_lv; + + g->average_lv = 0; + g->connect_member = 0; + c = 0; + sum = 0; + + 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; + if( g->connect_member != prev_count || g->average_lv != prev_alv ) + g->save_flag |= GS_CONNECT; + if( g->save_flag & GS_REMOVE ) + g->save_flag &= ~GS_REMOVE; + } + g->save_flag |= GS_MEMBER; //Update guild member data + return 0; +} + +// BreakGuild +int mapif_parse_BreakGuild(int fd,int guild_id) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + // Delete guild from sql + //printf("- Delete guild %d from guild\n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_member_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_castle_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_storage_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d' OR `alliance_id` = '%d'", guild_alliance_db, guild_id, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_position_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_skill_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_expulsion_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + //printf("- Update guild %d of char\n",guild_id); + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'", char_db, guild_id) ) + Sql_ShowDebug(sql_handle); + + mapif_guild_broken(guild_id,0); + + if(log_inter) + inter_log("guild %s (id=%d) broken\n",g->name,guild_id); + + //Remove the guild from memory. [Skotlex] + idb_remove(guild_db_, guild_id); + return 0; +} + +// Forward Guild message to others map servers +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); +} + +// Modification of the guild +int mapif_parse_GuildBasicInfoChange(int fd,int guild_id,int type,const char *data,int len) +{ + struct guild * g; + short dw=*((short *)data); + g = inter_guild_fromsql(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); + g->save_flag |= GS_LEVEL; + return 0; + default: + ShowError("int_guild: GuildBasicInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_basicinfochanged(guild_id,type,data,len); + return 0; +} + +// Modification of the guild +int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,int account_id,int char_id,int type,const char *data,int len) +{ + // Could make some improvement in speed, because only change guild_member + int i; + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + // Search the member + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + break; + + // Not Found + if(i==g->max_member){ + ShowWarning("int_guild: GuildMemberChange: Not found %d,%d in guild (%d - %s)\n", + account_id,char_id,guild_id,g->name); + return 0; + } + + switch(type) + { + case GMI_POSITION: + { + g->member[i].position=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; + break; + } + case GMI_EXP: + { // EXP + uint64 exp, old_exp=g->member[i].exp; + g->member[i].exp=*((uint64 *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + if (g->member[i].exp > old_exp) + { + exp = g->member[i].exp - old_exp; + + // Compute gained exp + if (guild_exp_rate != 100) + exp = exp*guild_exp_rate/100; + + // Update guild exp + if (exp > UINT64_MAX - g->exp) + g->exp = UINT64_MAX; + else + g->exp+=exp; + + guild_calcinfo(g); + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,sizeof(g->exp)); + g->save_flag |= GS_LEVEL; + } + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; + break; + } + case GMI_HAIR: + { + g->member[i].hair=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_HAIR_COLOR: + { + g->member[i].hair_color=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_GENDER: + { + g->member[i].gender=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_CLASS: + { + g->member[i].class_=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_LEVEL: + { + g->member[i].lv=*((short *)data); + g->member[i].modified = GS_MEMBER_MODIFIED; + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + default: + ShowError("int_guild: GuildMemberInfoChange: Unknown type %d\n",type); + break; + } + return 0; +} + +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, short gender) +{ + return mapif_parse_GuildMemberInfoChange(0, guild_id, account_id, char_id, GMI_GENDER, (const char*)&gender, sizeof(gender)); +} + +int inter_guild_charname_changed(int guild_id,int account_id, int char_id, char *name) +{ + struct guild *g; + int i, flag = 0; + + g = inter_guild_fromsql(guild_id); + if( g == NULL ) + { + ShowError("inter_guild_charrenamed: Can't find guild %d.\n", guild_id); + return 0; + } + + ARR_FIND(0, g->max_member, i, g->member[i].char_id == char_id); + if( i == g->max_member ) + { + ShowError("inter_guild_charrenamed: Can't find character %d in the guild\n", char_id); + return 0; + } + + if( !strcmp(g->member[i].name, g->master) ) + { + safestrncpy(g->master, name, NAME_LENGTH); + flag |= GS_BASIC; + } + safestrncpy(g->member[i].name, name, NAME_LENGTH); + g->member[i].modified = GS_MEMBER_MODIFIED; + flag |= GS_MEMBER; + + if( !inter_guild_tosql(g, flag) ) + return 0; + + mapif_guild_info(-1,g); + + return 0; +} + +// Change a position desc +int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p) +{ + // Could make some improvement in speed, because only change guild_position + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION) + return 0; + + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + mapif_guild_position(g,idx); + g->position[idx].modified = GS_POSITION_MODIFIED; + g->save_flag |= GS_POSITION; // Change guild_position + return 0; +} + +// Guild Skill UP +int mapif_parse_GuildSkillUp(int fd,int guild_id,uint16 skill_id,int account_id,int max) +{ + struct guild * g; + int idx = skill_id - GD_SKILLBASE; + + g = inter_guild_fromsql(guild_id); + if(g == NULL || idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + if(g->skill_point>0 && g->skill[idx].id>0 && g->skill[idx].lv<max ) + { + g->skill[idx].lv++; + g->skill_point--; + if (!guild_calcinfo(g)) + mapif_guild_info(-1,g); + mapif_guild_skillupack(guild_id,skill_id,account_id); + g->save_flag |= (GS_LEVEL|GS_SKILL); // Change guild & guild_skill + } + return 0; +} + +//Manual deletion of an alliance when partnering guild does not exists. [Skotlex] +static int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, int account_id1, int account_id2, int flag) +{ + int i; + char name[NAME_LENGTH]; + + ARR_FIND( 0, MAX_GUILDALLIANCE, i, g->alliance[i].guild_id == guild_id ); + if( i == MAX_GUILDALLIANCE ) + return -1; + + strcpy(name, g->alliance[i].name); + g->alliance[i].guild_id=0; + + mapif_guild_alliance(g->guild_id,guild_id,account_id1,account_id2,flag,g->name,name); + g->save_flag |= GS_ALLIANCE; + return 0; +} + +// Alliance modification +int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2,int account_id1,int account_id2,int flag) +{ + // Could speed up + struct guild *g[2]; + int j,i; + g[0] = inter_guild_fromsql(guild_id1); + g[1] = inter_guild_fromsql(guild_id2); + + if(g[0] && g[1]==NULL && (flag & GUILD_ALLIANCE_REMOVE)) //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&GUILD_ALLIANCE_REMOVE) + { + // Remove alliance/opposition, in case of alliance, remove on both side + for(i=0;i<2-(flag&GUILD_ALLIANCE_TYPE_MASK);i++) + { + ARR_FIND( 0, MAX_GUILDALLIANCE, j, g[i]->alliance[j].guild_id == g[1-i]->guild_id && g[i]->alliance[j].opposition == (flag&GUILD_ALLIANCE_TYPE_MASK) ); + if( j < MAX_GUILDALLIANCE ) + g[i]->alliance[j].guild_id = 0; + } + } + else + { + // Add alliance, in case of alliance, add on both side + for(i=0;i<2-(flag&GUILD_ALLIANCE_TYPE_MASK);i++) + { + // Search an empty slot + ARR_FIND( 0, MAX_GUILDALLIANCE, j, g[i]->alliance[j].guild_id == 0 ); + if( j < MAX_GUILDALLIANCE ) + { + g[i]->alliance[j].guild_id=g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name,g[1-i]->name,NAME_LENGTH); + // Set alliance type + g[i]->alliance[j].opposition = flag&GUILD_ALLIANCE_TYPE_MASK; + } + } + } + + // Send on all map the new alliance/opposition + mapif_guild_alliance(guild_id1,guild_id2,account_id1,account_id2,flag,g[0]->name,g[1]->name); + + // Mark the two guild to be saved + g[0]->save_flag |= GS_ALLIANCE; + g[1]->save_flag |= GS_ALLIANCE; + return 0; +} + +// Change guild message +int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2) +{ + struct guild *g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,MAX_GUILDMES1); + memcpy(g->mes2,mes2,MAX_GUILDMES2); + g->save_flag |= GS_MES; //Change mes of guild + return mapif_guild_notice(g); +} + +int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + if (len > sizeof(g->emblem_data)) + len = sizeof(g->emblem_data); + + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id++; + g->save_flag |= GS_EMBLEM; //Change guild + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd, int len, int *castle_ids) +{ + return mapif_guild_castle_dataload(fd, len, castle_ids); +} + +int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value) +{ + struct guild_castle *gc = inter_guildcastle_fromsql(castle_id); + + if (gc == NULL) { + ShowError("mapif_parse_GuildCastleDataSave: castle id=%d not found\n", castle_id); + return 0; + } + + switch (index) { + case 1: + if (log_inter && gc->guild_id != value) { + int gid = (value) ? value : gc->guild_id; + struct guild *g = idb_get(guild_db_, gid); + inter_log("guild %s (id=%d) %s castle id=%d\n", + (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", castle_id); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + default: + if (index > 9 && index <= 9+MAX_GUARDIANS) { + gc->guardian[index-10].visible = value; + break; + } + ShowError("mapif_parse_GuildCastleDataSave: not found index=%d\n", index); + return 0; + } + inter_guildcastle_tosql(gc); + return 0; +} + +int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len) +{ + struct guild * g; + struct guild_member gm; + int pos; + + g = inter_guild_fromsql(guild_id); + + if(g==NULL || len > NAME_LENGTH) + return 0; + + // Find member (name) + 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?? + + // Switch current and old GM + 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)); + + // Switch positions + g->member[pos].position = g->member[0].position; + g->member[pos].modified = GS_MEMBER_MODIFIED; + g->member[0].position = 0; //Position 0: guild Master. + g->member[0].modified = GS_MEMBER_MODIFIED; + + strncpy(g->master, name, len); + if (len < NAME_LENGTH) + g->master[len] = '\0'; + + ShowInfo("int_guild: Guildmaster Changed to %s (Guild %d - %s)\n",g->master, guild_id, g->name); + g->save_flag |= (GS_BASIC|GS_MEMBER); //Save main data and member data. + return mapif_guild_master_changed(g, g->member[0].account_id, g->member[0].char_id); +} + +// Communication from the map server +// - Can analyzed only one by one packet +// Data packet length that you set to inter.c +//- Shouldn't do checking and packet length, RFIFOSKIP is done by the caller +// Must Return +// 1 : ok +// 0 : error +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),RFIFOL(fd,14)); 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),(int *)RFIFOP(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd,RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); break; + + default: + return 0; + } + + return 1; +} + +//Leave request from the server (for deleting character from guild) +int inter_guild_leave(int guild_id, int account_id, int char_id) +{ + return mapif_parse_GuildLeave(-1, guild_id, account_id, char_id, 0, "** Character Deleted **"); +} + +int inter_guild_broken(int guild_id) +{ + return mapif_guild_broken(guild_id, 0); +} diff --git a/src/char/int_guild.h b/src/char/int_guild.h new file mode 100644 index 000000000..47c42dcc5 --- /dev/null +++ b/src/char/int_guild.h @@ -0,0 +1,37 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_GUILD_SQL_H_ +#define _INT_GUILD_SQL_H_ + +enum { + GS_BASIC = 0x0001, + GS_MEMBER = 0x0002, + GS_POSITION = 0x0004, + GS_ALLIANCE = 0x0008, + GS_EXPULSION = 0x0010, + GS_SKILL = 0x0020, + GS_EMBLEM = 0x0040, + GS_CONNECT = 0x0080, + GS_LEVEL = 0x0100, + GS_MES = 0x0200, + GS_MASK = 0x03FF, + GS_BASIC_MASK = (GS_BASIC | GS_EMBLEM | GS_CONNECT | GS_LEVEL | GS_MES), + GS_REMOVE = 0x8000, +}; + +struct guild; +struct guild_castle; + +int inter_guild_parse_frommap(int fd); +int inter_guild_sql_init(void); +void inter_guild_sql_final(void); +int inter_guild_leave(int guild_id,int account_id,int char_id); +int mapif_parse_BreakGuild(int fd,int guild_id); +int inter_guild_broken(int guild_id); +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, short gender); +int inter_guild_charname_changed(int guild_id,int account_id, int char_id, char *name); +int inter_guild_CharOnline(int char_id, int guild_id); +int inter_guild_CharOffline(int char_id, int guild_id); + +#endif /* _INT_GUILD_SQL_H_ */ diff --git a/src/char/int_homun.c b/src/char/int_homun.c new file mode 100644 index 000000000..933661954 --- /dev/null +++ b/src/char/int_homun.c @@ -0,0 +1,314 @@ +// 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/strlib.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/utils.h" +#include "../common/sql.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +int inter_homunculus_sql_init(void) +{ + return 0; +} +void inter_homunculus_sql_final(void) +{ + return; +} + +static void mapif_homunculus_created(int fd, int account_id, struct s_homunculus *sh, unsigned char flag) +{ + 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)= flag; + memcpy(WFIFOP(fd,9),sh,sizeof(struct s_homunculus)); + WFIFOSET(fd, WFIFOW(fd,2)); +} + +static void mapif_homunculus_deleted(int fd, int flag) +{ + WFIFOHEAD(fd, 3); + WFIFOW(fd, 0) = 0x3893; + WFIFOB(fd,2) = flag; //Flag 1 = success + WFIFOSET(fd, 3); +} + +static void mapif_homunculus_loaded(int fd, int account_id, struct s_homunculus *hd) +{ + WFIFOHEAD(fd, sizeof(struct s_homunculus)+9); + WFIFOW(fd,0) = 0x3891; + WFIFOW(fd,2) = sizeof(struct s_homunculus)+9; + WFIFOL(fd,4) = account_id; + if( hd != NULL ) + { + WFIFOB(fd,8) = 1; // success + memcpy(WFIFOP(fd,9), hd, sizeof(struct s_homunculus)); + } + else + { + WFIFOB(fd,8) = 0; // not found. + memset(WFIFOP(fd,9), 0, sizeof(struct s_homunculus)); + } + WFIFOSET(fd, sizeof(struct s_homunculus)+9); +} + +static void mapif_homunculus_saved(int fd, int account_id, bool flag) +{ + WFIFOHEAD(fd, 7); + WFIFOW(fd,0) = 0x3892; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = flag; // 1:success, 0:failure + WFIFOSET(fd, 7); +} + +static void mapif_homunculus_renamed(int fd, int account_id, int char_id, unsigned char 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; + safestrncpy((char*)WFIFOP(fd,11), name, NAME_LENGTH); + WFIFOSET(fd, NAME_LENGTH+12); +} + +bool mapif_homunculus_save(struct s_homunculus* hd) +{ + bool flag = true; + char esc_name[NAME_LENGTH*2+1]; + + Sql_EscapeStringLen(sql_handle, esc_name, hd->name, strnlen(hd->name, NAME_LENGTH)); + + if( hd->hom_id == 0 ) + {// new homunculus + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` " + "(`char_id`, `class`,`prev_class`,`name`,`level`,`exp`,`intimacy`,`hunger`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `hp`,`max_hp`,`sp`,`max_sp`,`skill_point`, `rename_flag`, `vaporize`) " + "VALUES ('%d', '%d', '%d', '%s', '%d', '%u', '%u', '%d', '%d', %d, '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + homunculus_db, hd->char_id, hd->class_, hd->prev_class, esc_name, hd->level, hd->exp, hd->intimacy, hd->hunger, hd->str, hd->agi, hd->vit, hd->int_, hd->dex, hd->luk, + hd->hp, hd->max_hp, hd->sp, hd->max_sp, hd->skillpts, hd->rename_flag, hd->vaporize) ) + { + Sql_ShowDebug(sql_handle); + flag = false; + } + else + { + hd->hom_id = (int)Sql_LastInsertId(sql_handle); + } + } + else + { + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `char_id`='%d', `class`='%d',`prev_class`='%d',`name`='%s',`level`='%d',`exp`='%u',`intimacy`='%u',`hunger`='%d', `str`='%d', `agi`='%d', `vit`='%d', `int`='%d', `dex`='%d', `luk`='%d', `hp`='%d',`max_hp`='%d',`sp`='%d',`max_sp`='%d',`skill_point`='%d', `rename_flag`='%d', `vaporize`='%d' WHERE `homun_id`='%d'", + homunculus_db, hd->char_id, hd->class_, hd->prev_class, esc_name, hd->level, hd->exp, hd->intimacy, hd->hunger, hd->str, hd->agi, hd->vit, hd->int_, hd->dex, hd->luk, + hd->hp, hd->max_hp, hd->sp, hd->max_sp, hd->skillpts, hd->rename_flag, hd->vaporize, hd->hom_id) ) + { + Sql_ShowDebug(sql_handle); + flag = false; + } + else + { + SqlStmt* stmt; + int i; + + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_ERROR == SqlStmt_Prepare(stmt, "REPLACE INTO `%s` (`homun_id`, `id`, `lv`) VALUES (%d, ?, ?)", skill_homunculus_db, hd->hom_id) ) + SqlStmt_ShowDebug(stmt); + for( i = 0; i < MAX_HOMUNSKILL; ++i ) + { + if( hd->hskill[i].id > 0 && hd->hskill[i].lv != 0 ) + { + SqlStmt_BindParam(stmt, 0, SQLDT_USHORT, &hd->hskill[i].id, 0); + SqlStmt_BindParam(stmt, 1, SQLDT_USHORT, &hd->hskill[i].lv, 0); + if( SQL_ERROR == SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + flag = false; + break; + } + } + } + SqlStmt_Free(stmt); + } + } + + return flag; +} + + + +// Load an homunculus +bool mapif_homunculus_load(int homun_id, struct s_homunculus* hd) +{ + int i; + char* data; + size_t len; + + memset(hd, 0, sizeof(*hd)); + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `homun_id`,`char_id`,`class`,`prev_class`,`name`,`level`,`exp`,`intimacy`,`hunger`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `hp`,`max_hp`,`sp`,`max_sp`,`skill_point`,`rename_flag`, `vaporize` FROM `%s` WHERE `homun_id`='%u'", homunculus_db, homun_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( !Sql_NumRows(sql_handle) ) + { //No homunculus found. + Sql_FreeResult(sql_handle); + return false; + } + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_ShowDebug(sql_handle); + Sql_FreeResult(sql_handle); + return false; + } + + hd->hom_id = homun_id; + Sql_GetData(sql_handle, 1, &data, NULL); hd->char_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); hd->class_ = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); hd->prev_class = atoi(data); + Sql_GetData(sql_handle, 4, &data, &len); safestrncpy(hd->name, data, sizeof(hd->name)); + Sql_GetData(sql_handle, 5, &data, NULL); hd->level = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); hd->exp = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); hd->intimacy = (unsigned int)strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 8, &data, NULL); hd->hunger = atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); hd->str = atoi(data); + Sql_GetData(sql_handle, 10, &data, NULL); hd->agi = atoi(data); + Sql_GetData(sql_handle, 11, &data, NULL); hd->vit = atoi(data); + Sql_GetData(sql_handle, 12, &data, NULL); hd->int_ = atoi(data); + Sql_GetData(sql_handle, 13, &data, NULL); hd->dex = atoi(data); + Sql_GetData(sql_handle, 14, &data, NULL); hd->luk = atoi(data); + Sql_GetData(sql_handle, 15, &data, NULL); hd->hp = atoi(data); + Sql_GetData(sql_handle, 16, &data, NULL); hd->max_hp = atoi(data); + Sql_GetData(sql_handle, 17, &data, NULL); hd->sp = atoi(data); + Sql_GetData(sql_handle, 18, &data, NULL); hd->max_sp = atoi(data); + Sql_GetData(sql_handle, 19, &data, NULL); hd->skillpts = atoi(data); + Sql_GetData(sql_handle, 20, &data, NULL); hd->rename_flag = atoi(data); + Sql_GetData(sql_handle, 21, &data, NULL); hd->vaporize = atoi(data); + Sql_FreeResult(sql_handle); + + hd->intimacy = cap_value(hd->intimacy, 0, 100000); + hd->hunger = cap_value(hd->hunger, 0, 100); + + // Load Homunculus Skill + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`lv` FROM `%s` WHERE `homun_id`=%d", skill_homunculus_db, homun_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + // id + Sql_GetData(sql_handle, 0, &data, NULL); + i = atoi(data); + if( i < HM_SKILLBASE || i >= HM_SKILLBASE + MAX_HOMUNSKILL ) + continue;// invalid skill id + i = i - HM_SKILLBASE; + hd->hskill[i].id = (unsigned short)atoi(data); + + // lv + Sql_GetData(sql_handle, 1, &data, NULL); + hd->hskill[i].lv = (unsigned char)atoi(data); + } + Sql_FreeResult(sql_handle); + + if( save_log ) + ShowInfo("Homunculus loaded (%d - %s).\n", hd->hom_id, hd->name); + + return true; +} + +bool mapif_homunculus_delete(int homun_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id` = '%u'", homunculus_db, homun_id) + || SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id` = '%u'", skill_homunculus_db, homun_id) + ) { + Sql_ShowDebug(sql_handle); + return false; + } + return true; +} + +bool mapif_homunculus_rename(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 ) + return false; + } 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 false; + } + + return true; +} + + +static void mapif_parse_homunculus_create(int fd, int len, int account_id, struct s_homunculus* phd) +{ + bool result = mapif_homunculus_save(phd); + mapif_homunculus_created(fd, account_id, phd, result); +} + +static void mapif_parse_homunculus_delete(int fd, int homun_id) +{ + bool result = mapif_homunculus_delete(homun_id); + mapif_homunculus_deleted(fd, result); +} + +static void mapif_parse_homunculus_load(int fd, int account_id, int homun_id) +{ + struct s_homunculus hd; + bool result = mapif_homunculus_load(homun_id, &hd); + mapif_homunculus_loaded(fd, account_id, ( result ? &hd : NULL )); +} + +static void mapif_parse_homunculus_save(int fd, int len, int account_id, struct s_homunculus* phd) +{ + bool result = mapif_homunculus_save(phd); + mapif_homunculus_saved(fd, account_id, result); +} + +static void mapif_parse_homunculus_rename(int fd, int account_id, int char_id, char* name) +{ + bool result = mapif_homunculus_rename(name); + mapif_homunculus_renamed(fd, account_id, char_id, result, name); +} + +/*========================================== + * Inter Packets + *------------------------------------------*/ +int inter_homunculus_parse_frommap(int fd) +{ + unsigned short cmd = RFIFOW(fd,0); + + switch( cmd ) + { + case 0x3090: mapif_parse_homunculus_create(fd, (int)RFIFOW(fd,2), (int)RFIFOL(fd,4), (struct s_homunculus*)RFIFOP(fd,8)); break; + case 0x3091: mapif_parse_homunculus_load (fd, (int)RFIFOL(fd,2), (int)RFIFOL(fd,6)); break; + case 0x3092: mapif_parse_homunculus_save (fd, (int)RFIFOW(fd,2), (int)RFIFOL(fd,4), (struct s_homunculus*)RFIFOP(fd,8)); break; + case 0x3093: mapif_parse_homunculus_delete(fd, (int)RFIFOL(fd,2)); break; + case 0x3094: mapif_parse_homunculus_rename(fd, (int)RFIFOL(fd,2), (int)RFIFOL(fd,6), (char*)RFIFOP(fd,10)); 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..1c0d76269 --- /dev/null +++ b/src/char/int_homun.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_HOMUN_SQL_H_ +#define _INT_HOMUN_SQL_H_ + +struct s_homunculus; + +int inter_homunculus_sql_init(void); +void inter_homunculus_sql_final(void); +int inter_homunculus_parse_frommap(int fd); + +bool mapif_homunculus_save(struct s_homunculus* hd); +bool mapif_homunculus_load(int homun_id, struct s_homunculus* hd); +bool mapif_homunculus_delete(int homun_id); +bool mapif_homunculus_rename(char *name); + +#endif /* _INT_HOMUN_SQL_H_ */ diff --git a/src/char/int_mail.c b/src/char/int_mail.c new file mode 100644 index 000000000..8d50c713f --- /dev/null +++ b/src/char/int_mail.c @@ -0,0 +1,483 @@ +// 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/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/sql.h" +#include "../common/timer.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static int mail_fromsql(int char_id, struct mail_data* md) +{ + int i, j; + struct mail_message *msg; + struct item *item; + char *data; + StringBuf buf; + + memset(md, 0, sizeof(struct mail_data)); + md->amount = 0; + md->full = false; + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,`title`,`message`,`time`,`status`," + "`zeny`,`amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`"); + for (i = 0; i < MAX_SLOTS; i++) + StringBuf_Printf(&buf, ",`card%d`", i); + + // I keep the `status` < 3 just in case someone forget to apply the sqlfix + StringBuf_Printf(&buf, " FROM `%s` WHERE `dest_id`='%d' AND `status` < 3 ORDER BY `id` LIMIT %d", + mail_db, char_id, MAIL_MAX_INBOX + 1); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + + StringBuf_Destroy(&buf); + + for (i = 0; i < MAIL_MAX_INBOX && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + msg = &md->msg[i]; + Sql_GetData(sql_handle, 0, &data, NULL); msg->id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(msg->send_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 2, &data, NULL); msg->send_id = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(msg->dest_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 4, &data, NULL); msg->dest_id = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); safestrncpy(msg->title, data, MAIL_TITLE_LENGTH); + Sql_GetData(sql_handle, 6, &data, NULL); safestrncpy(msg->body, data, MAIL_BODY_LENGTH); + Sql_GetData(sql_handle, 7, &data, NULL); msg->timestamp = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); msg->status = (mail_status)atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); msg->zeny = atoi(data); + item = &msg->item; + Sql_GetData(sql_handle,10, &data, NULL); item->amount = (short)atoi(data); + Sql_GetData(sql_handle,11, &data, NULL); item->nameid = atoi(data); + Sql_GetData(sql_handle,12, &data, NULL); item->refine = atoi(data); + Sql_GetData(sql_handle,13, &data, NULL); item->attribute = atoi(data); + Sql_GetData(sql_handle,14, &data, NULL); item->identify = atoi(data); + Sql_GetData(sql_handle,15, &data, NULL); item->unique_id = strtoull(data, NULL, 10); + item->expire_time = 0; + + for (j = 0; j < MAX_SLOTS; j++) + { + Sql_GetData(sql_handle, 16 + j, &data, NULL); + item->card[j] = atoi(data); + } + } + + md->full = ( Sql_NumRows(sql_handle) > MAIL_MAX_INBOX ); + + md->amount = i; + Sql_FreeResult(sql_handle); + + md->unchecked = 0; + md->unread = 0; + for (i = 0; i < md->amount; i++) + { + msg = &md->msg[i]; + if( msg->status == MAIL_NEW ) + { + if ( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `status` = '%d' WHERE `id` = '%d'", mail_db, MAIL_UNREAD, msg->id) ) + Sql_ShowDebug(sql_handle); + + msg->status = MAIL_UNREAD; + md->unchecked++; + } + else if ( msg->status == MAIL_UNREAD ) + md->unread++; + } + + ShowInfo("mail load complete from DB - id: %d (total: %d)\n", char_id, md->amount); + return 1; +} + +/// Stores a single message in the database. +/// Returns the message's ID if successful (or 0 if it fails). +int mail_savemessage(struct mail_message* msg) +{ + StringBuf buf; + SqlStmt* stmt; + int j; + + // build message save query + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`send_name`, `send_id`, `dest_name`, `dest_id`, `title`, `message`, `time`, `status`, `zeny`, `amount`, `nameid`, `refine`, `attribute`, `identify`, `unique_id`", mail_db); + for (j = 0; j < MAX_SLOTS; j++) + StringBuf_Printf(&buf, ", `card%d`", j); + StringBuf_Printf(&buf, ") VALUES (?, '%d', ?, '%d', ?, ?, '%lu', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%"PRIu64"'", + msg->send_id, msg->dest_id, (unsigned long)msg->timestamp, msg->status, msg->zeny, msg->item.amount, msg->item.nameid, msg->item.refine, msg->item.attribute, msg->item.identify, msg->item.unique_id); + for (j = 0; j < MAX_SLOTS; j++) + StringBuf_Printf(&buf, ", '%d'", msg->item.card[j]); + StringBuf_AppendStr(&buf, ")"); + + //Unique Non Stackable Item ID + updateLastUid(msg->item.unique_id); + dbUpdateUid(sql_handle); + + // prepare and execute query + stmt = SqlStmt_Malloc(sql_handle); + if( SQL_SUCCESS != SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, msg->send_name, strnlen(msg->send_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, msg->dest_name, strnlen(msg->dest_name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, msg->title, strnlen(msg->title, MAIL_TITLE_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, msg->body, strnlen(msg->body, MAIL_BODY_LENGTH)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + msg->id = 0; + } else + msg->id = (int)SqlStmt_LastInsertId(stmt); + + SqlStmt_Free(stmt); + StringBuf_Destroy(&buf); + + return msg->id; +} + +/// Retrieves a single message from the database. +/// Returns true if the operation succeeds (or false if it fails). +static bool mail_loadmessage(int mail_id, struct mail_message* msg) +{ + int j; + StringBuf buf; + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,`title`,`message`,`time`,`status`," + "`zeny`,`amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`"); + for( j = 0; j < MAX_SLOTS; j++ ) + StringBuf_Printf(&buf, ",`card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `id` = '%d'", mail_db, mail_id); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) + || SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_ShowDebug(sql_handle); + Sql_FreeResult(sql_handle); + StringBuf_Destroy(&buf); + return false; + } + else + { + char* data; + + Sql_GetData(sql_handle, 0, &data, NULL); msg->id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(msg->send_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 2, &data, NULL); msg->send_id = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(msg->dest_name, data, NAME_LENGTH); + Sql_GetData(sql_handle, 4, &data, NULL); msg->dest_id = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); safestrncpy(msg->title, data, MAIL_TITLE_LENGTH); + Sql_GetData(sql_handle, 6, &data, NULL); safestrncpy(msg->body, data, MAIL_BODY_LENGTH); + Sql_GetData(sql_handle, 7, &data, NULL); msg->timestamp = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); msg->status = (mail_status)atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); msg->zeny = atoi(data); + Sql_GetData(sql_handle,10, &data, NULL); msg->item.amount = (short)atoi(data); + Sql_GetData(sql_handle,11, &data, NULL); msg->item.nameid = atoi(data); + Sql_GetData(sql_handle,12, &data, NULL); msg->item.refine = atoi(data); + Sql_GetData(sql_handle,13, &data, NULL); msg->item.attribute = atoi(data); + Sql_GetData(sql_handle,14, &data, NULL); msg->item.identify = atoi(data); + Sql_GetData(sql_handle,15, &data, NULL); msg->item.unique_id = strtoull(data, NULL, 10); + msg->item.expire_time = 0; + + for( j = 0; j < MAX_SLOTS; j++ ) + { + Sql_GetData(sql_handle,16 + j, &data, NULL); + msg->item.card[j] = atoi(data); + } + } + + StringBuf_Destroy(&buf); + Sql_FreeResult(sql_handle); + + return true; +} + +/*========================================== + * Client Inbox Request + *------------------------------------------*/ +static void mapif_Mail_sendinbox(int fd, int char_id, unsigned char flag) +{ + struct mail_data md; + mail_fromsql(char_id, &md); + + //FIXME: dumping the whole structure like this is unsafe [ultramage] + WFIFOHEAD(fd, sizeof(md) + 9); + WFIFOW(fd,0) = 0x3848; + WFIFOW(fd,2) = sizeof(md) + 9; + WFIFOL(fd,4) = char_id; + WFIFOB(fd,8) = flag; + memcpy(WFIFOP(fd,9),&md,sizeof(md)); + WFIFOSET(fd,WFIFOW(fd,2)); +} + +static void mapif_parse_Mail_requestinbox(int fd) +{ + mapif_Mail_sendinbox(fd, RFIFOL(fd,2), RFIFOB(fd,6)); +} + +/*========================================== + * Mark mail as 'Read' + *------------------------------------------*/ +static void mapif_parse_Mail_read(int fd) +{ + int mail_id = RFIFOL(fd,2); + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `status` = '%d' WHERE `id` = '%d'", mail_db, MAIL_READ, mail_id) ) + Sql_ShowDebug(sql_handle); +} + +/*========================================== + * Client Attachment Request + *------------------------------------------*/ +static bool mail_DeleteAttach(int mail_id) +{ + StringBuf buf; + int i; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "UPDATE `%s` SET `zeny` = '0', `nameid` = '0', `amount` = '0', `refine` = '0', `attribute` = '0', `identify` = '0'", mail_db); + for (i = 0; i < MAX_SLOTS; i++) + StringBuf_Printf(&buf, ", `card%d` = '0'", i); + StringBuf_Printf(&buf, " WHERE `id` = '%d'", mail_id); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ) + { + Sql_ShowDebug(sql_handle); + StringBuf_Destroy(&buf); + + return false; + } + + StringBuf_Destroy(&buf); + return true; +} + +static void mapif_Mail_getattach(int fd, int char_id, int mail_id) +{ + struct mail_message msg; + + if( !mail_loadmessage(mail_id, &msg) ) + return; + + if( msg.dest_id != char_id ) + return; + + if( msg.status != MAIL_READ ) + return; + + if( (msg.item.nameid < 1 || msg.item.amount < 1) && msg.zeny < 1 ) + return; // No Attachment + + if( !mail_DeleteAttach(mail_id) ) + return; + + WFIFOHEAD(fd, sizeof(struct item) + 12); + WFIFOW(fd,0) = 0x384a; + WFIFOW(fd,2) = sizeof(struct item) + 12; + WFIFOL(fd,4) = char_id; + WFIFOL(fd,8) = (msg.zeny > 0)?msg.zeny:0; + memcpy(WFIFOP(fd,12), &msg.item, sizeof(struct item)); + WFIFOSET(fd,WFIFOW(fd,2)); +} + +static void mapif_parse_Mail_getattach(int fd) +{ + mapif_Mail_getattach(fd, RFIFOL(fd,2), RFIFOL(fd,6)); +} + +/*========================================== + * Delete Mail + *------------------------------------------*/ +static void mapif_Mail_delete(int fd, int char_id, int mail_id) +{ + bool failed = false; + if ( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", mail_db, mail_id) ) + { + Sql_ShowDebug(sql_handle); + failed = true; + } + + WFIFOHEAD(fd,11); + WFIFOW(fd,0) = 0x384b; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = mail_id; + WFIFOB(fd,10) = failed; + WFIFOSET(fd,11); +} + +static void mapif_parse_Mail_delete(int fd) +{ + mapif_Mail_delete(fd, RFIFOL(fd,2), RFIFOL(fd,6)); +} + +/*========================================== + * Report New Mail to Map Server + *------------------------------------------*/ +void mapif_Mail_new(struct mail_message *msg) +{ + unsigned char buf[74]; + + if( !msg || !msg->id ) + return; + + WBUFW(buf,0) = 0x3849; + WBUFL(buf,2) = msg->dest_id; + WBUFL(buf,6) = msg->id; + memcpy(WBUFP(buf,10), msg->send_name, NAME_LENGTH); + memcpy(WBUFP(buf,34), msg->title, MAIL_TITLE_LENGTH); + mapif_sendall(buf, 74); +} + +/*========================================== + * Return Mail + *------------------------------------------*/ +static void mapif_Mail_return(int fd, int char_id, int mail_id) +{ + struct mail_message msg; + int new_mail = 0; + + if( mail_loadmessage(mail_id, &msg) ) + { + if( msg.dest_id != char_id) + return; + else if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", mail_db, mail_id) ) + Sql_ShowDebug(sql_handle); + else + { + char temp_[MAIL_TITLE_LENGTH]; + + // swap sender and receiver + swap(msg.send_id, msg.dest_id); + safestrncpy(temp_, msg.send_name, NAME_LENGTH); + safestrncpy(msg.send_name, msg.dest_name, NAME_LENGTH); + safestrncpy(msg.dest_name, temp_, NAME_LENGTH); + + // set reply message title + snprintf(temp_, MAIL_TITLE_LENGTH, "RE:%s", msg.title); + safestrncpy(msg.title, temp_, MAIL_TITLE_LENGTH); + + msg.status = MAIL_NEW; + msg.timestamp = time(NULL); + + new_mail = mail_savemessage(&msg); + mapif_Mail_new(&msg); + } + } + + WFIFOHEAD(fd,11); + WFIFOW(fd,0) = 0x384c; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = mail_id; + WFIFOB(fd,10) = (new_mail == 0); + WFIFOSET(fd,11); +} + +static void mapif_parse_Mail_return(int fd) +{ + mapif_Mail_return(fd, RFIFOL(fd,2), RFIFOL(fd,6)); +} + +/*========================================== + * Send Mail + *------------------------------------------*/ +static void mapif_Mail_send(int fd, struct mail_message* msg) +{ + int len = sizeof(struct mail_message) + 4; + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x384d; + WFIFOW(fd,2) = len; + memcpy(WFIFOP(fd,4), msg, sizeof(struct mail_message)); + WFIFOSET(fd,len); +} + +static void mapif_parse_Mail_send(int fd) +{ + struct mail_message msg; + char esc_name[NAME_LENGTH*2+1]; + int account_id = 0; + + if(RFIFOW(fd,2) != 8 + sizeof(struct mail_message)) + return; + + account_id = RFIFOL(fd,4); + memcpy(&msg, RFIFOP(fd,8), sizeof(struct mail_message)); + + // Try to find the Dest Char by Name + Sql_EscapeStringLen(sql_handle, esc_name, msg.dest_name, strnlen(msg.dest_name, NAME_LENGTH)); + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`, `char_id` FROM `%s` WHERE `name` = '%s'", char_db, esc_name) ) + Sql_ShowDebug(sql_handle); + else + if ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char *data; + Sql_GetData(sql_handle, 0, &data, NULL); + if (atoi(data) != account_id) + { // Cannot send mail to char in the same account + Sql_GetData(sql_handle, 1, &data, NULL); + msg.dest_id = atoi(data); + } + } + Sql_FreeResult(sql_handle); + msg.status = MAIL_NEW; + + if( msg.dest_id > 0 ) + msg.id = mail_savemessage(&msg); + + mapif_Mail_send(fd, &msg); // notify sender + mapif_Mail_new(&msg); // notify recipient +} + +void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item) +{ + struct mail_message msg; + memset(&msg, 0, sizeof(struct mail_message)); + + msg.send_id = send_id; + safestrncpy(msg.send_name, send_name, NAME_LENGTH); + msg.dest_id = dest_id; + safestrncpy(msg.dest_name, dest_name, NAME_LENGTH); + safestrncpy(msg.title, title, MAIL_TITLE_LENGTH); + safestrncpy(msg.body, body, MAIL_BODY_LENGTH); + msg.zeny = zeny; + if( item != NULL ) + memcpy(&msg.item, item, sizeof(struct item)); + + msg.timestamp = time(NULL); + + mail_savemessage(&msg); + mapif_Mail_new(&msg); +} + +/*========================================== + * Packets From Map Server + *------------------------------------------*/ +int inter_mail_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)) + { + case 0x3048: mapif_parse_Mail_requestinbox(fd); break; + case 0x3049: mapif_parse_Mail_read(fd); break; + case 0x304a: mapif_parse_Mail_getattach(fd); break; + case 0x304b: mapif_parse_Mail_delete(fd); break; + case 0x304c: mapif_parse_Mail_return(fd); break; + case 0x304d: mapif_parse_Mail_send(fd); break; + default: + return 0; + } + return 1; +} + +int inter_mail_sql_init(void) +{ + return 1; +} + +void inter_mail_sql_final(void) +{ + return; +} diff --git a/src/char/int_mail.h b/src/char/int_mail.h new file mode 100644 index 000000000..77db51e5b --- /dev/null +++ b/src/char/int_mail.h @@ -0,0 +1,16 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_MAIL_SQL_H_ +#define _INT_MAIL_SQL_H_ + +int inter_mail_parse_frommap(int fd); +void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item); + +int inter_mail_sql_init(void); +void inter_mail_sql_final(void); + +int mail_savemessage(struct mail_message* msg); +void mapif_Mail_new(struct mail_message *msg); + +#endif /* _INT_MAIL_SQL_H_ */ diff --git a/src/char/int_mercenary.c b/src/char/int_mercenary.c new file mode 100644 index 000000000..3b3714416 --- /dev/null +++ b/src/char/int_mercenary.c @@ -0,0 +1,218 @@ +// 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/strlib.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/utils.h" +#include "../common/sql.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +bool mercenary_owner_fromsql(int char_id, struct mmo_charstatus *status) +{ + char* data; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `merc_id`, `arch_calls`, `arch_faith`, `spear_calls`, `spear_faith`, `sword_calls`, `sword_faith` FROM `%s` WHERE `char_id` = '%d'", mercenary_owner_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, NULL); status->mer_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); status->arch_calls = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); status->arch_faith = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); status->spear_calls = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); status->spear_faith = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); status->sword_calls = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); status->sword_faith = atoi(data); + Sql_FreeResult(sql_handle); + + return true; +} + +bool mercenary_owner_tosql(int char_id, struct mmo_charstatus *status) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`char_id`, `merc_id`, `arch_calls`, `arch_faith`, `spear_calls`, `spear_faith`, `sword_calls`, `sword_faith`) VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + mercenary_owner_db, char_id, status->mer_id, status->arch_calls, status->arch_faith, status->spear_calls, status->spear_faith, status->sword_calls, status->sword_faith) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +bool mercenary_owner_delete(int char_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", mercenary_owner_db, char_id) ) + Sql_ShowDebug(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", mercenary_db, char_id) ) + Sql_ShowDebug(sql_handle); + + return true; +} + +bool mapif_mercenary_save(struct s_mercenary* merc) +{ + bool flag = true; + + if( merc->mercenary_id == 0 ) + { // Create new DB entry + if( SQL_ERROR == Sql_Query(sql_handle, + "INSERT INTO `%s` (`char_id`,`class`,`hp`,`sp`,`kill_counter`,`life_time`) VALUES ('%d','%d','%d','%d','%u','%u')", + mercenary_db, merc->char_id, merc->class_, merc->hp, merc->sp, merc->kill_count, merc->life_time) ) + { + Sql_ShowDebug(sql_handle); + flag = false; + } + else + merc->mercenary_id = (int)Sql_LastInsertId(sql_handle); + } + else if( SQL_ERROR == Sql_Query(sql_handle, + "UPDATE `%s` SET `char_id` = '%d', `class` = '%d', `hp` = '%d', `sp` = '%d', `kill_counter` = '%u', `life_time` = '%u' WHERE `mer_id` = '%d'", + mercenary_db, merc->char_id, merc->class_, merc->hp, merc->sp, merc->kill_count, merc->life_time, merc->mercenary_id) ) + { // Update DB entry + Sql_ShowDebug(sql_handle); + flag = false; + } + + return flag; +} + +bool mapif_mercenary_load(int merc_id, int char_id, struct s_mercenary *merc) +{ + char* data; + + memset(merc, 0, sizeof(struct s_mercenary)); + merc->mercenary_id = merc_id; + merc->char_id = char_id; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `class`, `hp`, `sp`, `kill_counter`, `life_time` FROM `%s` WHERE `mer_id` = '%d' AND `char_id` = '%d'", mercenary_db, merc_id, char_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, NULL); merc->class_ = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); merc->hp = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); merc->sp = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); merc->kill_count = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); merc->life_time = atoi(data); + Sql_FreeResult(sql_handle); + if( save_log ) + ShowInfo("Mercenary loaded (%d - %d).\n", merc->mercenary_id, merc->char_id); + + return true; +} + +bool mapif_mercenary_delete(int merc_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `mer_id` = '%d'", mercenary_db, merc_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +static void mapif_mercenary_send(int fd, struct s_mercenary *merc, unsigned char flag) +{ + int size = sizeof(struct s_mercenary) + 5; + + WFIFOHEAD(fd,size); + WFIFOW(fd,0) = 0x3870; + WFIFOW(fd,2) = size; + WFIFOB(fd,4) = flag; + memcpy(WFIFOP(fd,5),merc,sizeof(struct s_mercenary)); + WFIFOSET(fd,size); +} + +static void mapif_parse_mercenary_create(int fd, struct s_mercenary* merc) +{ + bool result = mapif_mercenary_save(merc); + mapif_mercenary_send(fd, merc, result); +} + +static void mapif_parse_mercenary_load(int fd, int merc_id, int char_id) +{ + struct s_mercenary merc; + bool result = mapif_mercenary_load(merc_id, char_id, &merc); + mapif_mercenary_send(fd, &merc, result); +} + +static void mapif_mercenary_deleted(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x3871; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,3); +} + +static void mapif_parse_mercenary_delete(int fd, int merc_id) +{ + bool result = mapif_mercenary_delete(merc_id); + mapif_mercenary_deleted(fd, result); +} + +static void mapif_mercenary_saved(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x3872; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,3); +} + +static void mapif_parse_mercenary_save(int fd, struct s_mercenary* merc) +{ + bool result = mapif_mercenary_save(merc); + mapif_mercenary_saved(fd, result); +} + +int inter_mercenary_sql_init(void) +{ + return 0; +} +void inter_mercenary_sql_final(void) +{ + return; +} + +/*========================================== + * Inter Packets + *------------------------------------------*/ +int inter_mercenary_parse_frommap(int fd) +{ + unsigned short cmd = RFIFOW(fd,0); + + switch( cmd ) + { + case 0x3070: mapif_parse_mercenary_create(fd, (struct s_mercenary*)RFIFOP(fd,4)); break; + case 0x3071: mapif_parse_mercenary_load(fd, (int)RFIFOL(fd,2), (int)RFIFOL(fd,6)); break; + case 0x3072: mapif_parse_mercenary_delete(fd, (int)RFIFOL(fd,2)); break; + case 0x3073: mapif_parse_mercenary_save(fd, (struct s_mercenary*)RFIFOP(fd,4)); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_mercenary.h b/src/char/int_mercenary.h new file mode 100644 index 000000000..01e4a841f --- /dev/null +++ b/src/char/int_mercenary.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_MERCENARY_SQL_H_ +#define _INT_MERCENARY_SQL_H_ + +struct s_mercenary; + +int inter_mercenary_sql_init(void); +void inter_mercenary_sql_final(void); +int inter_mercenary_parse_frommap(int fd); + +// Mercenary Owner Database +bool mercenary_owner_fromsql(int char_id, struct mmo_charstatus *status); +bool mercenary_owner_tosql(int char_id, struct mmo_charstatus *status); +bool mercenary_owner_delete(int char_id); + +bool mapif_mercenary_delete(int merc_id); + +#endif /* _INT_MERCENARY_SQL_H_ */ diff --git a/src/char/int_party.c b/src/char/int_party.c new file mode 100644 index 000000000..a88e5c586 --- /dev/null +++ b/src/char/int_party.c @@ -0,0 +1,863 @@ +// 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/malloc.h" +#include "../common/strlib.h" +#include "../common/socket.h" +#include "../common/showmsg.h" +#include "../common/mapindex.h" +#include "../common/sql.h" +#include "char.h" +#include "inter.h" +#include "int_party.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +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 struct party_data *party_pt; +static DBMap* party_db_; // int party_id -> struct party_data* + +int mapif_party_broken(int party_id,int flag); +int party_check_empty(struct party_data *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 not online OR if it's a family party and this is the child (doesn't affect exp range) + **/ + if(!p->party.member[i].online || p->party.member[i].char_id == p->family ) + 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 == 2 && ( char_child(p->party.member[0].char_id,p->party.member[1].char_id) || char_child(p->party.member[1].char_id,p->party.member[0].char_id) ) ) { + //Child should be able to share with either of their parents [RoM] + if(p->party.member[0].class_&0x2000) //first slot is the child? + p->family = p->party.member[0].char_id; + else + p->family = p->party.member[1].char_id; + } else 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; +} + +// Save party to mysql +int inter_party_tosql(struct party *p, int flag, int index) +{ + // 'party' ('party_id','name','exp','item','leader_id','leader_char') + char esc_name[NAME_LENGTH*2+1];// escaped party name + int party_id; + + if( p == NULL || p->party_id == 0 ) + return 0; + party_id = p->party_id; + +#ifdef NOISY + ShowInfo("Save party request ("CL_BOLD"%d"CL_RESET" - %s).\n", party_id, p->name); +#endif + Sql_EscapeStringLen(sql_handle, esc_name, p->name, strnlen(p->name, NAME_LENGTH)); + + if( flag & PS_BREAK ) + {// Break the party + // we'll skip name-checking and just reset everyone with the same party id [celest] + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id) ) + Sql_ShowDebug(sql_handle); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `party_id`='%d'", party_db, party_id) ) + Sql_ShowDebug(sql_handle); + //Remove from memory + idb_remove(party_db_, party_id); + return 1; + } + + if( flag & PS_CREATE ) + {// Create party + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` " + "(`name`, `exp`, `item`, `leader_id`, `leader_char`) " + "VALUES ('%s', '%d', '%d', '%d', '%d')", + party_db, esc_name, p->exp, p->item, p->member[index].account_id, p->member[index].char_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + party_id = p->party_id = (int)Sql_LastInsertId(sql_handle); + } + + if( flag & PS_BASIC ) + {// Update party info. + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `name`='%s', `exp`='%d', `item`='%d' WHERE `party_id`='%d'", + party_db, esc_name, p->exp, p->item, party_id) ) + Sql_ShowDebug(sql_handle); + } + + if( flag & PS_LEADER ) + {// Update leader + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `leader_id`='%d', `leader_char`='%d' WHERE `party_id`='%d'", + party_db, p->member[index].account_id, p->member[index].char_id, party_id) ) + Sql_ShowDebug(sql_handle); + } + + if( flag & PS_ADDMEMBER ) + {// Add one party member. + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `party_id`='%d' WHERE `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id) ) + Sql_ShowDebug(sql_handle); + } + + if( flag & PS_DELMEMBER ) + {// Remove one party member. + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d' AND `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id) ) + Sql_ShowDebug(sql_handle); + } + + if( save_log ) + ShowInfo("Party Saved (%d - %s)\n", party_id, p->name); + return 1; +} + +// Read party from mysql +struct party_data *inter_party_fromsql(int party_id) +{ + int leader_id = 0; + int leader_char = 0; + struct party_data* p; + struct party_member* m; + char* data; + size_t len; + int i; + +#ifdef NOISY + ShowInfo("Load party request ("CL_BOLD"%d"CL_RESET")\n", party_id); +#endif + if( party_id <= 0 ) + return NULL; + + //Load from memory + p = (struct party_data*)idb_get(party_db_, party_id); + if( p != NULL ) + return p; + + p = party_pt; + memset(p, 0, sizeof(struct party_data)); + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `party_id`, `name`,`exp`,`item`, `leader_id`, `leader_char` FROM `%s` WHERE `party_id`='%d'", party_db, party_id) ) + { + Sql_ShowDebug(sql_handle); + return NULL; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + return NULL; + + p->party.party_id = party_id; + Sql_GetData(sql_handle, 1, &data, &len); memcpy(p->party.name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 2, &data, NULL); p->party.exp = (atoi(data) ? 1 : 0); + Sql_GetData(sql_handle, 3, &data, NULL); p->party.item = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); leader_id = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); leader_char = atoi(data); + Sql_FreeResult(sql_handle); + + // Load members + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`char_id`,`name`,`base_level`,`last_map`,`online`,`class` FROM `%s` WHERE `party_id`='%d'", char_db, party_id) ) + { + Sql_ShowDebug(sql_handle); + return NULL; + } + for( i = 0; i < MAX_PARTY && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + m = &p->party.member[i]; + Sql_GetData(sql_handle, 0, &data, NULL); m->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); m->char_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, &len); memcpy(m->name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 3, &data, NULL); m->lv = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); m->map = mapindex_name2id(data); + Sql_GetData(sql_handle, 5, &data, NULL); m->online = (atoi(data) ? 1 : 0); + Sql_GetData(sql_handle, 6, &data, NULL); m->class_ = atoi(data); + m->leader = (m->account_id == leader_id && m->char_id == leader_char ? 1 : 0); + } + Sql_FreeResult(sql_handle); + + if( save_log ) + ShowInfo("Party loaded (%d - %s).\n", party_id, p->party.name); + //Add party to memory. + CREATE(p, struct party_data, 1); + memcpy(p, party_pt, sizeof(struct party_data)); + //init state + int_party_calc_state(p); + idb_put(party_db_, party_id, p); + return p; +} + +int inter_party_sql_init(void) +{ + //memory alloc + party_db_ = idb_alloc(DB_OPT_RELEASE_DATA); + party_pt = (struct party_data*)aCalloc(sizeof(struct party_data), 1); + if (!party_pt) { + ShowFatalError("inter_party_sql_init: Out of Memory!\n"); + exit(EXIT_FAILURE); + } + + /* Uncomment the following if you want to do a party_db cleanup (remove parties with no members) on startup.[Skotlex] + ShowStatus("cleaning party table...\n"); + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` USING `%s` LEFT JOIN `%s` ON `%s`.leader_id =`%s`.account_id AND `%s`.leader_char = `%s`.char_id WHERE `%s`.account_id IS NULL", + party_db, party_db, char_db, party_db, char_db, party_db, char_db, char_db) ) + Sql_ShowDebug(sql_handle); + */ + return 0; +} + +void inter_party_sql_final(void) +{ + party_db_->destroy(party_db_, NULL); + aFree(party_pt); + return; +} + +// Search for the party according to its name +struct party_data* search_partyname(char* str) +{ + char esc_name[NAME_LENGTH*2+1]; + char* data; + struct party_data* p = NULL; + + Sql_EscapeStringLen(sql_handle, esc_name, str, safestrnlen(str, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `party_id` FROM `%s` WHERE `name`='%s'", party_db, esc_name) ) + Sql_ShowDebug(sql_handle); + else if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + Sql_GetData(sql_handle, 0, &data, NULL); + p = inter_party_fromsql(atoi(data)); + } + Sql_FreeResult(sql_handle); + + 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); +} + +// Is there any member in the party? +int party_check_empty(struct party_data *p) +{ + int i; + if (p==NULL||p->party.party_id==0) return 1; + for(i=0;i<MAX_PARTY && !p->party.member[i].account_id;i++); + if (i < MAX_PARTY) return 0; + // If there is no member, then break the party + mapif_party_broken(p->party.party_id,0); + inter_party_tosql(&p->party, PS_BREAK, 0); + return 1; +} + +//------------------------------------------------------------------- +// Communication to the map server + + +// Create a party whether or not +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("int_party: Party created (%d - %s)\n",p->party_id,p->name); + }else{ + WFIFOB(fd,10)=1; + WFIFOL(fd,11)=0; + memset(WFIFOP(fd,15),0,NAME_LENGTH); + } + WFIFOSET(fd,39); + + return 0; +} + +//Party information not found +static void mapif_party_noinfo(int fd, int party_id, int char_id) +{ + WFIFOHEAD(fd, 12); + WFIFOW(fd,0) = 0x3821; + WFIFOW(fd,2) = 12; + WFIFOL(fd,4) = char_id; + WFIFOL(fd,8) = party_id; + WFIFOSET(fd,12); + ShowWarning("int_party: info not found (party_id=%d char_id=%d)\n", party_id, char_id); +} + +//Digest party information +static void mapif_party_info(int fd, struct party* p, int char_id) +{ + unsigned char buf[8 + sizeof(struct party)]; + WBUFW(buf,0) = 0x3821; + WBUFW(buf,2) = 8 + sizeof(struct party); + WBUFL(buf,4) = char_id; + memcpy(WBUFP(buf,8), p, sizeof(struct party)); + + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); +} + +//Whether or not additional party members +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; +} + +// Party setting change notification +int mapif_party_optionchanged(int fd,struct party *p,int account_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3823; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=account_id; + WBUFW(buf,10)=p->exp; + WBUFW(buf,12)=p->item; + WBUFB(buf,14)=flag; + if(flag==0) + mapif_sendall(buf,15); + else + mapif_send(fd,buf,15); + return 0; +} + +//Withdrawal notification party +int mapif_party_withdraw(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; +} + +//Party map update notification +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; +} + +//Dissolution party notification +int mapif_party_broken(int party_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3826; + WBUFL(buf,2)=party_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + //printf("int_party: broken %d\n",party_id); + return 0; +} + +//Remarks in the party +int mapif_party_message(int party_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3827; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=party_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendallwos(sfd, buf,len+12); + return 0; +} + +//------------------------------------------------------------------- +// Communication from the map server + + +// Create Party +int mapif_parse_CreateParty(int fd, char *name, int item, int item2, struct party_member *leader) +{ + struct party_data *p; + int i; + if( (p=search_partyname(name))!=NULL){ + 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(1, sizeof(struct party_data)); + + 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; + p->party.member[0].online=1; + + p->party.party_id=-1;//New party. + if (inter_party_tosql(&p->party,PS_CREATE|PS_ADDMEMBER,0)) { + //Add party to db + int_party_calc_state(p); + idb_put(party_db_, p->party.party_id, p); + mapif_party_info(fd, &p->party, 0); + mapif_party_created(fd,leader->account_id,leader->char_id,&p->party); + } else { //Failed to create party. + aFree(p); + mapif_party_created(fd,leader->account_id,leader->char_id,NULL); + } + + return 0; +} + +// Party information request +static void mapif_parse_PartyInfo(int fd, int party_id, int char_id) +{ + struct party_data *p; + p = inter_party_fromsql(party_id); + + if (p) + mapif_party_info(fd, &p->party, char_id); + else + mapif_party_noinfo(fd, party_id, char_id); +} + +// Add a player to party request +int mapif_parse_PartyAddMember(int fd, int party_id, struct party_member *member) +{ + struct party_data *p; + int i; + + p = inter_party_fromsql(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 == 2 || p->size == 3) // Check family state. And also accept either of their Parents. [RoM] + 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_info(-1, &p->party, 0); + mapif_party_memberadded(fd, party_id, member->account_id, member->char_id, 0); + inter_party_tosql(&p->party, PS_ADDMEMBER, i); + + return 0; +} + +//Party setting change request +int mapif_parse_PartyChangeOption(int fd,int party_id,int account_id,int exp,int item) +{ + struct party_data *p; + int flag = 0; + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + + p->party.exp=exp; + if( exp && !party_check_exp_share(p) ){ + flag|=0x01; + p->party.exp=0; + } + p->party.item = item&0x3; //Filter out invalid values. + mapif_party_optionchanged(fd,&p->party,account_id,flag); + inter_party_tosql(&p->party, PS_BASIC, 0); + return 0; +} + +//Request leave party +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id) +{ + struct party_data *p; + int i,j=-1; + + p = inter_party_fromsql(party_id); + if( p == NULL ) + {// Party does not exists? + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id) ) + Sql_ShowDebug(sql_handle); + 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) { + break; + } + } + if (i >= MAX_PARTY) + return 0; //Member not found? + + mapif_party_withdraw(party_id, account_id, char_id); + + if (p->party.member[i].leader){ + p->party.member[i].account_id = 0; + for (j = 0; j < MAX_PARTY; j++) { + if (!p->party.member[j].account_id) + continue; + mapif_party_withdraw(party_id, p->party.member[j].account_id, p->party.member[j].char_id); + p->party.member[j].account_id = 0; + } + //Party gets deleted on the check_empty call below. + } else { + inter_party_tosql(&p->party,PS_DELMEMBER,i); + j = 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 (j == p->min_lv || j == p->max_lv || p->family) + { + if(p->family) p->family = 0; //Family state broken. + int_party_check_lv(p); + } + } + + if (party_check_empty(p) == 0) + mapif_party_info(-1, &p->party, 0); + return 0; +} +// When member goes to other map or levels up. +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 = inter_party_fromsql(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; +} + +//Request party dissolution +int mapif_parse_BreakParty(int fd,int party_id) +{ + struct party_data *p; + + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + inter_party_tosql(&p->party,PS_BREAK,0); + mapif_party_broken(fd,party_id); + return 0; +} + +//Party sending the message +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_PartyLeaderChange(int fd,int party_id,int account_id,int char_id) +{ + struct party_data *p; + int i; + + p = inter_party_fromsql(party_id); + + if(!p) + 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; + inter_party_tosql(&p->party,PS_LEADER, i); + } + } + return 1; +} + + +// Communication from the map server +//-Analysis that only one packet +// Data packet length is set to inter.c that you +// Do NOT go and check the packet length, RFIFOSKIP is done by the caller +// Return : +// 0 : error +// 1 : ok +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), RFIFOL(fd,6)); 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 0x3029: mapif_parse_PartyLeaderChange(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + default: + return 0; + } + return 1; +} + +//Leave request from the server (for delete character) +int inter_party_leave(int party_id,int account_id, int char_id) +{ + return mapif_parse_PartyLeave(-1,party_id,account_id, char_id); +} + +int inter_party_CharOnline(int char_id, int party_id) +{ + struct party_data* p; + int i; + + if( party_id == -1 ) + {// Get party_id from the database + char* data; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT party_id FROM `%s` WHERE char_id='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + return 0; //Eh? No party? + + Sql_GetData(sql_handle, 0, &data, NULL); + party_id = atoi(data); + Sql_FreeResult(sql_handle); + } + if (party_id == 0) + return 0; //No party... + + p = inter_party_fromsql(party_id); + if(!p) { + ShowError("Character %d's party %d not found!\n", char_id, party_id); + return 0; + } + + //Set member online + for(i=0; i<MAX_PARTY; i++) { + if (p->party.member[i].char_id == char_id) { + if (!p->party.member[i].online) { + p->party.member[i].online = 1; + p->party.count++; + if (p->party.member[i].lv < p->min_lv || + p->party.member[i].lv > p->max_lv) + int_party_check_lv(p); + } + break; + } + } + return 1; +} + +int inter_party_CharOffline(int char_id, int party_id) { + struct party_data *p=NULL; + int i; + + if( party_id == -1 ) + {// Get guild_id from the database + char* data; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT party_id FROM `%s` WHERE char_id='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + return 0; //Eh? No party? + + Sql_GetData(sql_handle, 0, &data, NULL); + party_id = atoi(data); + Sql_FreeResult(sql_handle); + } + if (party_id == 0) + return 0; //No party... + + //Character has a party, set character offline and check if they were the only member online + if ((p = inter_party_fromsql(party_id)) == NULL) + return 0; + + //Set member offline + for(i=0; i< MAX_PARTY; i++) { + if(p->party.member[i].char_id == char_id) + { + p->party.member[i].online = 0; + p->party.count--; + if(p->party.member[i].lv == p->min_lv || + p->party.member[i].lv == p->max_lv) + int_party_check_lv(p); + break; + } + } + + if(!p->party.count) + //Parties don't have any data that needs be saved at this point... so just remove it from memory. + idb_remove(party_db_, party_id); + return 1; +} diff --git a/src/char/int_party.h b/src/char/int_party.h new file mode 100644 index 000000000..d8cdcdc6a --- /dev/null +++ b/src/char/int_party.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_PARTY_SQL_H_ +#define _INT_PARTY_SQL_H_ + +//Party Flags on what to save/delete. +enum { + PS_CREATE = 0x01, //Create a new party entry (index holds leader's info) + PS_BASIC = 0x02, //Update basic party info. + PS_LEADER = 0x04, //Update party's leader + PS_ADDMEMBER = 0x08, //Specify new party member (index specifies which party member) + PS_DELMEMBER = 0x10, //Specify member that left (index specifies which party member) + PS_BREAK = 0x20, //Specify that this party must be deleted. +}; + +struct party; + +int inter_party_parse_frommap(int fd); +int inter_party_sql_init(void); +void inter_party_sql_final(void); +int inter_party_leave(int party_id,int account_id, int char_id); +int inter_party_CharOnline(int char_id, int party_id); +int inter_party_CharOffline(int char_id, int party_id); + +#endif /* _INT_PARTY_SQL_H_ */ diff --git a/src/char/int_pet.c b/src/char/int_pet.c new file mode 100644 index 000000000..114398290 --- /dev/null +++ b/src/char/int_pet.c @@ -0,0 +1,309 @@ +// 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/strlib.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "../common/sql.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct s_pet *pet_pt; + +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet* p) +{ + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + char esc_name[NAME_LENGTH*2+1];// escaped pet name + + Sql_EscapeStringLen(sql_handle, esc_name, p->name, strnlen(p->name, NAME_LENGTH)); + p->hungry = cap_value(p->hungry, 0, 100); + p->intimate = cap_value(p->intimate, 0, 1000); + + if( pet_id == -1 ) + {// New pet. + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` " + "(`class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) " + "VALUES ('%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + pet_db, p->class_, esc_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + p->pet_id = (int)Sql_LastInsertId(sql_handle); + } + else + {// Update pet. + if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + pet_db, p->class_, esc_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + } + + if (save_log) + ShowInfo("Pet saved %d - %s.\n", pet_id, p->name); + return 1; +} + +int inter_pet_fromsql(int pet_id, struct s_pet* p) +{ + char* data; + size_t len; + +#ifdef NOISY + ShowInfo("Loading pet (%d)...\n",pet_id); +#endif + memset(p, 0, sizeof(struct s_pet)); + + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate` FROM `%s` WHERE `pet_id`='%d'", pet_db, pet_id) ) + { + Sql_ShowDebug(sql_handle); + return 0; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + p->pet_id = pet_id; + Sql_GetData(sql_handle, 1, &data, NULL); p->class_ = atoi(data); + Sql_GetData(sql_handle, 2, &data, &len); memcpy(p->name, data, min(len, NAME_LENGTH)); + Sql_GetData(sql_handle, 3, &data, NULL); p->account_id = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); p->char_id = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); p->level = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); p->egg_id = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); p->equip = atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); p->intimate = atoi(data); + Sql_GetData(sql_handle, 9, &data, NULL); p->hungry = atoi(data); + Sql_GetData(sql_handle, 10, &data, NULL); p->rename_flag = atoi(data); + Sql_GetData(sql_handle, 11, &data, NULL); p->incuvate = atoi(data); + + Sql_FreeResult(sql_handle); + + p->hungry = cap_value(p->hungry, 0, 100); + p->intimate = cap_value(p->intimate, 0, 1000); + + if( save_log ) + ShowInfo("Pet loaded (%d - %s).\n", pet_id, p->name); + } + return 0; +} +//---------------------------------------------- + +int inter_pet_sql_init(void){ + //memory alloc + pet_pt = (struct s_pet*)aCalloc(sizeof(struct s_pet), 1); + return 0; +} +void inter_pet_sql_final(void){ + if (pet_pt) aFree(pet_pt); + return; +} +//---------------------------------- +int inter_pet_delete(int pet_id){ + ShowInfo("delete pet request: %d...\n",pet_id); + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `pet_id`='%d'", pet_db, pet_id) ) + Sql_ShowDebug(sql_handle); + 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("int_pet: created pet %d - %s\n", p->pet_id, p->name); + }else{ + WFIFOB(fd, 6)=1; + WFIFOL(fd, 7)=0; + } + WFIFOSET(fd, 11); + + return 0; +} + +int mapif_pet_info(int fd, int account_id, struct s_pet *p){ + 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) +{ + memset(pet_pt, 0, sizeof(struct s_pet)); + strncpy(pet_pt->name, pet_name, NAME_LENGTH); + if(incuvate == 1) + pet_pt->account_id = pet_pt->char_id = 0; + else { + pet_pt->account_id = account_id; + pet_pt->char_id = char_id; + } + pet_pt->class_ = pet_class; + pet_pt->level = pet_lv; + pet_pt->egg_id = pet_egg_id; + pet_pt->equip = pet_equip; + pet_pt->intimate = intimate; + pet_pt->hungry = hungry; + pet_pt->rename_flag = rename_flag; + pet_pt->incuvate = incuvate; + + if(pet_pt->hungry < 0) + pet_pt->hungry = 0; + else if(pet_pt->hungry > 100) + pet_pt->hungry = 100; + if(pet_pt->intimate < 0) + pet_pt->intimate = 0; + else if(pet_pt->intimate > 1000) + pet_pt->intimate = 1000; + + pet_pt->pet_id = -1; //Signal NEW pet. + if (inter_pet_tosql(pet_pt->pet_id,pet_pt)) + mapif_pet_created(fd, account_id, pet_pt); + else //Failed... + mapif_pet_created(fd, account_id, NULL); + + return 0; +} + +int mapif_load_pet(int fd, int account_id, int char_id, int pet_id){ + memset(pet_pt, 0, sizeof(struct s_pet)); + + inter_pet_fromsql(pet_id, pet_pt); + + if(pet_pt!=NULL) { + if(pet_pt->incuvate == 1) { + pet_pt->account_id = pet_pt->char_id = 0; + mapif_pet_info(fd, account_id, pet_pt); + } + else if(account_id == pet_pt->account_id && char_id == pet_pt->char_id) + mapif_pet_info(fd, account_id, pet_pt); + else + mapif_pet_noinfo(fd, account_id); + } + else + mapif_pet_noinfo(fd, account_id); + + return 0; +} + +int mapif_save_pet(int fd, int account_id, struct s_pet *data) { + //here process pet save request. + int len; + 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{ + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + inter_pet_tosql(data->pet_id,data); + mapif_save_pet_ack(fd, account_id, 0); + } + + return 0; +} + +int mapif_delete_pet(int fd, int pet_id){ + mapif_delete_pet_ack(fd, inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd){ + 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; +} + +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; +} diff --git a/src/char/int_pet.h b/src/char/int_pet.h new file mode 100644 index 000000000..733468c77 --- /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_SQL_H_ +#define _INT_PET_SQL_H_ + +struct s_pet; + +int inter_pet_init(void); +void inter_pet_sql_final(void); +int inter_pet_save(void); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); +int inter_pet_sql_init(void); +//extern char pet_txt[256]; + +//Exported for use in the TXT-SQL converter. +int inter_pet_tosql(int pet_id, struct s_pet *p); + +#endif /* _INT_PET_SQL_H_ */ diff --git a/src/char/int_quest.c b/src/char/int_quest.c new file mode 100644 index 000000000..224205412 --- /dev/null +++ b/src/char/int_quest.c @@ -0,0 +1,184 @@ +// 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/malloc.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/sql.h" +#include "../common/timer.h" + +#include "char.h" +#include "inter.h" +#include "int_quest.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +//Load entire questlog for a character +int mapif_quests_fromsql(int char_id, struct quest questlog[]) +{ + int i; + struct quest tmp_quest; + SqlStmt * stmt; + + stmt = SqlStmt_Malloc(sql_handle); + if( stmt == NULL ) + { + SqlStmt_ShowDebug(stmt); + return 0; + } + + memset(&tmp_quest, 0, sizeof(struct quest)); + + if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `quest_id`, `state`, `time`, `count1`, `count2`, `count3` FROM `%s` WHERE `char_id`=? LIMIT %d", quest_db, MAX_QUEST_DB) + || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) + || SQL_ERROR == SqlStmt_Execute(stmt) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_quest.quest_id, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &tmp_quest.state, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UINT, &tmp_quest.time, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_INT, &tmp_quest.count[0], 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_INT, &tmp_quest.count[1], 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_INT, &tmp_quest.count[2], 0, NULL, NULL) ) + SqlStmt_ShowDebug(stmt); + + for( i = 0; i < MAX_QUEST_DB && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i ) + memcpy(&questlog[i], &tmp_quest, sizeof(tmp_quest)); + + SqlStmt_Free(stmt); + return i; +} + +//Delete a quest +bool mapif_quest_delete(int char_id, int quest_id) +{ + if ( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, quest_id, char_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +//Add a quest to a questlog +bool mapif_quest_add(int char_id, struct quest qd) +{ + if ( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`quest_id`, `char_id`, `state`, `time`, `count1`, `count2`, `count3`) VALUES ('%d', '%d', '%d','%d', '%d', '%d', '%d')", quest_db, qd.quest_id, char_id, qd.state, qd.time, qd.count[0], qd.count[1], qd.count[2]) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +//Update a questlog +bool mapif_quest_update(int char_id, struct quest qd) +{ + if ( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `state`='%d', `count1`='%d', `count2`='%d', `count3`='%d' WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, qd.state, qd.count[0], qd.count[1], qd.count[2], qd.quest_id, char_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + return true; +} + +//Save quests +int mapif_parse_quest_save(int fd) +{ + int i, j, k, num2, num1 = (RFIFOW(fd,2)-8)/sizeof(struct quest); + int char_id = RFIFOL(fd,4); + struct quest qd1[MAX_QUEST_DB],qd2[MAX_QUEST_DB]; + bool success = true; + + memset(qd1, 0, sizeof(qd1)); + memset(qd2, 0, sizeof(qd2)); + if( num1 ) memcpy(&qd1, RFIFOP(fd,8), RFIFOW(fd,2)-8); + num2 = mapif_quests_fromsql(char_id, qd2); + + for( i = 0; i < num1; i++ ) + { + ARR_FIND( 0, num2, j, qd1[i].quest_id == qd2[j].quest_id ); + if( j < num2 ) // Update existed quests + { // Only states and counts are changable. + ARR_FIND( 0, MAX_QUEST_OBJECTIVES, k, qd1[i].count[k] != qd2[j].count[k] ); + if( k != MAX_QUEST_OBJECTIVES || qd1[i].state != qd2[j].state ) + success &= mapif_quest_update(char_id, qd1[i]); + + if( j < (--num2) ) + { + memmove(&qd2[j],&qd2[j+1],sizeof(struct quest)*(num2-j)); + memset(&qd2[num2], 0, sizeof(struct quest)); + } + + } + else // Add new quests + success &= mapif_quest_add(char_id, qd1[i]); + } + + for( i = 0; i < num2; i++ ) // Quests not in qd1 but in qd2 are to be erased. + success &= mapif_quest_delete(char_id, qd2[i].quest_id); + + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x3861; + WFIFOL(fd,2) = char_id; + WFIFOB(fd,6) = success?1:0; + WFIFOSET(fd,7); + + return 0; +} + +//Send questlog to map server +int mapif_parse_quest_load(int fd) +{ + int char_id = RFIFOL(fd,2); + struct quest tmp_questlog[MAX_QUEST_DB]; + int num_quests, i, num_complete = 0; + int complete[MAX_QUEST_DB]; + + memset(tmp_questlog, 0, sizeof(tmp_questlog)); + memset(complete, 0, sizeof(complete)); + + num_quests = mapif_quests_fromsql(char_id, tmp_questlog); + + WFIFOHEAD(fd,num_quests*sizeof(struct quest)+8); + WFIFOW(fd,0) = 0x3860; + WFIFOW(fd,2) = num_quests*sizeof(struct quest)+8; + WFIFOL(fd,4) = char_id; + + //Active and inactive quests + for( i = 0; i < num_quests; i++ ) + { + if( tmp_questlog[i].state == Q_COMPLETE ) + { + complete[num_complete++] = i; + continue; + } + memcpy(WFIFOP(fd,(i-num_complete)*sizeof(struct quest)+8), &tmp_questlog[i], sizeof(struct quest)); + } + + // Completed quests + for( i = num_quests - num_complete; i < num_quests; i++ ) + memcpy(WFIFOP(fd,i*sizeof(struct quest)+8), &tmp_questlog[complete[i-num_quests+num_complete]], sizeof(struct quest)); + + WFIFOSET(fd,num_quests*sizeof(struct quest)+8); + + return 0; +} + +int inter_quest_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)) + { + case 0x3060: mapif_parse_quest_load(fd); break; + case 0x3061: mapif_parse_quest_save(fd); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_quest.h b/src/char/int_quest.h new file mode 100644 index 000000000..f2a0b626e --- /dev/null +++ b/src/char/int_quest.h @@ -0,0 +1,13 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _QUEST_H_ +#define _QUEST_H_ + +/*questlog system*/ +struct quest; + +int inter_quest_parse_frommap(int fd); + +#endif + diff --git a/src/char/int_storage.c b/src/char/int_storage.c new file mode 100644 index 000000000..68b588087 --- /dev/null +++ b/src/char/int_storage.c @@ -0,0 +1,248 @@ +// 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/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" // StringBuf +#include "../common/sql.h" +#include "char.h" +#include "inter.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + + +#define STORAGE_MEMINC 16 + +/// Save storage data to sql +int storage_tosql(int account_id, struct storage_data* p) +{ + memitemdata_to_sql(p->items, MAX_STORAGE, account_id, TABLE_STORAGE); + return 0; +} + +/// Load storage data to mem +int storage_fromsql(int account_id, struct storage_data* p) +{ + StringBuf buf; + struct item* item; + char* data; + int i; + int j; + + memset(p, 0, sizeof(struct storage_data)); //clean up memory + p->storage_amount = 0; + + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`unique_id`"); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ",`card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `account_id`='%d' ORDER BY `nameid`", storage_db, account_id); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + + StringBuf_Destroy(&buf); + + for( i = 0; i < MAX_STORAGE && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + item = &p->items[i]; + Sql_GetData(sql_handle, 0, &data, NULL); item->id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); item->nameid = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); item->amount = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); item->equip = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); item->identify = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); item->refine = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); item->attribute = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); item->expire_time = (unsigned int)atoi(data); + Sql_GetData(sql_handle, 8, &data, NULL); item->unique_id = strtoull(data, NULL, 10); + for( j = 0; j < MAX_SLOTS; ++j ) + { + Sql_GetData(sql_handle, 9+j, &data, NULL); item->card[j] = atoi(data); + } + } + p->storage_amount = i; + Sql_FreeResult(sql_handle); + + ShowInfo("storage load complete from DB - id: %d (total: %d)\n", account_id, p->storage_amount); + return 1; +} + +/// Save guild_storage data to sql +int guild_storage_tosql(int guild_id, struct guild_storage* p) +{ + memitemdata_to_sql(p->items, MAX_GUILD_STORAGE, guild_id, TABLE_GUILD_STORAGE); + ShowInfo ("guild storage save to DB - guild: %d\n", guild_id); + return 0; +} + +/// Load guild_storage data to mem +int guild_storage_fromsql(int guild_id, struct guild_storage* p) +{ + StringBuf buf; + struct item* item; + char* data; + int i; + int j; + + memset(p, 0, sizeof(struct guild_storage)); //clean up memory + p->storage_amount = 0; + p->guild_id = guild_id; + + // storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`unique_id`"); + for( j = 0; j < MAX_SLOTS; ++j ) + StringBuf_Printf(&buf, ",`card%d`", j); + StringBuf_Printf(&buf, " FROM `%s` WHERE `guild_id`='%d' ORDER BY `nameid`", guild_storage_db, guild_id); + + if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ) + Sql_ShowDebug(sql_handle); + + StringBuf_Destroy(&buf); + + for( i = 0; i < MAX_GUILD_STORAGE && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + item = &p->items[i]; + Sql_GetData(sql_handle, 0, &data, NULL); item->id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); item->nameid = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); item->amount = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); item->equip = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); item->identify = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); item->refine = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); item->attribute = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); item->unique_id = strtoull(data, NULL, 10); + item->expire_time = 0; + for( j = 0; j < MAX_SLOTS; ++j ) + { + Sql_GetData(sql_handle, 8+j, &data, NULL); item->card[j] = atoi(data); + } + } + p->storage_amount = i; + Sql_FreeResult(sql_handle); + + ShowInfo("guild storage load complete from DB - id: %d (total: %d)\n", guild_id, p->storage_amount); + return 0; +} + +//--------------------------------------------------------- +// storage data initialize +int inter_storage_sql_init(void) +{ + return 1; +} +// storage data finalize +void inter_storage_sql_final(void) +{ + return; +} + +// q?f[^? +int inter_storage_delete(int account_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id`='%d'", storage_db, account_id) ) + Sql_ShowDebug(sql_handle); + return 0; +} +int inter_guild_storage_delete(int guild_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `guild_id`='%d'", guild_storage_db, guild_id) ) + Sql_ShowDebug(sql_handle); + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `guild_id` FROM `%s` WHERE `guild_id`='%d'", guild_db, guild_id) ) + Sql_ShowDebug(sql_handle); + else if( Sql_NumRows(sql_handle) > 0 ) + {// guild exists + WFIFOHEAD(fd, sizeof(struct guild_storage)+12); + WFIFOW(fd,0) = 0x3818; + WFIFOW(fd,2) = sizeof(struct guild_storage)+12; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = guild_id; + guild_storage_fromsql(guild_id, (struct guild_storage*)WFIFOP(fd,12)); + WFIFOSET(fd, WFIFOW(fd,2)); + return 0; + } + // guild does not exist + Sql_FreeResult(sql_handle); + WFIFOHEAD(fd, 12); + WFIFOW(fd,0) = 0x3818; + WFIFOW(fd,2) = 12; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = 0; + WFIFOSET(fd, 12); + 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; +} + +//--------------------------------------------------------- +// packet from map server + +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) +{ + int guild_id; + int 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 + { + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `guild_id` FROM `%s` WHERE `guild_id`='%d'", guild_db, guild_id) ) + Sql_ShowDebug(sql_handle); + else if( Sql_NumRows(sql_handle) > 0 ) + {// guild exists + Sql_FreeResult(sql_handle); + guild_storage_tosql(guild_id, (struct guild_storage*)RFIFOP(fd,12)); + mapif_save_guild_storage_ack(fd, RFIFOL(fd,4), guild_id, 0); + return 0; + } + Sql_FreeResult(sql_handle); + } + mapif_save_guild_storage_ack(fd, RFIFOL(fd,4), guild_id, 1); + return 0; +} + + +int inter_storage_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)){ + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_storage.h b/src/char/int_storage.h new file mode 100644 index 000000000..811608f82 --- /dev/null +++ b/src/char/int_storage.h @@ -0,0 +1,22 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_STORAGE_SQL_H_ +#define _INT_STORAGE_SQL_H_ + +struct storage_data; +struct guild_storage; + +int inter_storage_sql_init(void); +void inter_storage_sql_final(void); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + +//Exported for use in the TXT-SQL converter. +int storage_fromsql(int account_id, struct storage_data* p); +int storage_tosql(int account_id,struct storage_data *p); +int guild_storage_tosql(int guild_id, struct guild_storage *p); + +#endif /* _INT_STORAGE_SQL_H_ */ diff --git a/src/char/inter.c b/src/char/inter.c new file mode 100644 index 000000000..8faff42c1 --- /dev/null +++ b/src/char/inter.c @@ -0,0 +1,1257 @@ +// 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/malloc.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "char.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_homun.h" +#include "int_mercenary.h" +#include "int_mail.h" +#include "int_auction.h" +#include "int_quest.h" +#include "int_elemental.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/stat.h> // for stat/lstat/fstat - [Dekamaster/Ultimate GM Tool] + + +#define WISDATA_TTL (60*1000) //Wis data Time To Live (60 seconds) +#define WISDELLIST_MAX 256 // Number of elements in the list Delete data Wis + + +Sql* sql_handle = NULL; + +int char_server_port = 3306; +char char_server_ip[32] = "127.0.0.1"; +char char_server_id[32] = "ragnarok"; +char char_server_pw[32] = "ragnarok"; +char char_server_db[32] = "ragnarok"; +char default_codepage[32] = ""; //Feature by irmin. + +static struct accreg *accreg_pt; +unsigned int party_share_level = 10; +char main_chat_nick[16] = "Main"; + +// recv. packet list +int inter_recv_packet_length[] = { + -1,-1, 7,-1, -1,13,36, (2 + 4 + 4 + 4 + NAME_LENGTH), 0, 0, 0, 0, 0, 0, 0, 0, // 3000- + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010- + -1,10,-1,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, // 3020- Party + -1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1, // 3030- + -1, 9, 0, 0, 0, 0, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040- + -1,-1,10,10, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus] + 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish] + -1,10, 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, -1,10, 6,-1, // 3070- Mercenary packets [Zephyrus], Elemental packets [pakpil] + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3080- + -1,10,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3090- Homunculus packets [albator] +}; + +struct WisData { + int id, fd, count, len; + unsigned long tick; + unsigned char src[24], dst[24], msg[512]; +}; +static DBMap* wis_db = NULL; // int wis_id -> struct WisData* +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + +#define MAX_JOB_NAMES 106 +static char* msg_table[MAX_JOB_NAMES]; // messages 550 ~ 655 are job names + +const char* msg_txt(int msg_number) { + msg_number -= 550; + if (msg_number >= 0 && msg_number < MAX_JOB_NAMES && + msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0') + return msg_table[msg_number]; + + return "Unknown"; +} + +/*========================================== + * Read Message Data -- at char server we only keep job names. + *------------------------------------------*/ +int msg_config_read(const char* cfgName) { + int msg_number; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + static int called = 1; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("Messages file not found: %s\n", cfgName); + return 1; + } + + if ((--called) == 0) + memset(msg_table, 0, sizeof(msg_table[0]) * MAX_JOB_NAMES); + + while(fgets(line, sizeof(line), fp) ) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "import") == 0) + msg_config_read(w2); + else { + msg_number = atoi(w1); + if( msg_number < 550 || msg_number > (550+MAX_JOB_NAMES) ) + continue; + msg_number -= 550; + if (msg_number >= 0 && msg_number < MAX_JOB_NAMES) { + if (msg_table[msg_number] != NULL) + aFree(msg_table[msg_number]); + msg_table[msg_number] = (char *)aMalloc((strlen(w2) + 1)*sizeof (char)); + strcpy(msg_table[msg_number],w2); + } + } + } + + fclose(fp); + + return 0; +} + +/*========================================== + * Cleanup Message Data + *------------------------------------------*/ +void do_final_msg(void) { + int i; + for (i = 0; i < MAX_JOB_NAMES; i++) + aFree(msg_table[i]); +} +/* from pc.c due to @accinfo. any ideas to replace this crap are more than welcome. */ +const char* job_name(int class_) { + switch (class_) { + case JOB_NOVICE: + case JOB_SWORDMAN: + case JOB_MAGE: + case JOB_ARCHER: + case JOB_ACOLYTE: + case JOB_MERCHANT: + case JOB_THIEF: + return msg_txt(550 - JOB_NOVICE+class_); + + case JOB_KNIGHT: + case JOB_PRIEST: + case JOB_WIZARD: + case JOB_BLACKSMITH: + case JOB_HUNTER: + case JOB_ASSASSIN: + return msg_txt(557 - JOB_KNIGHT+class_); + + case JOB_KNIGHT2: + return msg_txt(557); + + case JOB_CRUSADER: + case JOB_MONK: + case JOB_SAGE: + case JOB_ROGUE: + case JOB_ALCHEMIST: + case JOB_BARD: + case JOB_DANCER: + return msg_txt(563 - JOB_CRUSADER+class_); + + case JOB_CRUSADER2: + return msg_txt(563); + + case JOB_WEDDING: + case JOB_SUPER_NOVICE: + case JOB_GUNSLINGER: + case JOB_NINJA: + case JOB_XMAS: + return msg_txt(570 - JOB_WEDDING+class_); + + case JOB_SUMMER: + return msg_txt(621); + + case JOB_NOVICE_HIGH: + case JOB_SWORDMAN_HIGH: + case JOB_MAGE_HIGH: + case JOB_ARCHER_HIGH: + case JOB_ACOLYTE_HIGH: + case JOB_MERCHANT_HIGH: + case JOB_THIEF_HIGH: + return msg_txt(575 - JOB_NOVICE_HIGH+class_); + + case JOB_LORD_KNIGHT: + case JOB_HIGH_PRIEST: + case JOB_HIGH_WIZARD: + case JOB_WHITESMITH: + case JOB_SNIPER: + case JOB_ASSASSIN_CROSS: + return msg_txt(582 - JOB_LORD_KNIGHT+class_); + + case JOB_LORD_KNIGHT2: + return msg_txt(582); + + case JOB_PALADIN: + case JOB_CHAMPION: + case JOB_PROFESSOR: + case JOB_STALKER: + case JOB_CREATOR: + case JOB_CLOWN: + case JOB_GYPSY: + return msg_txt(588 - JOB_PALADIN + class_); + + case JOB_PALADIN2: + return msg_txt(588); + + case JOB_BABY: + case JOB_BABY_SWORDMAN: + case JOB_BABY_MAGE: + case JOB_BABY_ARCHER: + case JOB_BABY_ACOLYTE: + case JOB_BABY_MERCHANT: + case JOB_BABY_THIEF: + return msg_txt(595 - JOB_BABY + class_); + + case JOB_BABY_KNIGHT: + case JOB_BABY_PRIEST: + case JOB_BABY_WIZARD: + case JOB_BABY_BLACKSMITH: + case JOB_BABY_HUNTER: + case JOB_BABY_ASSASSIN: + return msg_txt(602 - JOB_BABY_KNIGHT + class_); + + case JOB_BABY_KNIGHT2: + return msg_txt(602); + + case JOB_BABY_CRUSADER: + case JOB_BABY_MONK: + case JOB_BABY_SAGE: + case JOB_BABY_ROGUE: + case JOB_BABY_ALCHEMIST: + case JOB_BABY_BARD: + case JOB_BABY_DANCER: + return msg_txt(608 - JOB_BABY_CRUSADER + class_); + + case JOB_BABY_CRUSADER2: + return msg_txt(608); + + case JOB_SUPER_BABY: + return msg_txt(615); + + case JOB_TAEKWON: + return msg_txt(616); + case JOB_STAR_GLADIATOR: + case JOB_STAR_GLADIATOR2: + return msg_txt(617); + case JOB_SOUL_LINKER: + return msg_txt(618); + + case JOB_GANGSI: + case JOB_DEATH_KNIGHT: + case JOB_DARK_COLLECTOR: + return msg_txt(622 - JOB_GANGSI+class_); + + case JOB_RUNE_KNIGHT: + case JOB_WARLOCK: + case JOB_RANGER: + case JOB_ARCH_BISHOP: + case JOB_MECHANIC: + case JOB_GUILLOTINE_CROSS: + return msg_txt(625 - JOB_RUNE_KNIGHT+class_); + + case JOB_RUNE_KNIGHT_T: + case JOB_WARLOCK_T: + case JOB_RANGER_T: + case JOB_ARCH_BISHOP_T: + case JOB_MECHANIC_T: + case JOB_GUILLOTINE_CROSS_T: + return msg_txt(625 - JOB_RUNE_KNIGHT_T+class_); + + case JOB_ROYAL_GUARD: + case JOB_SORCERER: + case JOB_MINSTREL: + case JOB_WANDERER: + case JOB_SURA: + case JOB_GENETIC: + case JOB_SHADOW_CHASER: + return msg_txt(631 - JOB_ROYAL_GUARD+class_); + + case JOB_ROYAL_GUARD_T: + case JOB_SORCERER_T: + case JOB_MINSTREL_T: + case JOB_WANDERER_T: + case JOB_SURA_T: + case JOB_GENETIC_T: + case JOB_SHADOW_CHASER_T: + return msg_txt(631 - JOB_ROYAL_GUARD_T+class_); + + case JOB_RUNE_KNIGHT2: + case JOB_RUNE_KNIGHT_T2: + return msg_txt(625); + + case JOB_ROYAL_GUARD2: + case JOB_ROYAL_GUARD_T2: + return msg_txt(631); + + case JOB_RANGER2: + case JOB_RANGER_T2: + return msg_txt(627); + + case JOB_MECHANIC2: + case JOB_MECHANIC_T2: + return msg_txt(629); + + case JOB_BABY_RUNE: + case JOB_BABY_WARLOCK: + case JOB_BABY_RANGER: + case JOB_BABY_BISHOP: + case JOB_BABY_MECHANIC: + case JOB_BABY_CROSS: + case JOB_BABY_GUARD: + case JOB_BABY_SORCERER: + case JOB_BABY_MINSTREL: + case JOB_BABY_WANDERER: + case JOB_BABY_SURA: + case JOB_BABY_GENETIC: + case JOB_BABY_CHASER: + return msg_txt(638 - JOB_BABY_RUNE+class_); + + case JOB_BABY_RUNE2: + return msg_txt(638); + + case JOB_BABY_GUARD2: + return msg_txt(644); + + case JOB_BABY_RANGER2: + return msg_txt(640); + + case JOB_BABY_MECHANIC2: + return msg_txt(642); + + case JOB_SUPER_NOVICE_E: + case JOB_SUPER_BABY_E: + return msg_txt(651 - JOB_SUPER_NOVICE_E+class_); + + case JOB_KAGEROU: + case JOB_OBORO: + return msg_txt(653 - JOB_KAGEROU+class_); + + default: + return msg_txt(655); + } +} + +/** + * [Dekamaster/Nightroad] + **/ +const char * geoip_countryname[253] = {"Unknown","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles", + "Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados", + "Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia", + "Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the", + "Central African Republic","Congo","Switzerland","Cote D'Ivoire","Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica", + "Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic", + "Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji", + "Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana", + "Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala", + "Guam","Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia", + "Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan", + "Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis","Korea, Democratic People's Republic of","Korea, Republic of","Kuwait", + "Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania", + "Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia","Mali", + "Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives", + "Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua", + "Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia", + "Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau", + "Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles","Sudan", + "Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname", + "Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo","Thailand", + "Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan", + "Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines","Venezuela", + "Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa", + "Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey", + "Saint Barthelemy","Saint Martin"}; +unsigned char *geoip_cache; +void geoip_readdb(void){ + struct stat bufa; + FILE *db=fopen("./db/GeoIP.dat","rb"); + fstat(fileno(db), &bufa); + geoip_cache = (unsigned char *) malloc(sizeof(unsigned char) * bufa.st_size); + if(fread(geoip_cache, sizeof(unsigned char), bufa.st_size, db) != bufa.st_size) { ShowError("geoip_cache reading didn't read all elements \n"); } + fclose(db); + ShowStatus("Finished Reading "CL_GREEN"GeoIP"CL_RESET" Database.\n"); +} +/* [Dekamaster/Nightroad] */ +/* WHY NOT A DBMAP: There are millions of entries in GeoIP and it has its own algorithm to go quickly through them, a DBMap wouldn't be efficient */ +const char* geoip_getcountry(uint32 ipnum){ + int depth; + unsigned int x; + const unsigned char *buf; + unsigned int offset = 0; + + for (depth = 31; depth >= 0; depth--) { + buf = geoip_cache + (long)6 *offset; + if (ipnum & (1 << depth)) { + /* Take the right-hand branch */ + x = (buf[3*1 + 0] << (0*8)) + + (buf[3*1 + 1] << (1*8)) + + (buf[3*1 + 2] << (2*8)); + } else { + /* Take the left-hand branch */ + x = (buf[3*0 + 0] << (0*8)) + + (buf[3*0 + 1] << (1*8)) + + (buf[3*0 + 2] << (2*8)); + } + if (x >= 16776960) { + x=x-16776960; + return geoip_countryname[x]; + } + offset = x; + } + return geoip_countryname[0]; +} +/* sends a mesasge to map server (fd) to a user (u_fd) although we use fd we keep aid for safe-check */ +/* extremely handy I believe it will serve other uses in the near future */ +void inter_to_fd(int fd, int u_fd, int aid, char* msg, ...) { + char msg_out[512]; + va_list ap; + int len = 1;/* yes we start at 1 */ + + va_start(ap,msg); + len += vsnprintf(msg_out, 512, msg, ap); + va_end(ap); + + WFIFOHEAD(fd,12 + len); + + WFIFOW(fd,0) = 0x3807; + WFIFOW(fd,2) = 12 + (unsigned short)len; + WFIFOL(fd,4) = u_fd; + WFIFOL(fd,8) = aid; + safestrncpy((char*)WFIFOP(fd,12), msg_out, len); + + WFIFOSET(fd,12 + len); + + return; +} +/* [Dekamaster/Nightroad] */ +void mapif_parse_accinfo(int fd) { + int u_fd = RFIFOL(fd,2), aid = RFIFOL(fd,6), castergroup = RFIFOL(fd,10); + char query[NAME_LENGTH], query_esq[NAME_LENGTH*2+1]; + int account_id; + char *data; + + safestrncpy(query, (char*) RFIFOP(fd,14), NAME_LENGTH); + + Sql_EscapeString(sql_handle, query_esq, query); + + account_id = atoi(query); + + if (account_id < START_ACCOUNT_NUM) { // is string + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name`,`class`,`base_level`,`job_level`,`online` FROM `char` WHERE `name` LIKE '%s' LIMIT 10", query_esq) + || Sql_NumRows(sql_handle) == 0 ) { + if( Sql_NumRows(sql_handle) == 0 ) { + inter_to_fd(fd, u_fd, aid, "No matches were found for your criteria, '%s'",query); + } else { + Sql_ShowDebug(sql_handle); + inter_to_fd(fd, u_fd, aid, "An error occured, bother your admin about it."); + } + Sql_FreeResult(sql_handle); + return; + } else { + if( Sql_NumRows(sql_handle) == 1 ) {//we found a perfect match + Sql_NextRow(sql_handle); + Sql_GetData(sql_handle, 0, &data, NULL); account_id = atoi(data); + Sql_FreeResult(sql_handle); + } else {// more than one, listing... [Dekamaster/Nightroad] + inter_to_fd(fd, u_fd, aid, "Your query returned the following %d results, please be more specific...",(int)Sql_NumRows(sql_handle)); + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + int class_; + short base_level, job_level, online; + char name[NAME_LENGTH]; + + Sql_GetData(sql_handle, 0, &data, NULL); account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name)); + Sql_GetData(sql_handle, 2, &data, NULL); class_ = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); base_level = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); job_level = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); online = atoi(data); + + inter_to_fd(fd, u_fd, aid, "[AID: %d] %s | %s | Level: %d/%d | %s", account_id, name, job_name(class_), base_level, job_level, online?"Online":"Offline"); + } + Sql_FreeResult(sql_handle); + return; + } + } + } + + /* it will only get here if we have a single match */ + if( account_id ) { + char userid[NAME_LENGTH], user_pass[NAME_LENGTH], email[40], last_ip[20], lastlogin[30]; + short level = -1; + int logincount = 0,state = 0; + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `userid`, `user_pass`, `email`, `last_ip`, `group_id`, `lastlogin`, `logincount`, `state` FROM `login` WHERE `account_id` = '%d' LIMIT 1", account_id) + || Sql_NumRows(sql_handle) == 0 ) { + if( Sql_NumRows(sql_handle) == 0 ) { + inter_to_fd(fd, u_fd, aid, "No account with ID '%d' was found.", account_id ); + } else { + inter_to_fd(fd, u_fd, aid, "An error occured, bother your admin about it."); + Sql_ShowDebug(sql_handle); + } + } else { + Sql_NextRow(sql_handle); + Sql_GetData(sql_handle, 0, &data, NULL); safestrncpy(userid, data, sizeof(userid)); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(user_pass, data, sizeof(user_pass)); + Sql_GetData(sql_handle, 2, &data, NULL); safestrncpy(email, data, sizeof(email)); + Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(last_ip, data, sizeof(last_ip)); + Sql_GetData(sql_handle, 4, &data, NULL); level = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); safestrncpy(lastlogin, data, sizeof(lastlogin)); + Sql_GetData(sql_handle, 6, &data, NULL); logincount = atoi(data); + Sql_GetData(sql_handle, 7, &data, NULL); state = atoi(data); + } + + Sql_FreeResult(sql_handle); + + if (level == -1) + return; + + inter_to_fd(fd, u_fd, aid, "-- Account %d --", account_id ); + inter_to_fd(fd, u_fd, aid, "User: %s | GM Group: %d | State: %d", userid, level, state ); + + if (level < castergroup) /* only show pass if your gm level is greater than the one you're searching for */ + inter_to_fd(fd, u_fd, aid, "Password: %s", user_pass ); + + inter_to_fd(fd, u_fd, aid, "Account e-mail: %s", email); + inter_to_fd(fd, u_fd, aid, "Last IP: %s (%s)", last_ip, geoip_getcountry(str2ip(last_ip)) ); + inter_to_fd(fd, u_fd, aid, "This user has logged %d times, the last time were at %s", logincount, lastlogin ); + inter_to_fd(fd, u_fd, aid, "-- Character Details --" ); + + + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`, `name`, `char_num`, `class`, `base_level`, `job_level`, `online` FROM `char` WHERE `account_id` = '%d' ORDER BY `char_num` LIMIT %d", account_id, MAX_CHARS) + || Sql_NumRows(sql_handle) == 0 ) { + + if( Sql_NumRows(sql_handle) == 0 ) + inter_to_fd(fd, u_fd, aid,"This account doesn't have characters."); + else { + inter_to_fd(fd, u_fd, aid,"An error occured, bother your admin about it."); + Sql_ShowDebug(sql_handle); + } + + } else { + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + int char_id, class_; + short char_num, base_level, job_level, online; + char name[NAME_LENGTH]; + + Sql_GetData(sql_handle, 0, &data, NULL); char_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name)); + Sql_GetData(sql_handle, 2, &data, NULL); char_num = atoi(data); + Sql_GetData(sql_handle, 3, &data, NULL); class_ = atoi(data); + Sql_GetData(sql_handle, 4, &data, NULL); base_level = atoi(data); + Sql_GetData(sql_handle, 5, &data, NULL); job_level = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); online = atoi(data); + + inter_to_fd(fd, u_fd, aid, "[Slot/CID: %d/%d] %s | %s | Level: %d/%d | %s", char_num, char_id, name, job_name(class_), base_level, job_level, online?"On":"Off"); + } + } + Sql_FreeResult(sql_handle); + } + + return; +} +//-------------------------------------------------------- +// Save registry to sql +int inter_accreg_tosql(int account_id, int char_id, struct accreg* reg, int type) +{ + struct global_reg* r; + StringBuf buf; + int i; + + if( account_id <= 0 ) + return 0; + reg->account_id = account_id; + reg->char_id = char_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + switch( type ) + { + case 3: //Char Reg + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'", reg_db, char_id) ) + Sql_ShowDebug(sql_handle); + account_id = 0; + break; + case 2: //Account Reg + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'", reg_db, account_id) ) + Sql_ShowDebug(sql_handle); + char_id = 0; + break; + case 1: //Account2 Reg + ShowError("inter_accreg_tosql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n"); + return 0; + default: + ShowError("inter_accreg_tosql: Invalid type %d\n", type); + return 0; + } + + if( reg->reg_num <= 0 ) + return 0; + + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s` (`type`,`account_id`,`char_id`,`str`,`value`) VALUES ", reg_db); + + for( i = 0; i < reg->reg_num; ++i ) { + r = ®->reg[i]; + if( r->str[0] != '\0' && r->value[0] != '\0' ) { + char str[32]; + char val[256]; + + if( i > 0 ) + StringBuf_AppendStr(&buf, ","); + + Sql_EscapeString(sql_handle, str, r->str); + Sql_EscapeString(sql_handle, val, r->value); + + StringBuf_Printf(&buf, "('%d','%d','%d','%s','%s')", type, account_id, char_id, str, val); + } + } + + if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) { + Sql_ShowDebug(sql_handle); + } + + StringBuf_Destroy(&buf); + + return 1; +} + +// Load account_reg from sql (type=2) +int inter_accreg_fromsql(int account_id,int char_id, struct accreg *reg, int type) +{ + struct global_reg* r; + char* data; + size_t len; + int i; + + if( reg == NULL) + return 0; + + memset(reg, 0, sizeof(struct accreg)); + reg->account_id = account_id; + reg->char_id = char_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + switch( type ) + { + case 3: //char reg + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'", reg_db, char_id) ) + Sql_ShowDebug(sql_handle); + break; + case 2: //account reg + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'", reg_db, account_id) ) + Sql_ShowDebug(sql_handle); + break; + case 1: //account2 reg + ShowError("inter_accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n"); + return 0; + default: + ShowError("inter_accreg_fromsql: Invalid type %d\n", type); + return 0; + } + for( i = 0; i < MAX_REG_NUM && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i ) + { + r = ®->reg[i]; + // str + Sql_GetData(sql_handle, 0, &data, &len); + memcpy(r->str, data, min(len, sizeof(r->str))); + // value + Sql_GetData(sql_handle, 1, &data, &len); + memcpy(r->value, data, min(len, sizeof(r->value))); + } + reg->reg_num = i; + Sql_FreeResult(sql_handle); + return 1; +} + +// Initialize +int inter_accreg_sql_init(void) +{ + CREATE(accreg_pt, struct accreg, 1); + return 0; + +} + +/*========================================== + * read config file + *------------------------------------------*/ +static int inter_config_read(const char* cfgName) +{ + int i; + char line[1024], w1[1024], w2[1024]; + FILE* fp; + + fp = fopen(cfgName, "r"); + if(fp == NULL) { + ShowError("File not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, sizeof(line), fp)) + { + i = sscanf(line, "%[^:]: %[^\r\n]", w1, w2); + if(i != 2) + continue; + + if(!strcmpi(w1,"char_server_ip")) { + strcpy(char_server_ip,w2); + } else + if(!strcmpi(w1,"char_server_port")) { + char_server_port = atoi(w2); + } else + if(!strcmpi(w1,"char_server_id")) { + strcpy(char_server_id,w2); + } else + if(!strcmpi(w1,"char_server_pw")) { + strcpy(char_server_pw,w2); + } else + if(!strcmpi(w1,"char_server_db")) { + strcpy(char_server_db,w2); + } else + if(!strcmpi(w1,"default_codepage")) { + strcpy(default_codepage,w2); + } + else if(!strcmpi(w1,"party_share_level")) + party_share_level = atoi(w2); + else if(!strcmpi(w1,"log_inter")) + log_inter = atoi(w2); + else if(!strcmpi(w1,"main_chat_nick")) + safestrncpy(main_chat_nick, w2, sizeof(main_chat_nick)); + else if(!strcmpi(w1,"import")) + inter_config_read(w2); + } + fclose(fp); + + ShowInfo ("Done reading %s.\n", cfgName); + + return 0; +} + +// Save interlog into sql +int inter_log(char* fmt, ...) +{ + char str[255]; + char esc_str[sizeof(str)*2+1];// escaped str + va_list ap; + + va_start(ap,fmt); + vsnprintf(str, sizeof(str), fmt, ap); + va_end(ap); + + Sql_EscapeStringLen(sql_handle, esc_str, str, strnlen(str, sizeof(str))); + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')", interlog_db, esc_str) ) + Sql_ShowDebug(sql_handle); + + return 0; +} + +// initialize +int inter_init_sql(const char *file) +{ + //int i; + + inter_config_read(file); + + //DB connection initialized + sql_handle = Sql_Malloc(); + ShowInfo("Connect Character DB server.... (Character Server)\n"); + if( SQL_ERROR == Sql_Connect(sql_handle, char_server_id, char_server_pw, char_server_ip, (uint16)char_server_port, char_server_db) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(sql_handle); + exit(EXIT_FAILURE); + } + + if( *default_codepage ) { + if( SQL_ERROR == Sql_SetEncoding(sql_handle, default_codepage) ) + Sql_ShowDebug(sql_handle); + } + + wis_db = idb_alloc(DB_OPT_RELEASE_DATA); + inter_guild_sql_init(); + inter_storage_sql_init(); + inter_party_sql_init(); + inter_pet_sql_init(); + inter_homunculus_sql_init(); + inter_mercenary_sql_init(); + inter_elemental_sql_init(); + inter_accreg_sql_init(); + inter_mail_sql_init(); + inter_auction_sql_init(); + + geoip_readdb(); + msg_config_read("conf/msg_athena.conf"); + return 0; +} + +// finalize +void inter_final(void) +{ + wis_db->destroy(wis_db, NULL); + + inter_guild_sql_final(); + inter_storage_sql_final(); + inter_party_sql_final(); + inter_pet_sql_final(); + inter_homunculus_sql_final(); + inter_mercenary_sql_final(); + inter_elemental_sql_final(); + inter_mail_sql_final(); + inter_auction_sql_final(); + + if (accreg_pt) aFree(accreg_pt); + + do_final_msg(); + return; +} + +int inter_mapif_init(int fd) +{ + return 0; +} + + +//-------------------------------------------------------- + +// broadcast sending +int mapif_broadcast(unsigned char *mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, int sfd) +{ + unsigned char *buf = (unsigned char*)aMalloc((len)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = len; + WBUFL(buf,4) = fontColor; + WBUFW(buf,8) = fontType; + WBUFW(buf,10) = fontSize; + WBUFW(buf,12) = fontAlign; + WBUFW(buf,14) = fontY; + memcpy(WBUFP(buf,16), mes, len - 16); + mapif_sendallwos(sfd, buf, len); + + if (buf) + aFree(buf); + return 0; +} + +// Wis sending +int mapif_wis_message(struct WisData *wd) +{ + unsigned char buf[2048]; + if (wd->len > 2047-56) wd->len = 2047-56; //Force it to fit to avoid crashes. [Skotlex] + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 +wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, NAME_LENGTH); + memcpy(WBUFP(buf,32), wd->dst, NAME_LENGTH); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf,WBUFW(buf,2)); + + return 0; +} + +// Wis sending result +int mapif_wis_end(struct WisData *wd, int flag) +{ + unsigned char buf[27]; + + WBUFW(buf, 0)=0x3802; + memcpy(WBUFP(buf, 2),wd->src,24); + WBUFB(buf,26)=flag; + mapif_send(wd->fd,buf,27); + 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)); +} + +// Send the requested account_reg +int mapif_account_reg_reply(int fd,int account_id,int char_id, int type) +{ + struct accreg *reg=accreg_pt; + WFIFOHEAD(fd, 13 + 5000); + inter_accreg_fromsql(account_id,char_id,reg,type); + + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=char_id; + WFIFOB(fd,12)=type; + if(reg->reg_num==0){ + WFIFOW(fd,2)=13; + }else{ + int i,p; + for (p=13,i = 0; i < reg->reg_num && p < 5000; 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; + if (p>= 5000) + ShowWarning("Too many acc regs for %d:%d, not all values were loaded.\n", account_id, char_id); + } + 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) + { + WFIFOHEAD(fd,7); + WFIFOW(fd,0) = 0x2b1f; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = reason; + WFIFOSET(fd,7); + return 0; + } + return -1; +} + +//-------------------------------------------------------- + +/** + * Existence check of WISP data + * @see DBApply + */ +int check_ttl_wisdata_sub(DBKey key, DBData *data, va_list ap) +{ + unsigned long tick; + struct WisData *wd = db_data2ptr(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; +} + +//-------------------------------------------------------- + +// broadcast sending +int mapif_parse_broadcast(int fd) +{ + mapif_broadcast(RFIFOP(fd,16), RFIFOW(fd,2), RFIFOL(fd,4), RFIFOW(fd,8), RFIFOW(fd,10), RFIFOW(fd,12), RFIFOW(fd,14), fd); + return 0; +} + + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) +{ + struct WisData* wd; + static int wisid = 0; + char name[NAME_LENGTH]; + char esc_name[NAME_LENGTH*2+1];// escaped name + char* data; + size_t len; + + + if ( fd <= 0 ) {return 0;} // check if we have a valid fd + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + ShowWarning("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + ShowError("inter: Wis message doesn't exist.\n"); + return 0; + } + + safestrncpy(name, (char*)RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex] + + Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `name` FROM `%s` WHERE `name`='%s'", char_db, esc_name) ) + Sql_ShowDebug(sql_handle); + + // search if character exists before to ask all map-servers + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } + else + {// Character exists. So, ask all map-servers + // to be sure of the correct name, rewrite it + Sql_GetData(sql_handle, 0, &data, &len); + memset(name, 0, NAME_LENGTH); + memcpy(name, data, min(len, NAME_LENGTH)); + // if source is destination, don't ask other servers. + if( strncmp((const char*)RFIFOP(fd,4), name, NAME_LENGTH) == 0 ) + { + uint8 buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } + else + { + + CREATE(wd, struct WisData, 1); + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), NAME_LENGTH); + memcpy(wd->dst, RFIFOP(fd,28), NAME_LENGTH); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + idb_put(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + Sql_FreeResult(sql_handle); + return 0; +} + + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) +{ + int id, flag; + struct WisData *wd; + + 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 of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + idb_remove(wis_db, id); + } + + return 0; +} + +// Received wisp message from map-server for ALL gm (just copy the message and resends it to ALL map-servers) +int mapif_parse_WisToGM(int fd) +{ + unsigned char buf[2048]; // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B + + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf, 0) = 0x3803; + mapif_sendall(buf, RFIFOW(fd,2)); + + return 0; +} + +// Save account_reg into sql (type=2) +int mapif_parse_Registry(int fd) +{ + int j,p,len, max; + struct accreg *reg=accreg_pt; + + memset(accreg_pt,0,sizeof(struct accreg)); + switch (RFIFOB(fd, 12)) { + case 3: //Character registry + max = GLOBAL_REG_NUM; + break; + case 2: //Account Registry + max = ACCOUNT_REG_NUM; + break; + case 1: //Account2 registry, must be sent over to login server. + return save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4); + default: + return 1; + } + for(j=0,p=13;j<max && 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; + + inter_accreg_tosql(RFIFOL(fd,4),RFIFOL(fd,8),reg, RFIFOB(fd,12)); + mapif_account_reg(fd,RFIFOP(fd,0)); // Send updated accounts to other map servers. + return 0; +} + +// Request the value of all registries. +int mapif_parse_RegistryRequest(int fd) +{ + //Load Char Registry + if (RFIFOB(fd,12)) mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),3); + //Load Account Registry + if (RFIFOB(fd,11)) mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),2); + //Ask Login Server for Account2 values. + if (RFIFOB(fd,10)) request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6)); + 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; + + 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; +} + +//-------------------------------------------------------- + +/// Returns the length of the next complete packet to process, +/// or 0 if no complete packet exists in the queue. +/// +/// @param length The minimum allowed length, or -1 for dynamic lookup +int inter_check_length(int fd, int length) +{ + if( length == -1 ) + {// variable-length packet + if( RFIFOREST(fd) < 4 ) + return 0; + length = RFIFOW(fd,2); + } + + if( (int)RFIFOREST(fd) < length ) + return 0; + + return length; +} + +int inter_parse_frommap(int fd) +{ + int cmd; + int len = 0; + cmd = RFIFOW(fd,0); + // Check is valid packet entry + if(cmd < 0x3000 || cmd >= 0x3000 + ARRAYLENGTH(inter_recv_packet_length) || inter_recv_packet_length[cmd - 0x3000] == 0) + return 0; + + // Check packet length + if((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd) { + case 0x3000: mapif_parse_broadcast(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; + case 0x3007: mapif_parse_accinfo(fd); break; + /* 0x3008 is used by the report stuff */ + default: + if( inter_party_parse_frommap(fd) + || inter_guild_parse_frommap(fd) + || inter_storage_parse_frommap(fd) + || inter_pet_parse_frommap(fd) + || inter_homunculus_parse_frommap(fd) + || inter_mercenary_parse_frommap(fd) + || inter_elemental_parse_frommap(fd) + || inter_mail_parse_frommap(fd) + || inter_auction_parse_frommap(fd) + || inter_quest_parse_frommap(fd) + ) + break; + else + return 0; + } + + RFIFOSKIP(fd, len); + return 1; +} + +uint64 inter_chk_lastuid(int8 flag, uint64 value){ + static uint64 last_updt_uid = 0; + static int8 update = 0; + if(flag) + { + if(last_updt_uid < value){ + last_updt_uid = value; + update = 1; + } + + return 0; + }else if(update) + { + update = 0; + return last_updt_uid; + } + return 0; +} + + diff --git a/src/char/inter.h b/src/char/inter.h new file mode 100644 index 000000000..7ea9cf25c --- /dev/null +++ b/src/char/inter.h @@ -0,0 +1,44 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTER_SQL_H_ +#define _INTER_SQL_H_ + +struct accreg; +#include "../common/sql.h" + +int inter_init_sql(const char *file); +void inter_final(void); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); +int mapif_send_gmaccounts(void); +int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern unsigned int party_share_level; + +extern Sql* sql_handle; +extern Sql* lsql_handle; + +extern char main_chat_nick[16]; + +int inter_accreg_tosql(int account_id, int char_id, struct accreg *reg, int type); + +uint64 inter_chk_lastuid(int8 flag, uint64 value); +#ifdef NSI_UNIQUE_ID + #define updateLastUid(val_) inter_chk_lastuid(1, val_) + #define dbUpdateUid(handler_)\ + { \ + uint64 unique_id_ = inter_chk_lastuid(0, 0); \ + if (unique_id_ && SQL_ERROR == Sql_Query(handler_, "UPDATE `interreg` SET `value`='%"PRIu64"' WHERE `varname`='unique_id'", unique_id_)) \ + Sql_ShowDebug(handler_);\ + } +#else + #define dbUpdateUid(handler_) + #define updateLastUid(val_) +#endif + +#endif /* _INTER_SQL_H_ */ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 000000000..e8c6c0a70 --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,163 @@ + +# +# Create svnversion.h +# +message( STATUS "Creating svnversion.h" ) +if( SVNVERSION ) + file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h + "#ifndef SVNVERSION\n#define SVNVERSION ${SVNVERSION}\n#endif\n" ) +else() + file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h "" ) +endif() +set( GLOBAL_INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "" ) +set( SVNVERSION ${SVNVERSION} + CACHE STRING "SVN version of the source code" ) +if( INSTALL_COMPONENT_DEVELOPMENT ) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h + DESTINATION "src/common" + COMPONENT Development_base ) +endif( INSTALL_COMPONENT_DEVELOPMENT ) +message( STATUS "Creating svnversion.h - done" ) + + +##################################################################### +# setup +# +set( COMMON_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + CACHE PATH "common source directory" ) +mark_as_advanced( COMMON_SOURCE_DIR ) + +set( COMMON_ALL_HEADERS + "${CMAKE_CURRENT_BINARY_DIR}/svnversion.h" + "${COMMON_SOURCE_DIR}/cbasetypes.h" + "${COMMON_SOURCE_DIR}/mmo.h" + ) + +set( COMMON_MINI_HEADERS + ${COMMON_ALL_HEADERS} + "${COMMON_SOURCE_DIR}/core.h" + "${COMMON_SOURCE_DIR}/malloc.h" + "${COMMON_SOURCE_DIR}/showmsg.h" + "${COMMON_SOURCE_DIR}/strlib.h" + ${LIBCONFIG_HEADERS} # needed by showmsg.h + CACHE INTERNAL "" ) +set( COMMON_MINI_SOURCES + "${COMMON_SOURCE_DIR}/core.c" + "${COMMON_SOURCE_DIR}/malloc.c" + "${COMMON_SOURCE_DIR}/showmsg.c" + "${COMMON_SOURCE_DIR}/strlib.c" + ${LIBCONFIG_SOURCES} # needed by showmsg.c + CACHE INTERNAL "" ) +set( COMMON_MINI_INCLUDE_DIRS ${LIBCONFIG_INCLUDE_DIRS} CACHE INTERNAL "" ) +set( COMMON_MINI_DEFINITIONS "-DMINICORE ${LIBCONFIG_DEFINITIONS}" CACHE INTERNAL "" ) + + +# +# common_base +# +if( WITH_ZLIB ) +message( STATUS "Creating target common_base" ) +set( COMMON_BASE_HEADERS + ${COMMON_ALL_HEADERS} + "${COMMON_SOURCE_DIR}/conf.h" + "${COMMON_SOURCE_DIR}/core.h" + "${COMMON_SOURCE_DIR}/db.h" + "${COMMON_SOURCE_DIR}/des.h" + "${COMMON_SOURCE_DIR}/ers.h" + "${COMMON_SOURCE_DIR}/grfio.h" + "${COMMON_SOURCE_DIR}/malloc.h" + "${COMMON_SOURCE_DIR}/mapindex.h" + "${COMMON_SOURCE_DIR}/md5calc.h" + "${COMMON_SOURCE_DIR}/nullpo.h" + "${COMMON_SOURCE_DIR}/random.h" + "${COMMON_SOURCE_DIR}/showmsg.h" + "${COMMON_SOURCE_DIR}/socket.h" + "${COMMON_SOURCE_DIR}/strlib.h" + "${COMMON_SOURCE_DIR}/timer.h" + "${COMMON_SOURCE_DIR}/utils.h" + "${COMMON_SOURCE_DIR}/atomic.h" + "${COMMON_SOURCE_DIR}/spinlock.h" + "${COMMON_SOURCE_DIR}/thread.h" + "${COMMON_SOURCE_DIR}/mutex.h" + "${COMMON_SOURCE_DIR}/raconf.h" + "${COMMON_SOURCE_DIR}/mempool.h" + ${LIBCONFIG_HEADERS} # needed by conf.h/showmsg.h + CACHE INTERNAL "common_base headers" ) +set( COMMON_BASE_SOURCES + "${COMMON_SOURCE_DIR}/conf.c" + "${COMMON_SOURCE_DIR}/core.c" + "${COMMON_SOURCE_DIR}/db.c" + "${COMMON_SOURCE_DIR}/des.c" + "${COMMON_SOURCE_DIR}/ers.c" + "${COMMON_SOURCE_DIR}/grfio.c" + "${COMMON_SOURCE_DIR}/malloc.c" + "${COMMON_SOURCE_DIR}/mapindex.c" + "${COMMON_SOURCE_DIR}/md5calc.c" + "${COMMON_SOURCE_DIR}/nullpo.c" + "${COMMON_SOURCE_DIR}/random.c" + "${COMMON_SOURCE_DIR}/showmsg.c" + "${COMMON_SOURCE_DIR}/socket.c" + "${COMMON_SOURCE_DIR}/strlib.c" + "${COMMON_SOURCE_DIR}/timer.c" + "${COMMON_SOURCE_DIR}/utils.c" + "${COMMON_SOURCE_DIR}/thread.c" + "${COMMON_SOURCE_DIR}/mutex.c" + "${COMMON_SOURCE_DIR}/mempool.c" + "${COMMON_SOURCE_DIR}/raconf.c" + ${LIBCONFIG_SOURCES} # needed by conf.c/showmsg.c + CACHE INTERNAL "common_base sources" ) +set( COMMON_BASE_INCLUDE_DIRS + ${LIBCONFIG_INCLUDE_DIRS} + CACHE INTERNAL "common_base include dirs" ) +set( COMMON_BASE_DEFINITIONS + ${LIBCONFIG_DEFINITIONS} + CACHE INTERNAL "common_base definitions" ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ${ZLIB_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${MT19937AR_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS}" ) +set( SOURCE_FILES ${MT19937AR_HEADERS} ${MT19937AR_SOURCES} ${COMMON_BASE_HEADERS} ${COMMON_BASE_SOURCES} ) +source_group( mt19937ar FILES ${MT19937AR_HEADERS} ${MT19937AR_SOURCES} ) +source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_BASE_SOURCES} ) +add_library( common_base ${SOURCE_FILES} ) +target_link_libraries( common_base ${LIBRARIES} ) +set_target_properties( common_base PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +include_directories( ${INCLUDE_DIRS} ) +set( HAVE_common_base ON CACHE INTERNAL "" ) +set( TARGET_LIST ${TARGET_LIST} common_base CACHE INTERNAL "" ) +message( STATUS "Creating target common_base - done" ) +else() +message( STATUS "Skipping target common_base (requires ZLIB)" ) +unset( HAVE_common_base CACHE ) +endif() + + +# +# common_sql +# +if( HAVE_common_base AND WITH_MYSQL ) +message( STATUS "Creating target common_sql" ) +set( COMMON_SQL_HEADERS + ${COMMON_ALL_HEADERS} + "${CMAKE_CURRENT_SOURCE_DIR}/sql.h" + CACHE INTERNAL "common_sql headers" ) +set( COMMON_SQL_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/sql.c" + CACHE INTERNAL "common_sql sources" ) +set( DEPENDENCIES common_base ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ${MYSQL_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${MYSQL_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS}" ) +set( SOURCE_FILES ${COMMON_SQL_HEADERS} ${COMMON_SQL_SOURCES} ) +source_group( common FILES ${COMMON_SQL_HEADERS} ${COMMON_SQL_SOURCES} ) +add_library( common_sql ${SOURCE_FILES} ) +add_dependencies( common_sql ${DEPENDENCIES} ) +target_link_libraries( common_sql ${LIBRARIES} ${DEPENDENCIES} ) +set_target_properties( common_sql PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +include_directories( ${INCLUDE_DIRS} ) +set( HAVE_common_sql ON CACHE INTERNAL "" ) +set( TARGET_LIST ${TARGET_LIST} common_sql CACHE INTERNAL "" ) +message( STATUS "Creating target common_sql - done" ) +else() +message( STATUS "Skipping target common_sql (requires common_base and MYSQL)" ) +unset( HAVE_common_sql CACHE ) +endif() diff --git a/src/common/Makefile.in b/src/common/Makefile.in new file mode 100644 index 000000000..c24499c02 --- /dev/null +++ b/src/common/Makefile.in @@ -0,0 +1,97 @@ + +COMMON_OBJ = obj_all/core.o obj_all/socket.o obj_all/timer.o obj_all/db.o \ + obj_all/nullpo.o obj_all/malloc.o obj_all/showmsg.o obj_all/strlib.o obj_all/utils.o \ + obj_all/grfio.o obj_all/mapindex.o obj_all/ers.o obj_all/md5calc.o \ + obj_all/minicore.o obj_all/minisocket.o obj_all/minimalloc.o obj_all/random.o obj_all/des.o \ + obj_all/conf.o obj_all/thread.o obj_all/mutex.o obj_all/raconf.o obj_all/mempool.o + +COMMON_H = $(shell ls ../common/*.h) + +COMMON_SQL_OBJ = obj_sql/sql.o +COMMON_SQL_H = sql.h + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +HAVE_MYSQL=@HAVE_MYSQL@ +ifeq ($(HAVE_MYSQL),yes) + ALL_DEPENDS=sql + SQL_DEPENDS=common common_sql +else + SQL_DEPENDS=needs_mysql +endif + +@SET_MAKE@ + +##################################################################### +.PHONY : all sql clean help + +all: $(ALL_DEPENDS) + +sql: $(SQL_DEPENDS) + +clean: + @echo " CLEAN common" + @rm -rf *.o obj_all obj_sql + +help: + @echo "possible targets are 'sql' 'all' 'clean' 'help'" + @echo "'sql' - builds object files used in sql servers" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +needs_mysql: + @echo "MySQL not found or disabled by the configure script" + @exit 1 + +obj_all: + @echo " MKDIR obj_all" + @-mkdir obj_all + +obj_sql: + @echo " MKDIR obj_sql" + @-mkdir obj_sql + +obj_all/common.a: $(COMMON_OBJ) + @echo " AR $@" + @@AR@ rcs obj_all/common.a $(COMMON_OBJ) + +obj_sql/common_sql.a: $(COMMON_SQL_OBJ) + @echo " AR $@" + @@AR@ rcs obj_sql/common_sql.a $(COMMON_SQL_OBJ) + + +common: obj_all $(COMMON_OBJ) $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) obj_all/common.a + +common_sql: obj_sql $(COMMON_SQL_OBJ) obj_sql/common_sql.a + +obj_all/%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +obj_all/mini%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +obj_sql/%.o: %.c $(COMMON_H) $(COMMON_SQL_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + + +# missing object files +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/common/atomic.h b/src/common/atomic.h new file mode 100644 index 000000000..b1a4bda92 --- /dev/null +++ b/src/common/atomic.h @@ -0,0 +1,142 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_ATOMIC_H_ +#define _rA_ATOMIC_H_ + +// Atomic Operations +// (Interlocked CompareExchange, Add .. and so on ..) +// +// Implementation varies / depends on: +// - Architecture +// - Compiler +// - Operating System +// +// our Abstraction is fully API-Compatible to Microsofts implementation @ NT5.0+ +// +#include "../common/cbasetypes.h" + +#if defined(_MSC_VER) +#include "../common/winapi.h" + +#if !defined(_M_X64) +// When compiling for windows 32bit, the 8byte interlocked operations are not provided by microsoft +// (because they need at least i586 so its not generic enough.. ... ) +forceinline int64 InterlockedCompareExchange64(volatile int64 *dest, int64 exch, int64 _cmp){ + _asm{ + lea esi,_cmp; + lea edi,exch; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,dest; + + lock CMPXCHG8B [esi]; + } +} + + +forceinline volatile int64 InterlockedIncrement64(volatile int64 *addend){ + __int64 old; + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old+1), old) != old); + + return (old + 1); +} + + + +forceinline volatile int64 InterlockedDecrement64(volatile int64 *addend){ + __int64 old; + + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old-1), old) != old); + + return (old - 1); +} + +forceinline volatile int64 InterlockedExchangeAdd64(volatile int64 *addend, int64 increment){ + __int64 old; + + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old + increment), old) != old); + + return old; +} + +forceinline volatile int64 InterlockedExchange64(volatile int64 *target, int64 val){ + __int64 old; + do{ + old = *target; + }while(InterlockedCompareExchange64(target, val, old) != old); + + return old; +} + +#endif //endif 32bit windows + +#elif defined(__GNUC__) + +#if !defined(__x86_64__) && !defined(__i386__) +#error Your Target Platfrom is not supported +#endif + +static forceinline int64 InterlockedExchangeAdd64(volatile int64 *addend, int64 increment){ + return __sync_fetch_and_add(addend, increment); +}//end: InterlockedExchangeAdd64() + + +static forceinline int32 InterlockedExchangeAdd(volatile int32 *addend, int32 increment){ + return __sync_fetch_and_add(addend, increment); +}//end: InterlockedExchangeAdd() + + +static forceinline int64 InterlockedIncrement64(volatile int64 *addend){ + return __sync_add_and_fetch(addend, 1); +}//end: InterlockedIncrement64() + + +static forceinline int32 InterlockedIncrement(volatile int32 *addend){ + return __sync_add_and_fetch(addend, 1); +}//end: InterlockedIncrement() + + +static forceinline int64 InterlockedDecrement64(volatile int64 *addend){ + return __sync_sub_and_fetch(addend, 1); +}//end: InterlockedDecrement64() + + +static forceinline int32 InterlockedDecrement(volatile int32 *addend){ + return __sync_sub_and_fetch(addend, 1); +}//end: InterlockedDecrement() + + +static forceinline int64 InterlockedCompareExchange64(volatile int64 *dest, int64 exch, int64 cmp){ + return __sync_val_compare_and_swap(dest, cmp, exch); +}//end: InterlockedCompareExchange64() + + +static forceinline int32 InterlockedCompareExchange(volatile int32 *dest, int32 exch, int32 cmp){ + return __sync_val_compare_and_swap(dest, cmp, exch); +}//end: InterlockedCompareExchnage() + + +static forceinline int64 InterlockedExchange64(volatile int64 *target, int64 val){ + return __sync_lock_test_and_set(target, val); +}//end: InterlockedExchange64() + + +static forceinline int32 InterlockedExchange(volatile int32 *target, int32 val){ + return __sync_lock_test_and_set(target, val); +}//end: InterlockedExchange() + + +#endif //endif compiler decission + + +#endif diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h new file mode 100644 index 000000000..731a8b578 --- /dev/null +++ b/src/common/cbasetypes.h @@ -0,0 +1,404 @@ +#ifndef _CBASETYPES_H_ +#define _CBASETYPES_H_ + +/* +--------+-----------+--------+---------+ + * | ILP32 | LP64 | ILP64 | (LL)P64 | + * +------------+--------+-----------+--------+---------+ + * | ints | 32-bit | 32-bit | 64-bit | 32-bit | + * | longs | 32-bit | 64-bit | 64-bit | 32-bit | + * | long-longs | 64-bit | 64-bit | 64-bit | 64-bit | + * | pointers | 32-bit | 64-bit | 64-bit | 64-bit | + * +------------+--------+-----------+--------+---------+ + * | where | -- | Tiger | Alpha | Windows | + * | used | | Unix | Cray | | + * | | | Sun & SGI | | | + * +------------+--------+-----------+--------+---------+ + * Taken from http://developer.apple.com/macosx/64bit.html + */ + +////////////////////////////////////////////////////////////////////////// +// basic include for all basics +// introduces types and global functions +////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// setting some defines on platforms +////////////////////////////////////////////////////////////////////////// +#if (defined(__WIN32__) || defined(__WIN32) || defined(_WIN32) || defined(_WIN64) || defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(WIN32) +#define WIN32 +#endif + +#if defined(__MINGW32__) && !defined(MINGW) +#define MINGW +#endif + +#if (defined(__CYGWIN__) || defined(__CYGWIN32__)) && !defined(CYGWIN) +#define CYGWIN +#endif + +// __APPLE__ is the only predefined macro on MacOS X +#if defined(__APPLE__) +#define __DARWIN__ +#endif + +// 64bit OS +#if defined(_M_IA64) || defined(_M_X64) || defined(_WIN64) || defined(_LP64) || defined(_ILP64) || defined(__LP64__) || defined(__ppc64__) +#define __64BIT__ +#endif + +#if defined(_ILP64) +#error "this specific 64bit architecture is not supported" +#endif + +// debug mode +#if defined(_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif + +// debug function name +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif + + +// disable attributed stuff on non-GNU +#if !defined(__GNUC__) && !defined(MINGW) +# define __attribute__(x) +#endif + +////////////////////////////////////////////////////////////////////////// +// portable printf/scanf format macros and integer definitions +// NOTE: Visual C++ uses <inttypes.h> and <stdint.h> provided in /3rdparty +////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include <inttypes.h> +#include <stdint.h> +#include <limits.h> + +// temporary fix for bugreport:4961 (unintended conversion from signed to unsigned) +// (-20 >= UCHAR_MAX) returns true +// (-20 >= USHRT_MAX) returns true +#if defined(__FreeBSD__) && defined(__x86_64) +#undef UCHAR_MAX +#define UCHAR_MAX (unsigned char)0xff +#undef USHRT_MAX +#define USHRT_MAX (unsigned short)0xffff +#endif + +// ILP64 isn't supported, so always 32 bits? +#ifndef UINT_MAX +#define UINT_MAX 0xffffffff +#endif + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _exact_ size. +////////////////////////////////////////////////////////////////////////// + +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +typedef int8_t sint8; +typedef int16_t sint16; +typedef int32_t sint32; +typedef int64_t sint64; + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +#undef UINT8_MIN +#undef UINT16_MIN +#undef UINT32_MIN +#undef UINT64_MIN +#define UINT8_MIN ((uint8) UINT8_C(0x00)) +#define UINT16_MIN ((uint16)UINT16_C(0x0000)) +#define UINT32_MIN ((uint32)UINT32_C(0x00000000)) +#define UINT64_MIN ((uint64)UINT64_C(0x0000000000000000)) + +#undef UINT8_MAX +#undef UINT16_MAX +#undef UINT32_MAX +#undef UINT64_MAX +#define UINT8_MAX ((uint8) UINT8_C(0xFF)) +#define UINT16_MAX ((uint16)UINT16_C(0xFFFF)) +#define UINT32_MAX ((uint32)UINT32_C(0xFFFFFFFF)) +#define UINT64_MAX ((uint64)UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#undef SINT8_MIN +#undef SINT16_MIN +#undef SINT32_MIN +#undef SINT64_MIN +#define SINT8_MIN ((sint8) INT8_C(0x80)) +#define SINT16_MIN ((sint16)INT16_C(0x8000)) +#define SINT32_MIN ((sint32)INT32_C(0x80000000)) +#define SINT64_MIN ((sint32)INT64_C(0x8000000000000000)) + +#undef SINT8_MAX +#undef SINT16_MAX +#undef SINT32_MAX +#undef SINT64_MAX +#define SINT8_MAX ((sint8) INT8_C(0x7F)) +#define SINT16_MAX ((sint16)INT16_C(0x7FFF)) +#define SINT32_MAX ((sint32)INT32_C(0x7FFFFFFF)) +#define SINT64_MAX ((sint64)INT64_C(0x7FFFFFFFFFFFFFFF)) + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _minimum_ size. +// These could be larger than you expect, +// they are designed for speed. +////////////////////////////////////////////////////////////////////////// +typedef long int ppint; +typedef long int ppint8; +typedef long int ppint16; +typedef long int ppint32; + +typedef unsigned long int ppuint; +typedef unsigned long int ppuint8; +typedef unsigned long int ppuint16; +typedef unsigned long int ppuint32; + + +////////////////////////////////////////////////////////////////////////// +// integer with exact processor width (and best speed) +////////////////////////////// +#include <stddef.h> // size_t + +#if defined(WIN32) && !defined(MINGW) // does not have a signed size_t +////////////////////////////// +#if defined(_WIN64) // naive 64bit windows platform +typedef __int64 ssize_t; +#else +typedef int ssize_t; +#endif +////////////////////////////// +#endif +////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// pointer sized integers +////////////////////////////////////////////////////////////////////////// +typedef intptr_t intptr; +typedef uintptr_t uintptr; + + +////////////////////////////////////////////////////////////////////////// +// Add a 'sysint' Type which has the width of the platform we're compiled for. +////////////////////////////////////////////////////////////////////////// +#if defined(__GNUC__) + #if defined(__x86_64__) + typedef int64 sysint; + typedef uint64 usysint; + #else + typedef int32 sysint; + typedef uint32 usysint; + #endif +#elif defined(_MSC_VER) + #if defined(_M_X64) + typedef int64 sysint; + typedef uint64 usysint; + #else + typedef int32 sysint; + typedef uint32 usysint; + #endif +#else + #error Compiler / Platform is unsupported. +#endif + + +////////////////////////////////////////////////////////////////////////// +// some redefine of function redefines for some Compilers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define strncmpi strnicmp +#define snprintf _snprintf +#if defined(_MSC_VER) && _MSC_VER < 1400 +#define vsnprintf _vsnprintf +#endif +#else +#define strcmpi strcasecmp +#define stricmp strcasecmp +#define strncmpi strncasecmp +#define strnicmp strncasecmp +#endif +#if defined(_MSC_VER) && _MSC_VER > 1200 +#define strtoull _strtoui64 +#endif + +// keyword replacement +#ifdef _MSC_VER +// For MSVC (windows) +#define inline __inline +#define forceinline __forceinline +#define ra_align(n) __declspec(align(n)) +#else +// For GCC +#define forceinline __attribute__((always_inline)) inline +#define ra_align(n) __attribute__(( aligned(n) )) +#endif + + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef char bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not __cplusplus +////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// macro tools + +#ifdef swap // just to be sure +#undef swap +#endif +// hmm only ints? +//#define swap(a,b) { int temp=a; a=b; b=temp;} +// if using macros then something that is type independent +//#define swap(a,b) ((a == b) || ((a ^= b), (b ^= a), (a ^= b))) +// Avoid "value computed is not used" warning and generates the same assembly code +#define swap(a,b) if (a != b) ((a ^= b), (b ^= a), (a ^= b)) + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +////////////////////////////////////////////////////////////////////////// +// should not happen +#ifndef NULL +#define NULL (void *)0 +#endif + +////////////////////////////////////////////////////////////////////////// +// number of bits in a byte +#ifndef NBBY +#define NBBY 8 +#endif + +////////////////////////////////////////////////////////////////////////// +// path separator + +#if defined(WIN32) +#define PATHSEP '\\' +#define PATHSEP_STR "\\" +#elif defined(__APPLE__) +// FIXME Mac OS X is unix based, is this still correct? +#define PATHSEP ':' +#define PATHSEP_STR ":" +#else +#define PATHSEP '/' +#define PATHSEP_STR "/" +#endif + +////////////////////////////////////////////////////////////////////////// +// Assert + +#if ! defined(Assert) +#if defined(RELEASE) +#define Assert(EX) +#else +// extern "C" { +#include <assert.h> +// } +#if !defined(DEFCPP) && defined(WIN32) && !defined(MINGW) +#include <crtdbg.h> +#endif +#define Assert(EX) assert(EX) +#endif +#endif /* ! defined(Assert) */ + +////////////////////////////////////////////////////////////////////////// +// Has to be unsigned to avoid problems in some systems +// Problems arise when these functions expect an argument in the range [0,256[ and are fed a signed char. +#include <ctype.h> +#define ISALNUM(c) (isalnum((unsigned char)(c))) +#define ISALPHA(c) (isalpha((unsigned char)(c))) +#define ISCNTRL(c) (iscntrl((unsigned char)(c))) +#define ISDIGIT(c) (isdigit((unsigned char)(c))) +#define ISGRAPH(c) (isgraph((unsigned char)(c))) +#define ISLOWER(c) (islower((unsigned char)(c))) +#define ISPRINT(c) (isprint((unsigned char)(c))) +#define ISPUNCT(c) (ispunct((unsigned char)(c))) +#define ISSPACE(c) (isspace((unsigned char)(c))) +#define ISUPPER(c) (isupper((unsigned char)(c))) +#define ISXDIGIT(c) (isxdigit((unsigned char)(c))) +#define TOASCII(c) (toascii((unsigned char)(c))) +#define TOLOWER(c) (tolower((unsigned char)(c))) +#define TOUPPER(c) (toupper((unsigned char)(c))) + +////////////////////////////////////////////////////////////////////////// +// length of a static array +#define ARRAYLENGTH(A) ( sizeof(A)/sizeof((A)[0]) ) + +////////////////////////////////////////////////////////////////////////// +// Make sure va_copy exists +#include <stdarg.h> // va_list, va_copy(?) +#if !defined(va_copy) +#if defined(__va_copy) +#define va_copy __va_copy +#else +#define va_copy(dst, src) ((void) memcpy(&(dst), &(src), sizeof(va_list))) +#endif +#endif + + +////////////////////////////////////////////////////////////////////////// +// Use the preprocessor to 'stringify' stuff (concert to a string). +// example: +// #define TESTE blabla +// QUOTE(TESTE) -> "TESTE" +// EXPAND_AND_QUOTE(TESTE) -> "blabla" +#define QUOTE(x) #x +#define EXPAND_AND_QUOTE(x) QUOTE(x) + + +////////////////////////////////////////////////////////////////////////// +// Set a pointer variable to a pointer value. +#ifdef __cplusplus +template <typename T1, typename T2> +void SET_POINTER(T1*&var, T2* p) +{ + var = static_cast<T1*>(p); +} +template <typename T1, typename T2> +void SET_FUNCPOINTER(T1& var, T2 p) +{ + char ASSERT_POINTERSIZE[sizeof(T1) == sizeof(void*) && sizeof(T2) == sizeof(void*)?1:-1];// 1 if true, -1 if false + union{ T1 out; T2 in; } tmp;// /!\ WARNING casting a pointer to a function pointer is against the C++ standard + tmp.in = p; + var = tmp.out; +} +#else +#define SET_POINTER(var,p) (var) = (p) +#define SET_FUNCPOINTER(var,p) (var) = (p) +#endif + + +#endif /* _CBASETYPES_H_ */ diff --git a/src/common/conf.c b/src/common/conf.c new file mode 100644 index 000000000..3057bd4dc --- /dev/null +++ b/src/common/conf.c @@ -0,0 +1,109 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "conf.h" +#include "libconfig.h" + +#include "../common/showmsg.h" // ShowError + +int conf_read_file(config_t *config, const char *config_filename) +{ + config_init(config); + if (!config_read_file(config, config_filename)) { + ShowError("%s:%d - %s\n", config_error_file(config), + config_error_line(config), config_error_text(config)); + config_destroy(config); + return 1; + } + return 0; +} + +// +// Functions to copy settings from libconfig/contrib +// +static void config_setting_copy_simple(config_setting_t *parent, const config_setting_t *src); +static void config_setting_copy_elem(config_setting_t *parent, const config_setting_t *src); +static void config_setting_copy_aggregate(config_setting_t *parent, const config_setting_t *src); +int config_setting_copy(config_setting_t *parent, const config_setting_t *src); + +void config_setting_copy_simple(config_setting_t *parent, const config_setting_t *src) +{ + if (config_setting_is_aggregate(src)) { + config_setting_copy_aggregate(parent, src); + } + else { + config_setting_t *set = config_setting_add(parent, config_setting_name(src), config_setting_type(src)); + + if (set == NULL) + return; + + if (CONFIG_TYPE_INT == config_setting_type(src)) { + config_setting_set_int(set, config_setting_get_int(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_INT64 == config_setting_type(src)) { + config_setting_set_int64(set, config_setting_get_int64(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_FLOAT == config_setting_type(src)) { + config_setting_set_float(set, config_setting_get_float(src)); + } else if (CONFIG_TYPE_STRING == config_setting_type(src)) { + config_setting_set_string(set, config_setting_get_string(src)); + } else if (CONFIG_TYPE_BOOL == config_setting_type(src)) { + config_setting_set_bool(set, config_setting_get_bool(src)); + } + } +} + +void config_setting_copy_elem(config_setting_t *parent, const config_setting_t *src) +{ + config_setting_t *set = NULL; + + if (config_setting_is_aggregate(src)) + config_setting_copy_aggregate(parent, src); + else if (CONFIG_TYPE_INT == config_setting_type(src)) { + set = config_setting_set_int_elem(parent, -1, config_setting_get_int(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_INT64 == config_setting_type(src)) { + set = config_setting_set_int64_elem(parent, -1, config_setting_get_int64(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_FLOAT == config_setting_type(src)) { + config_setting_set_float_elem(parent, -1, config_setting_get_float(src)); + } else if (CONFIG_TYPE_STRING == config_setting_type(src)) { + config_setting_set_string_elem(parent, -1, config_setting_get_string(src)); + } else if (CONFIG_TYPE_BOOL == config_setting_type(src)) { + config_setting_set_bool_elem(parent, -1, config_setting_get_bool(src)); + } +} + +void config_setting_copy_aggregate(config_setting_t *parent, const config_setting_t *src) +{ + config_setting_t *newAgg; + int i, n; + + newAgg = config_setting_add(parent, config_setting_name(src), config_setting_type(src)); + + if (newAgg == NULL) + return; + + n = config_setting_length(src); + + for (i = 0; i < n; i++) { + if (config_setting_is_group(src)) { + config_setting_copy_simple(newAgg, config_setting_get_elem(src, i)); + } else { + config_setting_copy_elem(newAgg, config_setting_get_elem(src, i)); + } + } +} + +int config_setting_copy(config_setting_t *parent, const config_setting_t *src) +{ + if (!config_setting_is_group(parent) && !config_setting_is_list(parent)) + return CONFIG_FALSE; + + if (config_setting_is_aggregate(src)) { + config_setting_copy_aggregate(parent, src); + } else { + config_setting_copy_simple(parent, src); + } + return CONFIG_TRUE; +} diff --git a/src/common/conf.h b/src/common/conf.h new file mode 100644 index 000000000..666853ba6 --- /dev/null +++ b/src/common/conf.h @@ -0,0 +1,13 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CONF_H_ +#define _CONF_H_ + +#include "../common/cbasetypes.h" +#include "libconfig.h" + +int conf_read_file(config_t *config, const char *config_filename); +int config_setting_copy(config_setting_t *parent, const config_setting_t *src); + +#endif // _CONF_H_ diff --git a/src/common/core.c b/src/common/core.c new file mode 100644 index 000000000..e1f99885b --- /dev/null +++ b/src/common/core.c @@ -0,0 +1,355 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "core.h" +#ifndef MINICORE +#include "../common/db.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/thread.h" +#include "../common/mempool.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#ifndef _WIN32 +#include <unistd.h> +#else +#include "../common/winapi.h" // Console close event handling +#endif + + +/// Called when a terminate signal is received. +void (*shutdown_callback)(void) = NULL; + +#if defined(BUILDBOT) + int buildbotflag = 0; +#endif + +int runflag = CORE_ST_RUN; +int arg_c = 0; +char **arg_v = NULL; + +char *SERVER_NAME = NULL; +char SERVER_TYPE = ATHENA_SERVER_NONE; + +#ifndef MINICORE // minimalist Core +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +#ifdef WIN32 // windows don't have SIGPIPE +#define SIGPIPE SIGINT +#endif + +#ifndef POSIX +#define compat_signal(signo, func) signal(signo, func) +#else +sigfunc *compat_signal(int signo, sigfunc *func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_INTERRUPT + sact.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + + if (sigaction(signo, &sact, &oact) < 0) + return (SIG_ERR); + + return (oact.sa_handler); +} +#endif + +/*====================================== + * CORE : Console events for Windows + *--------------------------------------*/ +#ifdef _WIN32 +static BOOL WINAPI console_handler(DWORD c_event) +{ + switch(c_event) + { + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + if( shutdown_callback != NULL ) + shutdown_callback(); + else + runflag = CORE_ST_STOP;// auto-shutdown + break; + default: + return FALSE; + } + return TRUE; +} + +static void cevents_init() +{ + if (SetConsoleCtrlHandler(console_handler,TRUE)==FALSE) + ShowWarning ("Unable to install the console handler!\n"); +} +#endif + +/*====================================== + * CORE : Signal Sub Function + *--------------------------------------*/ +static void sig_proc(int sn) +{ + static int is_called = 0; + + switch (sn) { + case SIGINT: + case SIGTERM: + if (++is_called > 3) + exit(EXIT_SUCCESS); + if( shutdown_callback != NULL ) + shutdown_callback(); + else + runflag = CORE_ST_STOP;// auto-shutdown + break; + case SIGSEGV: + case SIGFPE: + do_abort(); + // Pass the signal to the system's default handler + compat_signal(sn, SIG_DFL); + raise(sn); + break; +#ifndef _WIN32 + case SIGXFSZ: + // ignore and allow it to set errno to EFBIG + ShowWarning ("Max file size reached!\n"); + //run_flag = 0; // should we quit? + break; + case SIGPIPE: + //ShowInfo ("Broken pipe found... closing socket\n"); // set to eof in socket.c + break; // does nothing here +#endif + } +} + +void signals_init (void) +{ + compat_signal(SIGTERM, sig_proc); + compat_signal(SIGINT, sig_proc); +#ifndef _DEBUG // need unhandled exceptions to debug on Windows + compat_signal(SIGSEGV, sig_proc); + compat_signal(SIGFPE, sig_proc); +#endif +#ifndef _WIN32 + compat_signal(SIGILL, SIG_DFL); + compat_signal(SIGXFSZ, sig_proc); + compat_signal(SIGPIPE, sig_proc); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); +#endif +} +#endif + +#ifdef SVNVERSION + const char *get_svn_revision(void) + { + return EXPAND_AND_QUOTE(SVNVERSION); + } +#else// not SVNVERSION +const char* get_svn_revision(void) +{ + static char svn_version_buffer[16] = ""; + FILE *fp; + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + + // subversion 1.7 uses a sqlite3 database + // FIXME this is hackish at best... + // - ignores database file structure + // - assumes the data in NODES.dav_cache column ends with "!svn/ver/<revision>/<path>)" + // - since it's a cache column, the data might not even exist + if( (fp = fopen(".svn"PATHSEP_STR"wc.db", "rb")) != NULL || (fp = fopen(".."PATHSEP_STR".svn"PATHSEP_STR"wc.db", "rb")) != NULL ) + { + #ifndef SVNNODEPATH + //not sure how to handle branches, so i'll leave this overridable define until a better solution comes up + #define SVNNODEPATH trunk + #endif + const char* prefix = "!svn/ver/"; + const char* postfix = "/"EXPAND_AND_QUOTE(SVNNODEPATH)")"; // there should exist only 1 entry like this + size_t prefix_len = strlen(prefix); + size_t postfix_len = strlen(postfix); + size_t i,j,len; + char* buffer; + + // read file to buffer + fseek(fp, 0, SEEK_END); + len = ftell(fp); + buffer = (char*)aMalloc(len + 1); + fseek(fp, 0, SEEK_SET); + len = fread(buffer, 1, len, fp); + buffer[len] = '\0'; + fclose(fp); + + // parse buffer + for( i = prefix_len + 1; i + postfix_len <= len; ++i ) + { + if( buffer[i] != postfix[0] || memcmp(buffer + i, postfix, postfix_len) != 0 ) + continue; // postfix missmatch + for( j = i; j > 0; --j ) + {// skip digits + if( !ISDIGIT(buffer[j - 1]) ) + break; + } + if( memcmp(buffer + j - prefix_len, prefix, prefix_len) != 0 ) + continue; // prefix missmatch + // done + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(buffer + j)); + break; + } + aFree(buffer); + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + } + + // subversion 1.6 and older? + if ((fp = fopen(".svn/entries", "r")) != NULL) + { + char line[1024]; + int rev; + // Check the version + if (fgets(line, sizeof(line), fp)) + { + if(!ISDIGIT(line[0])) + { + // XML File format + while (fgets(line,sizeof(line),fp)) + if (strstr(line,"revision=")) break; + if (sscanf(line," %*[^\"]\"%d%*[^\n]", &rev) == 1) { + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", rev); + } + } + else + { + // Bin File format + if ( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get bin name\n"); } // Get the name + if ( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get entries kind\n"); } // Get the entries kind + if(fgets(line, sizeof(line), fp)) // Get the rev numver + { + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(line)); + } + } + } + fclose(fp); + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + } + + // fallback + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "Unknown"); + return svn_version_buffer; +} +#endif + +/*====================================== + * CORE : Display title + * ASCII By CalciumKid 1/12/2011 + *--------------------------------------*/ +static void display_title(void) { + //ClearScreen(); // clear screen and go up/left (0, 0 position in text) + + ShowMessage("\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BT_WHITE" rAthena Development Team presents "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" ___ __ __ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" _____/ | / /_/ /_ ___ ____ ____ _ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" / ___/ /| |/ __/ __ \\/ _ \\/ __ \\/ __ `/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" / / / ___ / /_/ / / / __/ / / / /_/ / "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" /_/ /_/ |_\\__/_/ /_/\\___/_/ /_/\\__,_/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_GREEN" http://rathena.org/board/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + + ShowInfo("SVN Revision: '"CL_WHITE"%s"CL_RESET"'.\n", get_svn_revision()); +} + +// Warning if executed as superuser (root) +void usercheck(void) +{ +#ifndef _WIN32 + if (geteuid() == 0) { + ShowWarning ("You are running rAthena with root privileges, it is not necessary.\n"); + } +#endif +} + +/*====================================== + * CORE : MAINROUTINE + *--------------------------------------*/ +int main (int argc, char **argv) +{ + {// initialize program arguments + char *p1 = SERVER_NAME = argv[0]; + char *p2 = p1; + while ((p1 = strchr(p2, '/')) != NULL || (p1 = strchr(p2, '\\')) != NULL) + { + SERVER_NAME = ++p1; + p2 = p1; + } + arg_c = argc; + arg_v = argv; + } + + malloc_init();// needed for Show* in display_title() [FlavioJS] + +#ifdef MINICORE // minimalist Core + display_title(); + usercheck(); + do_init(argc,argv); + do_final(); +#else// not MINICORE + set_server_type(); + display_title(); + usercheck(); + + rathread_init(); + mempool_init(); + db_init(); + signals_init(); + +#ifdef _WIN32 + cevents_init(); +#endif + + timer_init(); + socket_init(); + + do_init(argc,argv); + + {// Main runtime cycle + int next; + while (runflag != CORE_ST_STOP) { + next = do_timer(gettick_nocache()); + do_sockets(next); + } + } + + do_final(); + + timer_final(); + socket_final(); + db_final(); + mempool_final(); + rathread_final(); +#endif + + malloc_final(); + + return 0; +} diff --git a/src/common/core.h b/src/common/core.h new file mode 100644 index 000000000..d48962c94 --- /dev/null +++ b/src/common/core.h @@ -0,0 +1,47 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CORE_H_ +#define _CORE_H_ + +extern int arg_c; +extern char **arg_v; + +#if defined(BUILDBOT) + extern int buildbotflag; +#endif + +/// @see E_CORE_ST +extern int runflag; +extern char *SERVER_NAME; + +enum { + ATHENA_SERVER_NONE = 0, // not defined + ATHENA_SERVER_LOGIN = 1, // login server + ATHENA_SERVER_CHAR = 2, // char server + ATHENA_SERVER_INTER = 4, // inter server + ATHENA_SERVER_MAP = 8, // map server +}; + +extern char SERVER_TYPE; + +extern int parse_console(const char* buf); +extern const char *get_svn_revision(void); +extern int do_init(int,char**); +extern void set_server_type(void); +extern void do_abort(void); +extern void do_final(void); + +/// The main loop continues until runflag is CORE_ST_STOP +enum E_CORE_ST +{ + CORE_ST_STOP = 0, + CORE_ST_RUN, + CORE_ST_LAST +}; + +/// Called when a terminate signal is received. (Ctrl+C pressed) +/// If NULL, runflag is set to CORE_ST_STOP instead. +extern void (*shutdown_callback)(void); + +#endif /* _CORE_H_ */ diff --git a/src/common/db.c b/src/common/db.c new file mode 100644 index 000000000..204c6d2ea --- /dev/null +++ b/src/common/db.c @@ -0,0 +1,2822 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL + * For more information, see LICENCE in the main folder + * + * This file is separated in five sections: + * (1) Private typedefs, enums, structures, defines and gblobal variables + * (2) Private functions + * (3) Protected functions used internally + * (4) Protected functions used in the interface of the database + * (5) Public functions + * + * The databases are structured as a hashtable of RED-BLACK trees. + * + * <B>Properties of the RED-BLACK trees being used:</B> + * 1. The value of any node is greater than the value of its left child and + * less than the value of its right child. + * 2. Every node is colored either RED or BLACK. + * 3. Every red node that is not a leaf has only black children. + * 4. Every path from the root to a leaf contains the same number of black + * nodes. + * 5. The root node is black. + * An <code>n</code> node in a RED-BLACK tree has the property that its + * height is <code>O(lg(n))</code>. + * Another important property is that after adding a node to a RED-BLACK + * tree, the tree can be readjusted in <code>O(lg(n))</code> time. + * Similarly, after deleting a node from a RED-BLACK tree, the tree can be + * readjusted in <code>O(lg(n))</code> time. + * {@link http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic18/} + * + * <B>How to add new database types:</B> + * 1. Add the identifier of the new database type to the enum DBType + * 2. If not already there, add the data type of the key to the union DBKey + * 3. If the key can be considered NULL, update the function db_is_key_null + * 4. If the key can be duplicated, update the functions db_dup_key and + * db_dup_key_free + * 5. Create a comparator and update the function db_default_cmp + * 6. Create a hasher and update the function db_default_hash + * 7. If the new database type requires or does not support some options, + * update the function db_fix_options + * + * TODO: + * - create test cases to test the database system thoroughly + * - finish this header describing the database system + * - create custom database allocator + * - make the system thread friendly + * - change the structure of the database to T-Trees + * - create a db that organizes itself by splaying + * + * HISTORY: + * 2012/03/09 - Added enum for data types (int, uint, void*) + * 2008/02/19 - Fixed db_obj_get not handling deleted entries correctly. + * 2007/11/09 - Added an iterator to the database. + * 2006/12/21 - Added 1-node cache to the database. + * 2.1 (Athena build #???#) - Portability fix + * - Fixed the portability of casting to union and added the functions + * ensure and clear to the database. + * 2.0 (Athena build 4859) - Transition version + * - Almost everything recoded with a strategy similar to objects, + * database structure is maintained. + * 1.0 (up to Athena build 4706) + * - Previous database system. + * + * @version 2006/12/21 + * @author Athena Dev team + * @encoding US-ASCII + * @see #db.h +\*****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "db.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.h" +#include "../common/strlib.h" + +/*****************************************************************************\ + * (1) Private typedefs, enums, structures, defines and global variables of * + * the database system. * + * DB_ENABLE_STATS - Define to enable database statistics. * + * HASH_SIZE - Define with the size of the hashtable. * + * DBNColor - Enumeration of colors of the nodes. * + * DBNode - Structure of a node in RED-BLACK trees. * + * struct db_free - Structure that holds a deleted node to be freed. * + * DBMap_impl - Struture of the database. * + * stats - Statistics about the database system. * +\*****************************************************************************/ + +/** + * If defined statistics about database nodes, database creating/destruction + * and function usage are keept and displayed when finalizing the database + * system. + * WARNING: This adds overhead to every database operation (not shure how much). + * @private + * @see #DBStats + * @see #stats + * @see #db_final(void) + */ +//#define DB_ENABLE_STATS + +/** + * Size of the hashtable in the database. + * @private + * @see DBMap_impl#ht + */ +#define HASH_SIZE (256+27) + +/** + * The color of individual nodes. + * @private + * @see struct dbn + */ +typedef enum node_color { + RED, + BLACK +} node_color; + +/** + * A node in a RED-BLACK tree of the database. + * @param parent Parent node + * @param left Left child node + * @param right Right child node + * @param key Key of this database entry + * @param data Data of this database entry + * @param deleted If the node is deleted + * @param color Color of the node + * @private + * @see DBMap_impl#ht + */ +typedef struct dbn { + // Tree structure + struct dbn *parent; + struct dbn *left; + struct dbn *right; + // Node data + DBKey key; + DBData data; + // Other + node_color color; + unsigned deleted : 1; +} *DBNode; + +/** + * Structure that holds a deleted node. + * @param node Deleted node + * @param root Address to the root of the tree + * @private + * @see DBMap_impl#free_list + */ +struct db_free { + DBNode node; + DBNode *root; +}; + +/** + * Complete database structure. + * @param vtable Interface of the database + * @param alloc_file File where the database was allocated + * @param alloc_line Line in the file where the database was allocated + * @param free_list Array of deleted nodes to be freed + * @param free_count Number of deleted nodes in free_list + * @param free_max Current maximum capacity of free_list + * @param free_lock Lock for freeing the nodes + * @param nodes Manager of reusable tree nodes + * @param cmp Comparator of the database + * @param hash Hasher of the database + * @param release Releaser of the database + * @param ht Hashtable of RED-BLACK trees + * @param type Type of the database + * @param options Options of the database + * @param item_count Number of items in the database + * @param maxlen Maximum length of strings in DB_STRING and DB_ISTRING databases + * @param global_lock Global lock of the database + * @private + * @see #db_alloc(const char*,int,DBType,DBOptions,unsigned short) + */ +typedef struct DBMap_impl { + // Database interface + struct DBMap vtable; + // File and line of allocation + const char *alloc_file; + int alloc_line; + // Lock system + struct db_free *free_list; + unsigned int free_count; + unsigned int free_max; + unsigned int free_lock; + // Other + ERS nodes; + DBComparator cmp; + DBHasher hash; + DBReleaser release; + DBNode ht[HASH_SIZE]; + DBNode cache; + DBType type; + DBOptions options; + uint32 item_count; + unsigned short maxlen; + unsigned global_lock : 1; +} DBMap_impl; + +/** + * Complete iterator structure. + * @param vtable Interface of the iterator + * @param db Parent database + * @param ht_index Current index of the hashtable + * @param node Current node + * @private + * @see #DBIterator + * @see #DBMap_impl + * @see #DBNode + */ +typedef struct DBIterator_impl { + // Iterator interface + struct DBIterator vtable; + DBMap_impl* db; + int ht_index; + DBNode node; +} DBIterator_impl; + +#if defined(DB_ENABLE_STATS) +/** + * Structure with what is counted when the database statistics are enabled. + * @private + * @see #DB_ENABLE_STATS + * @see #stats + */ +static struct db_stats { + // Node alloc/free + uint32 db_node_alloc; + uint32 db_node_free; + // Database creating/destruction counters + uint32 db_int_alloc; + uint32 db_uint_alloc; + uint32 db_string_alloc; + uint32 db_istring_alloc; + uint32 db_int_destroy; + uint32 db_uint_destroy; + uint32 db_string_destroy; + uint32 db_istring_destroy; + // Function usage counters + uint32 db_rotate_left; + uint32 db_rotate_right; + uint32 db_rebalance; + uint32 db_rebalance_erase; + uint32 db_is_key_null; + uint32 db_dup_key; + uint32 db_dup_key_free; + uint32 db_free_add; + uint32 db_free_remove; + uint32 db_free_lock; + uint32 db_free_unlock; + uint32 db_int_cmp; + uint32 db_uint_cmp; + uint32 db_string_cmp; + uint32 db_istring_cmp; + uint32 db_int_hash; + uint32 db_uint_hash; + uint32 db_string_hash; + uint32 db_istring_hash; + uint32 db_release_nothing; + uint32 db_release_key; + uint32 db_release_data; + uint32 db_release_both; + uint32 dbit_first; + uint32 dbit_last; + uint32 dbit_next; + uint32 dbit_prev; + uint32 dbit_exists; + uint32 dbit_remove; + uint32 dbit_destroy; + uint32 db_iterator; + uint32 db_exists; + uint32 db_get; + uint32 db_getall; + uint32 db_vgetall; + uint32 db_ensure; + uint32 db_vensure; + uint32 db_put; + uint32 db_remove; + uint32 db_foreach; + uint32 db_vforeach; + uint32 db_clear; + uint32 db_vclear; + uint32 db_destroy; + uint32 db_vdestroy; + uint32 db_size; + uint32 db_type; + uint32 db_options; + uint32 db_fix_options; + uint32 db_default_cmp; + uint32 db_default_hash; + uint32 db_default_release; + uint32 db_custom_release; + uint32 db_alloc; + uint32 db_i2key; + uint32 db_ui2key; + uint32 db_str2key; + uint32 db_i2data; + uint32 db_ui2data; + uint32 db_ptr2data; + uint32 db_data2i; + uint32 db_data2ui; + uint32 db_data2ptr; + uint32 db_init; + uint32 db_final; +} stats = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; +#define DB_COUNTSTAT(token) if (stats. ## token != UINT32_MAX) ++stats. ## token +#else /* !defined(DB_ENABLE_STATS) */ +#define DB_COUNTSTAT(token) +#endif /* !defined(DB_ENABLE_STATS) */ + +/*****************************************************************************\ + * (2) Section of private functions used by the database system. * + * db_rotate_left - Rotate a tree node to the left. * + * db_rotate_right - Rotate a tree node to the right. * + * db_rebalance - Rebalance the tree. * + * db_rebalance_erase - Rebalance the tree after a BLACK node was erased. * + * db_is_key_null - Returns not 0 if the key is considered NULL. * + * db_dup_key - Duplicate a key for internal use. * + * db_dup_key_free - Free the duplicated key. * + * db_free_add - Add a node to the free_list of a database. * + * db_free_remove - Remove a node from the free_list of a database. * + * db_free_lock - Increment the free_lock of a database. * + * db_free_unlock - Decrement the free_lock of a database. * + * If it was the last lock, frees the nodes in free_list. * + * NOTE: Keeps the database trees balanced. * +\*****************************************************************************/ + +/** + * Rotate a node to the left. + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_left(DBNode node, DBNode *root) +{ + DBNode y = node->right; + + DB_COUNTSTAT(db_rotate_left); + // put the left of y at the right of node + node->right = y->left; + if (y->left) + y->left->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->left) { + node->parent->left = y; // node was at the left + } else { + node->parent->right = y; // node was at the right + } + // put node at the left of y + y->left = node; + node->parent = y; +} + +/** + * Rotate a node to the right + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_right(DBNode node, DBNode *root) +{ + DBNode y = node->left; + + DB_COUNTSTAT(db_rotate_right); + // put the right of y at the left of node + node->left = y->right; + if (y->right != 0) + y->right->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->right) { + node->parent->right = y; // node was at the right + } else { + node->parent->left = y; // node was at the left + } + // put node at the right of y + y->right = node; + node->parent = y; +} + +/** + * Rebalance the RED-BLACK tree. + * Called when the node and it's parent are both RED. + * @param node Node to be rebalanced + * @param root Pointer to the root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_obj_put(DBMap*,DBKey,DBData) + */ +static void db_rebalance(DBNode node, DBNode *root) +{ + DBNode y; + + DB_COUNTSTAT(db_rebalance); + // Restore the RED-BLACK properties + node->color = RED; + while (node != *root && node->parent->color == RED) { + if (node->parent == node->parent->parent->left) { + // If node's parent is a left, y is node's right 'uncle' + y = node->parent->parent->right; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->right) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_left(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_right(node->parent->parent, root); + } + } else { + // If node's parent is a right, y is node's left 'uncle' + y = node->parent->parent->left; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->left) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_right(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_left(node->parent->parent, root); + } + } + } + (*root)->color = BLACK; // the root can and should always be black +} + +/** + * Erase a node from the RED-BLACK tree, keeping the tree balanced. + * @param node Node to be erased from the tree + * @param root Root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_free_unlock(DBMap_impl*) + */ +static void db_rebalance_erase(DBNode node, DBNode *root) +{ + DBNode y = node; + DBNode x = NULL; + DBNode x_parent = NULL; + DBNode w; + + DB_COUNTSTAT(db_rebalance_erase); + // Select where to change the tree + if (y->left == NULL) { // no left + x = y->right; + } else if (y->right == NULL) { // no right + x = y->left; + } else { // both exist, go to the leftmost node of the right sub-tree + y = y->right; + while (y->left != NULL) + y = y->left; + x = y->right; + } + + // Remove the node from the tree + if (y != node) { // both childs existed + // put the left of 'node' in the left of 'y' + node->left->parent = y; + y->left = node->left; + + // 'y' is not the direct child of 'node' + if (y != node->right) { + // put 'x' in the old position of 'y' + x_parent = y->parent; + if (x) x->parent = y->parent; + y->parent->left = x; + // put the right of 'node' in 'y' + y->right = node->right; + node->right->parent = y; + // 'y' is a direct child of 'node' + } else { + x_parent = y; + } + + // link 'y' and the parent of 'node' + if (*root == node) { + *root = y; // 'node' was the root + } else if (node->parent->left == node) { + node->parent->left = y; // 'node' was at the left + } else { + node->parent->right = y; // 'node' was at the right + } + y->parent = node->parent; + // switch colors + { + node_color tmp = y->color; + y->color = node->color; + node->color = tmp; + } + y = node; + } else { // one child did not exist + // put x in node's position + x_parent = y->parent; + if (x) x->parent = y->parent; + // link x and node's parent + if (*root == node) { + *root = x; // node was the root + } else if (node->parent->left == node) { + node->parent->left = x; // node was at the left + } else { + node->parent->right = x; // node was at the right + } + } + + // Restore the RED-BLACK properties + if (y->color != RED) { + while (x != *root && (x == NULL || x->color == BLACK)) { + if (x == x_parent->left) { + w = x_parent->right; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_left(x_parent, root); + w = x_parent->right; + } + if ((w->left == NULL || w->left->color == BLACK) && + (w->right == NULL || w->right->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->right == NULL || w->right->color == BLACK) { + if (w->left) w->left->color = BLACK; + w->color = RED; + db_rotate_right(w, root); + w = x_parent->right; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->right) w->right->color = BLACK; + db_rotate_left(x_parent, root); + break; + } + } else { + w = x_parent->left; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_right(x_parent, root); + w = x_parent->left; + } + if ((w->right == NULL || w->right->color == BLACK) && + (w->left == NULL || w->left->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->left == NULL || w->left->color == BLACK) { + if (w->right) w->right->color = BLACK; + w->color = RED; + db_rotate_left(w, root); + w = x_parent->left; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->left) w->left->color = BLACK; + db_rotate_right(x_parent, root); + break; + } + } + } + if (x) x->color = BLACK; + } +} + +/** + * Returns not 0 if the key is considered to be NULL. + * @param type Type of database + * @param key Key being tested + * @return not 0 if considered NULL, 0 otherwise + * @private + * @see #db_obj_get(DBMap*,DBKey) + * @see #db_obj_put(DBMap*,DBKey,DBData) + * @see #db_obj_remove(DBMap*,DBKey) + */ +static int db_is_key_null(DBType type, DBKey key) +{ + DB_COUNTSTAT(db_is_key_null); + switch (type) { + case DB_STRING: + case DB_ISTRING: + return (key.str == NULL); + + default: // Not a pointer + return 0; + } +} + +/** + * Duplicate the key used in the database. + * @param db Database the key is being used in + * @param key Key to be duplicated + * @param Duplicated key + * @private + * @see #db_free_add(DBMap_impl*,DBNode,DBNode *) + * @see #db_free_remove(DBMap_impl*,DBNode) + * @see #db_obj_put(DBMap*,DBKey,void *) + * @see #db_dup_key_free(DBMap_impl*,DBKey) + */ +static DBKey db_dup_key(DBMap_impl* db, DBKey key) +{ + char *str; + size_t len; + + DB_COUNTSTAT(db_dup_key); + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + len = strnlen(key.str, db->maxlen); + str = (char*)aMalloc(len + 1); + memcpy(str, key.str, len); + str[len] = '\0'; + key.str = str; + return key; + + default: + return key; + } +} + +/** + * Free a key duplicated by db_dup_key. + * @param db Database the key is being used in + * @param key Key to be freed + * @private + * @see #db_dup_key(DBMap_impl*,DBKey) + */ +static void db_dup_key_free(DBMap_impl* db, DBKey key) +{ + DB_COUNTSTAT(db_dup_key_free); + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + aFree((char*)key.str); + return; + + default: + return; + } +} + +/** + * Add a node to the free_list of the database. + * Marks the node as deleted. + * If the key isn't duplicated, the key is duplicated and released. + * @param db Target database + * @param root Root of the tree from the node + * @param node Target node + * @private + * @see #struct db_free + * @see DBMap_impl#free_list + * @see DBMap_impl#free_count + * @see DBMap_impl#free_max + * @see #db_obj_remove(DBMap*,DBKey) + * @see #db_free_remove(DBMap_impl*,DBNode) + */ +static void db_free_add(DBMap_impl* db, DBNode node, DBNode *root) +{ + DBKey old_key; + + DB_COUNTSTAT(db_free_add); + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_add: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + if (!(db->options&DB_OPT_DUP_KEY)) { // Make sure we have a key until the node is freed + old_key = node->key; + node->key = db_dup_key(db, node->key); + db->release(old_key, node->data, DB_RELEASE_KEY); + } + if (db->free_count == db->free_max) { // No more space, expand free_list + db->free_max = (db->free_max<<2) +3; // = db->free_max*4 +3 + if (db->free_max <= db->free_count) { + if (db->free_count == (unsigned int)~0) { + ShowFatalError("db_free_add: free_count overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_max = (unsigned int)~0; + } + RECREATE(db->free_list, struct db_free, db->free_max); + } + node->deleted = 1; + db->free_list[db->free_count].node = node; + db->free_list[db->free_count].root = root; + db->free_count++; + db->item_count--; +} + +/** + * Remove a node from the free_list of the database. + * Marks the node as not deleted. + * NOTE: Frees the duplicated key of the node. + * @param db Target database + * @param node Node being removed from free_list + * @private + * @see #struct db_free + * @see DBMap_impl#free_list + * @see DBMap_impl#free_count + * @see #db_obj_put(DBMap*,DBKey,DBData) + * @see #db_free_add(DBMap_impl*,DBNode*,DBNode) + */ +static void db_free_remove(DBMap_impl* db, DBNode node) +{ + unsigned int i; + + DB_COUNTSTAT(db_free_remove); + for (i = 0; i < db->free_count; i++) { + if (db->free_list[i].node == node) { + if (i < db->free_count -1) // copy the last item to where the removed one was + memcpy(&db->free_list[i], &db->free_list[db->free_count -1], sizeof(struct db_free)); + db_dup_key_free(db, node->key); + break; + } + } + node->deleted = 0; + if (i == db->free_count) { + ShowWarning("db_free_remove: node was not found - database allocated at %s:%d\n", db->alloc_file, db->alloc_line); + } else { + db->free_count--; + } + db->item_count++; +} + +/** + * Increment the free_lock of the database. + * @param db Target database + * @private + * @see DBMap_impl#free_lock + * @see #db_unlock(DBMap_impl*) + */ +static void db_free_lock(DBMap_impl* db) +{ + DB_COUNTSTAT(db_free_lock); + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_lock: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_lock++; +} + +/** + * Decrement the free_lock of the database. + * If it was the last lock, frees the nodes of the database. + * Keeps the tree balanced. + * NOTE: Frees the duplicated keys of the nodes + * @param db Target database + * @private + * @see DBMap_impl#free_lock + * @see #db_free_dbn(DBNode) + * @see #db_lock(DBMap_impl*) + */ +static void db_free_unlock(DBMap_impl* db) +{ + unsigned int i; + + DB_COUNTSTAT(db_free_unlock); + if (db->free_lock == 0) { + ShowWarning("db_free_unlock: free_lock was already 0\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + } else { + db->free_lock--; + } + if (db->free_lock) + return; // Not last lock + + for (i = 0; i < db->free_count ; i++) { + db_rebalance_erase(db->free_list[i].node, db->free_list[i].root); + db_dup_key_free(db, db->free_list[i].node->key); + DB_COUNTSTAT(db_node_free); + ers_free(db->nodes, db->free_list[i].node); + } + db->free_count = 0; +} + +/*****************************************************************************\ + * (3) Section of protected functions used internally. * + * NOTE: the protected functions used in the database interface are in the * + * next section. * + * db_int_cmp - Default comparator for DB_INT databases. * + * db_uint_cmp - Default comparator for DB_UINT databases. * + * db_string_cmp - Default comparator for DB_STRING databases. * + * db_istring_cmp - Default comparator for DB_ISTRING databases. * + * db_int_hash - Default hasher for DB_INT databases. * + * db_uint_hash - Default hasher for DB_UINT databases. * + * db_string_hash - Default hasher for DB_STRING databases. * + * db_istring_hash - Default hasher for DB_ISTRING databases. * + * db_release_nothing - Releaser that releases nothing. * + * db_release_key - Releaser that only releases the key. * + * db_release_data - Releaser that only releases the data. * + * db_release_both - Releaser that releases key and data. * +\*****************************************************************************/ + +/** + * Default comparator for DB_INT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * <code>maxlen</code> is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_INT + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_int_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_int_cmp); + if (key1.i < key2.i) return -1; + if (key1.i > key2.i) return 1; + return 0; +} + +/** + * Default comparator for DB_UINT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * <code>maxlen</code> is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_UINT + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_uint_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_uint_cmp); + if (key1.ui < key2.ui) return -1; + if (key1.ui > key2.ui) return 1; + return 0; +} + +/** + * Default comparator for DB_STRING databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_STRING + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_string_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + DB_COUNTSTAT(db_string_cmp); + return strncmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default comparator for DB_ISTRING databases. + * Compares key1 to key2 case insensitively. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_ISTRING + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + DB_COUNTSTAT(db_istring_cmp); + return strncasecmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default hasher for DB_INT databases. + * Returns the value of the key as an unsigned int. + * <code>maxlen</code> is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_INT + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_int_hash(DBKey key, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_int_hash); + return (unsigned int)key.i; +} + +/** + * Default hasher for DB_UINT databases. + * Just returns the value of the key. + * <code>maxlen</code> is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_UINT + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_uint_hash(DBKey key, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_uint_hash); + return key.ui; +} + +/** + * Default hasher for DB_STRING databases. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_STRING + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_string_hash(DBKey key, unsigned short maxlen) +{ + const char *k = key.str; + unsigned int hash = 0; + unsigned short i; + + DB_COUNTSTAT(db_string_hash); + + for (i = 0; *k; ++i) { + hash = (hash*33 + ((unsigned char)*k))^(hash>>24); + k++; + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Default hasher for DB_ISTRING databases. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_ISTRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_istring_hash(DBKey key, unsigned short maxlen) +{ + const char *k = key.str; + unsigned int hash = 0; + unsigned short i; + + DB_COUNTSTAT(db_istring_hash); + + for (i = 0; *k; i++) { + hash = (hash*33 + ((unsigned char)TOLOWER(*k)))^(hash>>24); + k++; + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Releaser that releases nothing. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBReleaser + * @see #db_default_releaser(DBType,DBOptions) + */ +static void db_release_nothing(DBKey key, DBData data, DBRelease which) +{ + (void)key;(void)data;(void)which;//not used + DB_COUNTSTAT(db_release_nothing); +} + +/** + * Releaser that only releases the key. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_key(DBKey key, DBData data, DBRelease which) +{ + (void)data;//not used + DB_COUNTSTAT(db_release_key); + if (which&DB_RELEASE_KEY) aFree((char*)key.str); // needs to be a pointer +} + +/** + * Releaser that only releases the data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBData + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_data(DBKey key, DBData data, DBRelease which) +{ + (void)key;//not used + DB_COUNTSTAT(db_release_data); + if (which&DB_RELEASE_DATA && data.type == DB_DATA_PTR) aFree(data.u.ptr); +} + +/** + * Releaser that releases both key and data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBKey + * @see #DBData + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_both(DBKey key, DBData data, DBRelease which) +{ + DB_COUNTSTAT(db_release_both); + if (which&DB_RELEASE_KEY) aFree((char*)key.str); // needs to be a pointer + if (which&DB_RELEASE_DATA && data.type == DB_DATA_PTR) aFree(data.u.ptr); +} + +/*****************************************************************************\ + * (4) Section with protected functions used in the interface of the * + * database and interface of the iterator. * + * dbit_obj_first - Fetches the first entry from the database. * + * dbit_obj_last - Fetches the last entry from the database. * + * dbit_obj_next - Fetches the next entry from the database. * + * dbit_obj_prev - Fetches the previous entry from the database. * + * dbit_obj_exists - Returns true if the current entry exists. * + * dbit_obj_remove - Remove the current entry from the database. * + * dbit_obj_destroy - Destroys the iterator, unlocking the database and * + * freeing used memory. * + * db_obj_iterator - Return a new database iterator. * + * db_obj_exists - Checks if an entry exists. * + * db_obj_get - Get the data identified by the key. * + * db_obj_vgetall - Get the data of the matched entries. * + * db_obj_getall - Get the data of the matched entries. * + * db_obj_vensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_ensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_put - Put data identified by the key in the database. * + * db_obj_remove - Remove an entry from the database. * + * db_obj_vforeach - Apply a function to every entry in the database. * + * db_obj_foreach - Apply a function to every entry in the database. * + * db_obj_vclear - Remove all entries from the database. * + * db_obj_clear - Remove all entries from the database. * + * db_obj_vdestroy - Destroy the database, freeing all the used memory. * + * db_obj_destroy - Destroy the database, freeing all the used memory. * + * db_obj_size - Return the size of the database. * + * db_obj_type - Return the type of the database. * + * db_obj_options - Return the options of the database. * +\*****************************************************************************/ + +/** + * Fetches the first entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#first + */ +DBData* dbit_obj_first(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_first); + // position before the first entry + it->ht_index = -1; + it->node = NULL; + // get next entry + return self->next(self, out_key); +} + +/** + * Fetches the last entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#last + */ +DBData* dbit_obj_last(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_last); + // position after the last entry + it->ht_index = HASH_SIZE; + it->node = NULL; + // get previous entry + return self->prev(self, out_key); +} + +/** + * Fetches the next entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#next + */ +DBData* dbit_obj_next(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + DBNode parent; + struct dbn fake; + + DB_COUNTSTAT(dbit_next); + if( it->ht_index < 0 ) + {// get first node + it->ht_index = 0; + it->node = NULL; + } + node = it->node; + memset(&fake, 0, sizeof(fake)); + for( ; it->ht_index < HASH_SIZE; ++(it->ht_index) ) + { + // Iterate in the order: left tree, current node, right tree + if( node == NULL ) + {// prepare initial node of this hash + node = it->db->ht[it->ht_index]; + if( node == NULL ) + continue;// next hash + fake.right = node; + node = &fake; + } + + while( node ) + {// next node + if( node->right ) + {// continue in the right subtree + node = node->right; + while( node->left ) + node = node->left;// get leftmost node + } + else + {// continue to the next parent (recursive) + parent = node->parent; + while( parent ) + { + if( parent->right != node ) + break; + node = parent; + parent = node->parent; + } + if( parent == NULL ) + {// next hash + node = NULL; + break; + } + node = parent; + } + + if( !node->deleted ) + {// found next entry + it->node = node; + if( out_key ) + memcpy(out_key, &node->key, sizeof(DBKey)); + return &node->data; + } + } + } + it->node = NULL; + return NULL;// not found +} + +/** + * Fetches the previous entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#prev + */ +DBData* dbit_obj_prev(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + DBNode parent; + struct dbn fake; + + DB_COUNTSTAT(dbit_prev); + if( it->ht_index >= HASH_SIZE ) + {// get last node + it->ht_index = HASH_SIZE-1; + it->node = NULL; + } + node = it->node; + memset(&fake, 0, sizeof(fake)); + for( ; it->ht_index >= 0; --(it->ht_index) ) + { + // Iterate in the order: right tree, current node, left tree + if( node == NULL ) + {// prepare initial node of this hash + node = it->db->ht[it->ht_index]; + if( node == NULL ) + continue;// next hash + fake.left = node; + node = &fake; + } + + + while( node ) + {// next node + if( node->left ) + {// continue in the left subtree + node = node->left; + while( node->right ) + node = node->right;// get rightmost node + } + else + {// continue to the next parent (recursive) + parent = node->parent; + while( parent ) + { + if( parent->left != node ) + break; + node = parent; + parent = node->parent; + } + if( parent == NULL ) + {// next hash + node = NULL; + break; + } + node = parent; + } + + if( !node->deleted ) + {// found previous entry + it->node = node; + if( out_key ) + memcpy(out_key, &node->key, sizeof(DBKey)); + return &node->data; + } + } + } + it->node = NULL; + return NULL;// not found +} + +/** + * Returns true if the fetched entry exists. + * The databases entries might have NULL data, so use this to to test if + * the iterator is done. + * @param self Iterator + * @return true if the entry exists + * @protected + * @see DBIterator#exists + */ +bool dbit_obj_exists(DBIterator* self) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_exists); + return (it->node && !it->node->deleted); +} + +/** + * Removes the current entry from the database. + * NOTE: {@link DBIterator#exists} will return false until another entry + * is fetched + * Puts data of the removed entry in out_data, if out_data is not NULL. + * @param self Iterator + * @param out_data Data of the removed entry. + * @return 1 if entry was removed, 0 otherwise + * @protected + * @see DBMap#remove + * @see DBIterator#remove + */ +int dbit_obj_remove(DBIterator* self, DBData *out_data) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + int retval = 0; + + DB_COUNTSTAT(dbit_remove); + node = it->node; + if( node && !node->deleted ) + { + DBMap_impl* db = it->db; + if( db->cache == node ) + db->cache = NULL; + if( out_data ) + memcpy(out_data, &node->data, sizeof(DBData)); + retval = 1; + db->release(node->key, node->data, DB_RELEASE_DATA); + db_free_add(db, node, &db->ht[it->ht_index]); + } + return retval; +} + +/** + * Destroys this iterator and unlocks the database. + * @param self Iterator + * @protected + */ +void dbit_obj_destroy(DBIterator* self) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_destroy); + // unlock the database + db_free_unlock(it->db); + // free iterator + aFree(self); +} + +/** + * Returns a new iterator for this database. + * The iterator keeps the database locked until it is destroyed. + * The database will keep functioning normally but will only free internal + * memory when unlocked, so destroy the iterator as soon as possible. + * @param self Database + * @return New iterator + * @protected + */ +static DBIterator* db_obj_iterator(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBIterator_impl* it; + + DB_COUNTSTAT(db_iterator); + CREATE(it, struct DBIterator_impl, 1); + /* Interface of the iterator **/ + it->vtable.first = dbit_obj_first; + it->vtable.last = dbit_obj_last; + it->vtable.next = dbit_obj_next; + it->vtable.prev = dbit_obj_prev; + it->vtable.exists = dbit_obj_exists; + it->vtable.remove = dbit_obj_remove; + it->vtable.destroy = dbit_obj_destroy; + /* Initial state (before the first entry) */ + it->db = db; + it->ht_index = -1; + it->node = NULL; + /* Lock the database */ + db_free_lock(db); + return &it->vtable; +} + +/** + * Returns true if the entry exists. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return true is the entry exists + * @protected + * @see DBMap#exists + */ +static bool db_obj_exists(DBMap* self, DBKey key) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + int c; + bool found = false; + + DB_COUNTSTAT(db_exists); + if (db == NULL) return false; // nullpo candidate + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + return false; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) { +#if defined(DEBUG) + if (db->cache->deleted) { + ShowDebug("db_exists: Cache contains a deleted node. Please report this!!!\n"); + return false; + } +#endif + return true; // cache hit + } + + db_free_lock(db); + node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + db->cache = node; + found = true; + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return found; +} + +/** + * Get the data of the entry identified by the key. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + * @see DBMap#get + */ +static DBData* db_obj_get(DBMap* self, DBKey key) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + int c; + DBData *data = NULL; + + DB_COUNTSTAT(db_get); + if (db == NULL) return NULL; // nullpo candidate + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_get: Attempted to retrieve non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) { +#if defined(DEBUG) + if (db->cache->deleted) { + ShowDebug("db_get: Cache contains a deleted node. Please report this!!!\n"); + return NULL; + } +#endif + return &db->cache->data; // cache hit + } + + db_free_lock(db); + node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + data = &node->data; + db->cache = node; + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return data; +} + +/** + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall + */ +static unsigned int db_obj_vgetall(DBMap* self, DBData **buf, unsigned int max, DBMatcher match, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int i; + DBNode node; + DBNode parent; + unsigned int ret = 0; + + DB_COUNTSTAT(db_vgetall); + if (db == NULL) return 0; // nullpo candidate + if (match == NULL) return 0; // nullpo candidate + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Match in the order: current node, left tree, right tree + node = db->ht[i]; + while (node) { + + if (!(node->deleted)) { + va_list argscopy; + va_copy(argscopy, args); + if (match(node->key, node->data, argscopy) == 0) { + if (buf && ret < max) + buf[ret] = &node->data; + ret++; + } + va_end(argscopy); + } + + if (node->left) { + node = node->left; + continue; + } + + if (node->right) { + node = node->right; + continue; + } + + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + + } + } + db_free_unlock(db); + return ret; +} + +/** + * Just calls {@link DBMap#vgetall}. + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall + * @see DBMap#getall + */ +static unsigned int db_obj_getall(DBMap* self, DBData **buf, unsigned int max, DBMatcher match, ...) +{ + va_list args; + unsigned int ret; + + DB_COUNTSTAT(db_getall); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, match); + ret = self->vgetall(self, buf, max, match, args); + va_end(args); + return ret; +} + +/** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure + */ +static DBData* db_obj_vensure(DBMap* self, DBKey key, DBCreateData create, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + DBNode parent = NULL; + unsigned int hash; + int c = 0; + DBData *data = NULL; + + DB_COUNTSTAT(db_vensure); + if (db == NULL) return NULL; // nullpo candidate + if (create == NULL) { + ShowError("db_ensure: Create function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_ensure: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) + return &db->cache->data; // cache hit + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + node = db->ht[hash]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + break; + } + parent = node; + if (c < 0) + node = node->left; + else + node = node->right; + } + // Create node if necessary + if (node == NULL) { + va_list argscopy; + if (db->item_count == UINT32_MAX) { + ShowError("db_vensure: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } + DB_COUNTSTAT(db_node_alloc); + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, *data, DB_RELEASE_KEY); + } else { + node->key = key; + } + va_copy(argscopy, args); + node->data = create(key, argscopy); + va_end(argscopy); + } + data = &node->data; + db->cache = node; + db_free_unlock(db); + return data; +} + +/** + * Just calls {@link DBMap#vensure}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure + * @see DBMap#ensure + */ +static DBData* db_obj_ensure(DBMap* self, DBKey key, DBCreateData create, ...) +{ + va_list args; + DBData *ret = NULL; + + DB_COUNTSTAT(db_ensure); + if (self == NULL) return NULL; // nullpo candidate + + va_start(args, create); + ret = self->vensure(self, key, create, args); + va_end(args); + return ret; +} + +/** + * Put the data identified by the key in the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: Uses the new key, the old one is released. + * @param self Interface of the database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + * @see #db_malloc_dbn(void) + * @see DBMap#put + */ +static int db_obj_put(DBMap* self, DBKey key, DBData data, DBData *out_data) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + DBNode parent = NULL; + int c = 0, retval = 0; + unsigned int hash; + + DB_COUNTSTAT(db_put); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_put: Database is being destroyed, aborting entry insertion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_put: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_DATA) && (data.type == DB_DATA_PTR && data.u.ptr == NULL)) { + ShowError("db_put: Attempted to use non-allowed NULL data for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + if (db->item_count == UINT32_MAX) { + ShowError("db_put: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return 0; + } + // search for an equal node + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for (node = db->ht[hash]; node; ) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { // equal entry, replace + if (node->deleted) { + db_free_remove(db, node); + } else { + db->release(node->key, node->data, DB_RELEASE_BOTH); + if (out_data) + memcpy(out_data, &node->data, sizeof(*out_data)); + retval = 1; + } + break; + } + parent = node; + if (c < 0) { + node = node->left; + } else { + node = node->right; + } + } + // allocate a new node if necessary + if (node == NULL) { + DB_COUNTSTAT(db_node_alloc); + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, data, DB_RELEASE_KEY); + } else { + node->key = key; + } + node->data = data; + db->cache = node; + db_free_unlock(db); + return retval; +} + +/** + * Remove an entry from the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: The key (of the database) is released in {@link #db_free_add(DBMap_impl*,DBNode,DBNode *)}. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + * @see #db_free_add(DBMap_impl*,DBNode,DBNode *) + * @see DBMap#remove + */ +static int db_obj_remove(DBMap* self, DBKey key, DBData *out_data) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + unsigned int hash; + int c = 0, retval = 0; + + DB_COUNTSTAT(db_remove); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_remove: Database is being destroyed. Aborting entry deletion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_remove: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for(node = db->ht[hash]; node; ){ + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + if (db->cache == node) + db->cache = NULL; + if (out_data) + memcpy(out_data, &node->data, sizeof(*out_data)); + retval = 1; + db->release(node->key, node->data, DB_RELEASE_DATA); + db_free_add(db, node, &db->ht[hash]); + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return retval; +} + +/** + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applied + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach + */ +static int db_obj_vforeach(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int i; + int sum = 0; + DBNode node; + DBNode parent; + + DB_COUNTSTAT(db_vforeach); + if (db == NULL) return 0; // nullpo candidate + if (func == NULL) { + ShowError("db_foreach: Passed function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Apply func in the order: current node, left node, right node + node = db->ht[i]; + while (node) { + if (!(node->deleted)) { + va_list argscopy; + va_copy(argscopy, args); + sum += func(node->key, &node->data, argscopy); + va_end(argscopy); + } + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + } + } + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link DBMap#vforeach}. + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applyed + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach + * @see DBMap#foreach + */ +static int db_obj_foreach(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_foreach); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vforeach(self, func, args); + va_end(args); + return ret; +} + +/** + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear + */ +static int db_obj_vclear(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + int sum = 0; + unsigned int i; + DBNode node; + DBNode parent; + + DB_COUNTSTAT(db_vclear); + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + db->cache = NULL; + for (i = 0; i < HASH_SIZE; i++) { + // Apply the func and delete in the order: left tree, right tree, current node + node = db->ht[i]; + db->ht[i] = NULL; + while (node) { + parent = node->parent; + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + if (node->deleted) { + db_dup_key_free(db, node->key); + } else { + if (func) + { + va_list argscopy; + va_copy(argscopy, args); + sum += func(node->key, &node->data, argscopy); + va_end(argscopy); + } + db->release(node->key, node->data, DB_RELEASE_BOTH); + node->deleted = 1; + } + DB_COUNTSTAT(db_node_free); + if (parent) { + if (parent->left == node) + parent->left = NULL; + else + parent->right = NULL; + } + ers_free(db->nodes, node); + node = parent; + } + db->ht[i] = NULL; + } + db->free_count = 0; + db->item_count = 0; + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link DBMap#vclear}. + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear + * @see DBMap#clear + */ +static int db_obj_clear(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_clear); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vclear(self, func, args); + va_end(args); + return ret; +} + +/** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy + */ +static int db_obj_vdestroy(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + int sum; + + DB_COUNTSTAT(db_vdestroy); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_vdestroy: Database is already locked for destruction. Aborting second database destruction.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; + } + if (db->free_lock) + ShowWarning("db_vdestroy: Database is still in use, %u lock(s) left. Continuing database destruction.\n" + "Database allocated at %s:%d\n", + db->free_lock, db->alloc_file, db->alloc_line); + +#ifdef DB_ENABLE_STATS + switch (db->type) { + case DB_INT: DB_COUNTSTAT(db_int_destroy); break; + case DB_UINT: DB_COUNTSTAT(db_uint_destroy); break; + case DB_STRING: DB_COUNTSTAT(db_string_destroy); break; + case DB_ISTRING: DB_COUNTSTAT(db_istring_destroy); break; + } +#endif /* DB_ENABLE_STATS */ + db_free_lock(db); + db->global_lock = 1; + sum = self->vclear(self, func, args); + aFree(db->free_list); + db->free_list = NULL; + db->free_max = 0; + ers_destroy(db->nodes); + db_free_unlock(db); + aFree(db); + return sum; +} + +/** + * Just calls {@link DBMap#db_vdestroy}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy + * @see DBMap#destroy + */ +static int db_obj_destroy(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_destroy); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vdestroy(self, func, args); + va_end(args); + return ret; +} + +/** + * Return the size of the database (number of items in the database). + * @param self Interface of the database + * @return Size of the database + * @protected + * @see DBMap_impl#item_count + * @see DBMap#size + */ +static unsigned int db_obj_size(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int item_count; + + DB_COUNTSTAT(db_size); + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + item_count = db->item_count; + db_free_unlock(db); + + return item_count; +} + +/** + * Return the type of database. + * @param self Interface of the database + * @return Type of the database + * @protected + * @see DBMap_impl#type + * @see DBMap#type + */ +static DBType db_obj_type(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBType type; + + DB_COUNTSTAT(db_type); + if (db == NULL) return (DBType)-1; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + type = db->type; + db_free_unlock(db); + + return type; +} + +/** + * Return the options of the database. + * @param self Interface of the database + * @return Options of the database + * @protected + * @see DBMap_impl#options + * @see DBMap#options + */ +static DBOptions db_obj_options(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBOptions options; + + DB_COUNTSTAT(db_options); + if (db == NULL) return DB_OPT_BASE; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + options = db->options; + db_free_unlock(db); + + return options; +} + +/*****************************************************************************\ + * (5) Section with public functions. + * db_fix_options - Apply database type restrictions to the options. + * db_default_cmp - Get the default comparator for a type of database. + * db_default_hash - Get the default hasher for a type of database. + * db_default_release - Get the default releaser for a type of database with the specified options. + * db_custom_release - Get a releaser that behaves a certains way. + * db_alloc - Allocate a new database. + * db_i2key - Manual cast from 'int' to 'DBKey'. + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. + * db_i2data - Manual cast from 'int' to 'DBData'. + * db_ui2data - Manual cast from 'unsigned int' to 'DBData'. + * db_ptr2data - Manual cast from 'void*' to 'DBData'. + * db_data2i - Gets 'int' value from 'DBData'. + * db_data2ui - Gets 'unsigned int' value from 'DBData'. + * db_data2ptr - Gets 'void*' value from 'DBData'. + * db_init - Initializes the database system. + * db_final - Finalizes the database system. +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +DBOptions db_fix_options(DBType type, DBOptions options) +{ + DB_COUNTSTAT(db_fix_options); + switch (type) { + case DB_INT: + case DB_UINT: // Numeric database, do nothing with the keys + return (DBOptions)(options&~(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)); + + default: + ShowError("db_fix_options: Unknown database type %u with options %x\n", type, options); + case DB_STRING: + case DB_ISTRING: // String databases, no fix required + return options; + } +} + +/** + * Returns the default comparator for the specified type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see #db_int_cmp(DBKey,DBKey,unsigned short) + * @see #db_uint_cmp(DBKey,DBKey,unsigned short) + * @see #db_string_cmp(DBKey,DBKey,unsigned short) + * @see #db_istring_cmp(DBKey,DBKey,unsigned short) + */ +DBComparator db_default_cmp(DBType type) +{ + DB_COUNTSTAT(db_default_cmp); + switch (type) { + case DB_INT: return &db_int_cmp; + case DB_UINT: return &db_uint_cmp; + case DB_STRING: return &db_string_cmp; + case DB_ISTRING: return &db_istring_cmp; + default: + ShowError("db_default_cmp: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see #db_int_hash(DBKey,unsigned short) + * @see #db_uint_hash(DBKey,unsigned short) + * @see #db_string_hash(DBKey,unsigned short) + * @see #db_istring_hash(DBKey,unsigned short) + */ +DBHasher db_default_hash(DBType type) +{ + DB_COUNTSTAT(db_default_hash); + switch (type) { + case DB_INT: return &db_int_hash; + case DB_UINT: return &db_uint_hash; + case DB_STRING: return &db_string_hash; + case DB_ISTRING: return &db_istring_hash; + default: + ShowError("db_default_hash: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed with {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser. + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the specified options + * @public + * @see #db_release_nothing(DBKey,DBData,DBRelease) + * @see #db_release_key(DBKey,DBData,DBRelease) + * @see #db_release_data(DBKey,DBData,DBRelease) + * @see #db_release_both(DBKey,DBData,DBRelease) + * @see #db_custom_release(DBRelease) + */ +DBReleaser db_default_release(DBType type, DBOptions options) +{ + DB_COUNTSTAT(db_default_release); + options = db_fix_options(type, options); + if (options&DB_OPT_RELEASE_DATA) { // Release data, what about the key? + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return &db_release_both; // Release both key and data + return &db_release_data; // Only release data + } + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return &db_release_key; // Only release key + return &db_release_nothing; // Release nothing +} + +/** + * Returns the releaser that releases the specified release options. + * @param which Options that specified what the releaser releases + * @return Releaser for the specified release options + * @public + * @see #db_release_nothing(DBKey,DBData,DBRelease) + * @see #db_release_key(DBKey,DBData,DBRelease) + * @see #db_release_data(DBKey,DBData,DBRelease) + * @see #db_release_both(DBKey,DBData,DBRelease) + * @see #db_default_release(DBType,DBOptions) + */ +DBReleaser db_custom_release(DBRelease which) +{ + DB_COUNTSTAT(db_custom_release); + switch (which) { + case DB_RELEASE_NOTHING: return &db_release_nothing; + case DB_RELEASE_KEY: return &db_release_key; + case DB_RELEASE_DATA: return &db_release_data; + case DB_RELEASE_BOTH: return &db_release_both; + default: + ShowError("db_custom_release: Unknown release options %u\n", which); + return NULL; + } +} + +/** + * Allocate a new database of the specified type. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases. If 0, the maximum number of maxlen is used (64K). + * @return The interface of the database + * @public + * @see #DBMap_impl + * @see #db_fix_options(DBType,DBOptions) + */ +DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen) +{ + DBMap_impl* db; + unsigned int i; + +#ifdef DB_ENABLE_STATS + DB_COUNTSTAT(db_alloc); + switch (type) { + case DB_INT: DB_COUNTSTAT(db_int_alloc); break; + case DB_UINT: DB_COUNTSTAT(db_uint_alloc); break; + case DB_STRING: DB_COUNTSTAT(db_string_alloc); break; + case DB_ISTRING: DB_COUNTSTAT(db_istring_alloc); break; + } +#endif /* DB_ENABLE_STATS */ + CREATE(db, struct DBMap_impl, 1); + + options = db_fix_options(type, options); + /* Interface of the database */ + db->vtable.iterator = db_obj_iterator; + db->vtable.exists = db_obj_exists; + db->vtable.get = db_obj_get; + db->vtable.getall = db_obj_getall; + db->vtable.vgetall = db_obj_vgetall; + db->vtable.ensure = db_obj_ensure; + db->vtable.vensure = db_obj_vensure; + db->vtable.put = db_obj_put; + db->vtable.remove = db_obj_remove; + db->vtable.foreach = db_obj_foreach; + db->vtable.vforeach = db_obj_vforeach; + db->vtable.clear = db_obj_clear; + db->vtable.vclear = db_obj_vclear; + db->vtable.destroy = db_obj_destroy; + db->vtable.vdestroy = db_obj_vdestroy; + db->vtable.size = db_obj_size; + db->vtable.type = db_obj_type; + db->vtable.options = db_obj_options; + /* File and line of allocation */ + db->alloc_file = file; + db->alloc_line = line; + /* Lock system */ + db->free_list = NULL; + db->free_count = 0; + db->free_max = 0; + db->free_lock = 0; + /* Other */ + db->nodes = ers_new(sizeof(struct dbn),"db.c::db_alloc",ERS_OPT_NONE); + db->cmp = db_default_cmp(type); + db->hash = db_default_hash(type); + db->release = db_default_release(type, options); + for (i = 0; i < HASH_SIZE; i++) + db->ht[i] = NULL; + db->cache = NULL; + db->type = type; + db->options = options; + db->item_count = 0; + db->maxlen = maxlen; + db->global_lock = 0; + + if( db->maxlen == 0 && (type == DB_STRING || type == DB_ISTRING) ) + db->maxlen = UINT16_MAX; + + return &db->vtable; +} + +/** + * Manual cast from 'int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_i2key(int key) +{ + DBKey ret; + + DB_COUNTSTAT(db_i2key); + ret.i = key; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_ui2key(unsigned int key) +{ + DBKey ret; + + DB_COUNTSTAT(db_ui2key); + ret.ui = key; + return ret; +} + +/** + * Manual cast from 'const char *' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_str2key(const char *key) +{ + DBKey ret; + + DB_COUNTSTAT(db_str2key); + ret.str = key; + return ret; +} + +/** + * Manual cast from 'int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_i2data(int data) +{ + DBData ret; + + DB_COUNTSTAT(db_i2data); + ret.type = DB_DATA_INT; + ret.u.i = data; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ui2data(unsigned int data) +{ + DBData ret; + + DB_COUNTSTAT(db_ui2data); + ret.type = DB_DATA_UINT; + ret.u.ui = data; + return ret; +} + +/** + * Manual cast from 'void *' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ptr2data(void *data) +{ + DBData ret; + + DB_COUNTSTAT(db_ptr2data); + ret.type = DB_DATA_PTR; + ret.u.ptr = data; + return ret; +} + +/** + * Gets int type data from struct DBData. + * If data is not int type, returns 0. + * @param data Data + * @return Integer value of the data. + * @public + */ +int db_data2i(DBData *data) +{ + DB_COUNTSTAT(db_data2i); + if (data && DB_DATA_INT == data->type) + return data->u.i; + return 0; +} + +/** + * Gets unsigned int type data from struct DBData. + * If data is not unsigned int type, returns 0. + * @param data Data + * @return Unsigned int value of the data. + * @public + */ +unsigned int db_data2ui(DBData *data) +{ + DB_COUNTSTAT(db_data2ui); + if (data && DB_DATA_UINT == data->type) + return data->u.ui; + return 0; +} + +/** + * Gets void* type data from struct DBData. + * If data is not void* type, returns NULL. + * @param data Data + * @return Void* value of the data. + * @public + */ +void* db_data2ptr(DBData *data) +{ + DB_COUNTSTAT(db_data2ptr); + if (data && DB_DATA_PTR == data->type) + return data->u.ptr; + return NULL; +} + +/** + * Initializes the database system. + * @public + * @see #db_final(void) + */ +void db_init(void) +{ + DB_COUNTSTAT(db_init); +} + +/** + * Finalizes the database system. + * @public + * @see #db_init(void) + */ +void db_final(void) +{ +#ifdef DB_ENABLE_STATS + DB_COUNTSTAT(db_final); + ShowInfo(CL_WHITE"Database nodes"CL_RESET":\n" + "allocated %u, freed %u\n", + stats.db_node_alloc, stats.db_node_free); + ShowInfo(CL_WHITE"Database types"CL_RESET":\n" + "DB_INT : allocated %10u, destroyed %10u\n" + "DB_UINT : allocated %10u, destroyed %10u\n" + "DB_STRING : allocated %10u, destroyed %10u\n" + "DB_ISTRING : allocated %10u, destroyed %10u\n", + stats.db_int_alloc, stats.db_int_destroy, + stats.db_uint_alloc, stats.db_uint_destroy, + stats.db_string_alloc, stats.db_string_destroy, + stats.db_istring_alloc, stats.db_istring_destroy); + ShowInfo(CL_WHITE"Database function counters"CL_RESET":\n" + "db_rotate_left %10u, db_rotate_right %10u,\n" + "db_rebalance %10u, db_rebalance_erase %10u,\n" + "db_is_key_null %10u,\n" + "db_dup_key %10u, db_dup_key_free %10u,\n" + "db_free_add %10u, db_free_remove %10u,\n" + "db_free_lock %10u, db_free_unlock %10u,\n" + "db_int_cmp %10u, db_uint_cmp %10u,\n" + "db_string_cmp %10u, db_istring_cmp %10u,\n" + "db_int_hash %10u, db_uint_hash %10u,\n" + "db_string_hash %10u, db_istring_hash %10u,\n" + "db_release_nothing %10u, db_release_key %10u,\n" + "db_release_data %10u, db_release_both %10u,\n" + "dbit_first %10u, dbit_last %10u,\n" + "dbit_next %10u, dbit_prev %10u,\n" + "dbit_exists %10u, dbit_remove %10u,\n" + "dbit_destroy %10u, db_iterator %10u,\n" + "db_exits %10u, db_get %10u,\n" + "db_getall %10u, db_vgetall %10u,\n" + "db_ensure %10u, db_vensure %10u,\n" + "db_put %10u, db_remove %10u,\n" + "db_foreach %10u, db_vforeach %10u,\n" + "db_clear %10u, db_vclear %10u,\n" + "db_destroy %10u, db_vdestroy %10u,\n" + "db_size %10u, db_type %10u,\n" + "db_options %10u, db_fix_options %10u,\n" + "db_default_cmp %10u, db_default_hash %10u,\n" + "db_default_release %10u, db_custom_release %10u,\n" + "db_alloc %10u, db_i2key %10u,\n" + "db_ui2key %10u, db_str2key %10u,\n" + "db_i2data %10u, db_ui2data %10u,\n" + "db_ptr2data %10u, db_data2i %10u,\n" + "db_data2ui %10u, db_data2ptr %10u,\n" + "db_init %10u, db_final %10u\n", + stats.db_rotate_left, stats.db_rotate_right, + stats.db_rebalance, stats.db_rebalance_erase, + stats.db_is_key_null, + stats.db_dup_key, stats.db_dup_key_free, + stats.db_free_add, stats.db_free_remove, + stats.db_free_lock, stats.db_free_unlock, + stats.db_int_cmp, stats.db_uint_cmp, + stats.db_string_cmp, stats.db_istring_cmp, + stats.db_int_hash, stats.db_uint_hash, + stats.db_string_hash, stats.db_istring_hash, + stats.db_release_nothing, stats.db_release_key, + stats.db_release_data, stats.db_release_both, + stats.dbit_first, stats.dbit_last, + stats.dbit_next, stats.dbit_prev, + stats.dbit_exists, stats.dbit_remove, + stats.dbit_destroy, stats.db_iterator, + stats.db_exists, stats.db_get, + stats.db_getall, stats.db_vgetall, + stats.db_ensure, stats.db_vensure, + stats.db_put, stats.db_remove, + stats.db_foreach, stats.db_vforeach, + stats.db_clear, stats.db_vclear, + stats.db_destroy, stats.db_vdestroy, + stats.db_size, stats.db_type, + stats.db_options, stats.db_fix_options, + stats.db_default_cmp, stats.db_default_hash, + stats.db_default_release, stats.db_custom_release, + stats.db_alloc, stats.db_i2key, + stats.db_ui2key, stats.db_str2key, + stats.db_i2data, stats.db_ui2data, + stats.db_ptr2data, stats.db_data2i, + stats.db_data2ui, stats.db_data2ptr, + stats.db_init, stats.db_final); +#endif /* DB_ENABLE_STATS */ +} + +// Link DB System - jAthena +void linkdb_insert( struct linkdb_node** head, void *key, void* data) +{ + struct linkdb_node *node; + if( head == NULL ) return ; + node = (struct linkdb_node*)aMalloc( sizeof(struct linkdb_node) ); + if( *head == NULL ) { + // first node + *head = node; + node->prev = NULL; + node->next = NULL; + } else { + // link nodes + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + node->key = key; + node->data = data; +} + +void linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ... ) +{ + struct linkdb_node *node; + if( head == NULL ) return; + node = *head; + while ( node ) { + va_list args; + va_start(args, func); + func( node->key, node->data, args ); + va_end(args); + node = node->next; + } +} + +void* linkdb_search( struct linkdb_node** head, void *key) +{ + int n = 0; + struct linkdb_node *node; + if( head == NULL ) return NULL; + node = *head; + while( node ) { + if( node->key == key ) { + if( node->prev && n > 5 ) { + //Moving the head in order to improve processing efficiency + if(node->prev) node->prev->next = node->next; + if(node->next) node->next->prev = node->prev; + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + return node->data; + } + node = node->next; + n++; + } + return NULL; +} + +void* linkdb_erase( struct linkdb_node** head, void *key) +{ + struct linkdb_node *node; + if( head == NULL ) return NULL; + node = *head; + while( node ) { + if( node->key == key ) { + void *data = node->data; + if( node->prev == NULL ) + *head = node->next; + else + node->prev->next = node->next; + if( node->next ) + node->next->prev = node->prev; + aFree( node ); + return data; + } + node = node->next; + } + return NULL; +} + +void linkdb_replace( struct linkdb_node** head, void *key, void *data ) +{ + int n = 0; + struct linkdb_node *node; + if( head == NULL ) return ; + node = *head; + while( node ) { + if( node->key == key ) { + if( node->prev && n > 5 ) { + //Moving the head in order to improve processing efficiency + if(node->prev) node->prev->next = node->next; + if(node->next) node->next->prev = node->prev; + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + node->data = data; + return ; + } + node = node->next; + n++; + } + //Insert because it can not find + linkdb_insert( head, key, data ); +} + +void linkdb_final( struct linkdb_node** head ) +{ + struct linkdb_node *node, *node2; + if( head == NULL ) return ; + node = *head; + while( node ) { + node2 = node->next; + aFree( node ); + node = node2; + } + *head = NULL; +} diff --git a/src/common/db.h b/src/common/db.h new file mode 100644 index 000000000..4fe6a93d6 --- /dev/null +++ b/src/common/db.h @@ -0,0 +1,1492 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * This file is separated in two sections: * + * (1) public typedefs, enums, unions, structures and defines * + * (2) public functions * + * * + * <B>Notes on the release system:</B> * + * Whenever an entry is removed from the database both the key and the * + * data are requested to be released. * + * At least one entry is removed when replacing an entry, removing an * + * entry, clearing the database or destroying the database. * + * What is actually released is defined by the release function, the * + * functions of the database only ask for the key and/or data to be * + * released. * + * * + * TODO: * + * - create a custom database allocator * + * - see what functions need or should be added to the database interface * + * * + * HISTORY: * + * 2012/03/09 - Added enum for data types (int, uint, void*) * + * 2007/11/09 - Added an iterator to the database. * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBMap#ensure(DBMap,DBKey,DBCreateData,...)} and * + * {@link DBMap#clear(DBMap,DBApply,...)}. * + * 2.0 (Athena build 4859) - Transition version * + * - Almost everything recoded with a strategy similar to objects, * + * database structure is maintained. * + * 1.0 (up to Athena build 4706) * + * - Previous database system. * + * * + * @version 2.1 (Athena build #???#) - Portability fix * + * @author (Athena build 4859) Flavio @ Amazon Project * + * @author (up to Athena build 4706) Athena Dev Teams * + * @encoding US-ASCII * + * @see common#db.c * +\*****************************************************************************/ +#ifndef _DB_H_ +#define _DB_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h> + +/*****************************************************************************\ + * (1) Section with public typedefs, enums, unions, structures and defines. * + * DBRelease - Enumeration of release options. * + * DBType - Enumeration of database types. * + * DBOptions - Bitfield enumeration of database options. * + * DBKey - Union of used key types. * + * DBDataType - Enumeration of data types. * + * DBData - Struct for used data types. * + * DBApply - Format of functions applied to the databases. * + * DBMatcher - Format of matchers used in DBMap::getall. * + * DBComparator - Format of the comparators used by the databases. * + * DBHasher - Format of the hashers used by the databases. * + * DBReleaser - Format of the releasers used by the databases. * + * DBIterator - Database iterator. * + * DBMap - Database interface. * +\*****************************************************************************/ + +/** + * Bitfield with what should be released by the releaser function (if the + * function supports it). + * @public + * @see #DBReleaser + * @see #db_custom_release(DBRelease) + */ +typedef enum DBRelease { + DB_RELEASE_NOTHING = 0, + DB_RELEASE_KEY = 1, + DB_RELEASE_DATA = 2, + DB_RELEASE_BOTH = 3 +} DBRelease; + +/** + * Supported types of database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_INT Uses int's for keys + * @param DB_UINT Uses unsigned int's for keys + * @param DB_STRING Uses strings for keys. + * @param DB_ISTRING Uses case insensitive strings for keys. + * @public + * @see #DBOptions + * @see #DBKey + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum DBType { + DB_INT, + DB_UINT, + DB_STRING, + DB_ISTRING +} DBType; + +/** + * Bitfield of options that define the behaviour of the database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_OPT_BASE Base options: does not duplicate keys, releases nothing + * and does not allow NULL keys or NULL data. + * @param DB_OPT_DUP_KEY Duplicates the keys internally. If DB_OPT_RELEASE_KEY + * is defined, the real key is freed as soon as the entry is added. + * @param DB_OPT_RELEASE_KEY Releases the key. + * @param DB_OPT_RELEASE_DATA Releases the data whenever an entry is removed + * from the database. + * WARNING: for funtions that return the data (like DBMap::remove), + * a dangling pointer will be returned. + * @param DB_OPT_RELEASE_BOTH Releases both key and data. + * @param DB_OPT_ALLOW_NULL_KEY Allow NULL keys in the database. + * @param DB_OPT_ALLOW_NULL_DATA Allow NULL data in the database. + * @public + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum DBOptions { + DB_OPT_BASE = 0, + DB_OPT_DUP_KEY = 1, + DB_OPT_RELEASE_KEY = 2, + DB_OPT_RELEASE_DATA = 4, + DB_OPT_RELEASE_BOTH = 6, + DB_OPT_ALLOW_NULL_KEY = 8, + DB_OPT_ALLOW_NULL_DATA = 16, +} DBOptions; + +/** + * Union of key types used by the database. + * @param i Type of key for DB_INT databases + * @param ui Type of key for DB_UINT databases + * @param str Type of key for DB_STRING and DB_ISTRING databases + * @public + * @see #DBType + * @see DBMap#get + * @see DBMap#put + * @see DBMap#remove + */ +typedef union DBKey { + int i; + unsigned int ui; + const char *str; +} DBKey; + +/** + * Supported types of database data. + * @param DB_DATA_INT Uses ints for data. + * @param DB_DATA_UINT Uses unsigned ints for data. + * @param DB_DATA_PTR Uses void pointers for data. + * @public + * @see #DBData + */ +typedef enum DBDataType { + DB_DATA_INT, + DB_DATA_UINT, + DB_DATA_PTR +} DBDataType; + +/** + * Struct for data types used by the database. + * @param type Type of data + * @param u Union of available data types + * @param u.i Data of int type + * @param u.ui Data of unsigned int type + * @param u.ptr Data of void* type + * @public + */ +typedef struct DBData { + DBDataType type; + union { + int i; + unsigned int ui; + void *ptr; + } u; +} DBData; + +/** + * Format of functions that create the data for the key when the entry doesn't + * exist in the database yet. + * @param key Key of the database entry + * @param args Extra arguments of the function + * @return Data identified by the key to be put in the database + * @public + * @see DBMap#vensure + * @see DBMap#ensure + */ +typedef DBData (*DBCreateData)(DBKey key, va_list args); + +/** + * Format of functions to be applied to an unspecified quantity of entries of + * a database. + * Any function that applies this function to the database will return the sum + * of values returned by this function. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the function + * @return Value to be added up by the function that is applying this + * @public + * @see DBMap#vforeach + * @see DBMap#foreach + * @see DBMap#vdestroy + * @see DBMap#destroy + */ +typedef int (*DBApply)(DBKey key, DBData *data, va_list args); + +/** + * Format of functions that match database entries. + * The purpose of the match depends on the function that is calling the matcher. + * Returns 0 if it is a match, another number otherwise. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the function + * @return 0 if a match, another number otherwise + * @public + * @see DBMap#getall + */ +typedef int (*DBMatcher)(DBKey key, DBData data, va_list args); + +/** + * Format of the comparators used internally by the database system. + * Compares key1 to key2. + * Returns 0 is equal, negative if lower and positive is higher. + * @param key1 Key being compared + * @param key2 Key we are comparing to + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return 0 if equal, negative if lower and positive if higher + * @public + * @see #db_default_cmp(DBType) + */ +typedef int (*DBComparator)(DBKey key1, DBKey key2, unsigned short maxlen); + +/** + * Format of the hashers used internally by the database system. + * Creates the hash of the key. + * @param key Key being hashed + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return Hash of the key + * @public + * @see #db_default_hash(DBType) + */ +typedef unsigned int (*DBHasher)(DBKey key, unsigned short maxlen); + +/** + * Format of the releaser used by the database system. + * Releases nothing, the key, the data or both. + * All standard releasers use aFree to release. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @public + * @see #DBRelease + * @see #db_default_releaser(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +typedef void (*DBReleaser)(DBKey key, DBData data, DBRelease which); + + + +typedef struct DBIterator DBIterator; +typedef struct DBMap DBMap; + + + +/** + * Database iterator. + * Supports forward iteration, backward iteration and removing entries from the database. + * The iterator is initially positioned before the first entry of the database. + * While the iterator exists the database is locked internally, so invoke + * {@link DBIterator#destroy} as soon as possible. + * @public + * @see #DBMap + */ +struct DBIterator +{ + + /** + * Fetches the first entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*first)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the last entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*last)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the next entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*next)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the previous entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*prev)(DBIterator* self, DBKey* out_key); + + /** + * Returns true if the fetched entry exists. + * The databases entries might have NULL data, so use this to to test if + * the iterator is done. + * @param self Iterator + * @return true is the entry exists + * @protected + */ + bool (*exists)(DBIterator* self); + + /** + * Removes the current entry from the database. + * NOTE: {@link DBIterator#exists} will return false until another entry + * is fetched + * Puts data of the removed entry in out_data, if out_data is not NULL. + * @param self Iterator + * @param out_data Data of the removed entry. + * @return 1 if entry was removed, 0 otherwise + * @protected + * @see DBMap#remove + */ + int (*remove)(DBIterator* self, DBData *out_data); + + /** + * Destroys this iterator and unlocks the database. + * @param self Iterator + * @protected + */ + void (*destroy)(DBIterator* self); + +}; + +/** + * Public interface of a database. Only contains funtions. + * All the functions take the interface as the first argument. + * @public + * @see #db_alloc(const char*,int,DBType,DBOptions,unsigned short) + */ +struct DBMap { + + /** + * Returns a new iterator for this database. + * The iterator keeps the database locked until it is destroyed. + * The database will keep functioning normally but will only free internal + * memory when unlocked, so destroy the iterator as soon as possible. + * @param self Database + * @return New iterator + * @protected + */ + DBIterator* (*iterator)(DBMap* self); + + /** + * Returns true if the entry exists. + * @param self Database + * @param key Key that identifies the entry + * @return true is the entry exists + * @protected + */ + bool (*exists)(DBMap* self, DBKey key); + + /** + * Get the data of the entry identified by the key. + * @param self Database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + */ + DBData* (*get)(DBMap* self, DBKey key); + + /** + * Just calls {@link DBMap#vgetall}. + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall(DBMap*,void **,unsigned int,DBMatcher,va_list) + */ + unsigned int (*getall)(DBMap* self, DBData** buf, unsigned int max, DBMatcher match, ...); + + /** + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#getall(DBMap*,void **,unsigned int,DBMatcher,...) + */ + unsigned int (*vgetall)(DBMap* self, DBData** buf, unsigned int max, DBMatcher match, va_list args); + + /** + * Just calls {@link DBMap#vensure}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure(DBMap*,DBKey,DBCreateData,va_list) + */ + DBData* (*ensure)(DBMap* self, DBKey key, DBCreateData create, ...); + + /** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#ensure(DBMap*,DBKey,DBCreateData,...) + */ + DBData* (*vensure)(DBMap* self, DBKey key, DBCreateData create, va_list args); + + /** + * Put the data identified by the key in the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: Uses the new key, the old one is released. + * @param self Database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + */ + int (*put)(DBMap* self, DBKey key, DBData data, DBData *out_data); + + /** + * Remove an entry from the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: The key (of the database) is released. + * @param self Database + * @param key Key that identifies the entry + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + */ + int (*remove)(DBMap* self, DBKey key, DBData *out_data); + + /** + * Just calls {@link DBMap#vforeach}. + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Database + * @param func Function to be applied + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach(DBMap*,DBApply,va_list) + */ + int (*foreach)(DBMap* self, DBApply func, ...); + + /** + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Database + * @param func Function to be applied + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#foreach(DBMap*,DBApply,...) + */ + int (*vforeach)(DBMap* self, DBApply func, va_list args); + + /** + * Just calls {@link DBMap#vclear}. + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear(DBMap*,DBApply,va_list) + */ + int (*clear)(DBMap* self, DBApply func, ...); + + /** + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#clear(DBMap*,DBApply,...) + */ + int (*vclear)(DBMap* self, DBApply func, va_list args); + + /** + * Just calls {@link DBMap#vdestroy}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy(DBMap*,DBApply,va_list) + */ + int (*destroy)(DBMap* self, DBApply func, ...); + + /** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#destroy(DBMap*,DBApply,...) + */ + int (*vdestroy)(DBMap* self, DBApply func, va_list args); + + /** + * Return the size of the database (number of items in the database). + * @param self Database + * @return Size of the database + * @protected + */ + unsigned int (*size)(DBMap* self); + + /** + * Return the type of the database. + * @param self Database + * @return Type of the database + * @protected + */ + DBType (*type)(DBMap* self); + + /** + * Return the options of the database. + * @param self Database + * @return Options of the database + * @protected + */ + DBOptions (*options)(DBMap* self); + +}; + +// For easy access to the common functions. + +#define db_exists(db,k) ( (db)->exists((db),(k)) ) +#define idb_exists(db,k) ( (db)->exists((db),db_i2key(k)) ) +#define uidb_exists(db,k) ( (db)->exists((db),db_ui2key(k)) ) +#define strdb_exists(db,k) ( (db)->exists((db),db_str2key(k)) ) + +// Get pointer-type data from DBMaps of various key types +#define db_get(db,k) ( db_data2ptr((db)->get((db),(k))) ) +#define idb_get(db,k) ( db_data2ptr((db)->get((db),db_i2key(k))) ) +#define uidb_get(db,k) ( db_data2ptr((db)->get((db),db_ui2key(k))) ) +#define strdb_get(db,k) ( db_data2ptr((db)->get((db),db_str2key(k))) ) + +// Get int-type data from DBMaps of various key types +#define db_iget(db,k) ( db_data2i((db)->get((db),(k))) ) +#define idb_iget(db,k) ( db_data2i((db)->get((db),db_i2key(k))) ) +#define uidb_iget(db,k) ( db_data2i((db)->get((db),db_ui2key(k))) ) +#define strdb_iget(db,k) ( db_data2i((db)->get((db),db_str2key(k))) ) + +// Get uint-type data from DBMaps of various key types +#define db_uiget(db,k) ( db_data2ui((db)->get((db),(k))) ) +#define idb_uiget(db,k) ( db_data2ui((db)->get((db),db_i2key(k))) ) +#define uidb_uiget(db,k) ( db_data2ui((db)->get((db),db_ui2key(k))) ) +#define strdb_uiget(db,k) ( db_data2ui((db)->get((db),db_str2key(k))) ) + +// Put pointer-type data into DBMaps of various key types +#define db_put(db,k,d) ( (db)->put((db),(k),db_ptr2data(d),NULL) ) +#define idb_put(db,k,d) ( (db)->put((db),db_i2key(k),db_ptr2data(d),NULL) ) +#define uidb_put(db,k,d) ( (db)->put((db),db_ui2key(k),db_ptr2data(d),NULL) ) +#define strdb_put(db,k,d) ( (db)->put((db),db_str2key(k),db_ptr2data(d),NULL) ) + +// Put int-type data into DBMaps of various key types +#define db_iput(db,k,d) ( (db)->put((db),(k),db_i2data(d),NULL) ) +#define idb_iput(db,k,d) ( (db)->put((db),db_i2key(k),db_i2data(d),NULL) ) +#define uidb_iput(db,k,d) ( (db)->put((db),db_ui2key(k),db_i2data(d),NULL) ) +#define strdb_iput(db,k,d) ( (db)->put((db),db_str2key(k),db_i2data(d),NULL) ) + +// Put uint-type data into DBMaps of various key types +#define db_uiput(db,k,d) ( (db)->put((db),(k),db_ui2data(d),NULL) ) +#define idb_uiput(db,k,d) ( (db)->put((db),db_i2key(k),db_ui2data(d),NULL) ) +#define uidb_uiput(db,k,d) ( (db)->put((db),db_ui2key(k),db_ui2data(d),NULL) ) +#define strdb_uiput(db,k,d) ( (db)->put((db),db_str2key(k),db_ui2data(d),NULL) ) + +// Remove entry from DBMaps of various key types +#define db_remove(db,k) ( (db)->remove((db),(k),NULL) ) +#define idb_remove(db,k) ( (db)->remove((db),db_i2key(k),NULL) ) +#define uidb_remove(db,k) ( (db)->remove((db),db_ui2key(k),NULL) ) +#define strdb_remove(db,k) ( (db)->remove((db),db_str2key(k),NULL) ) + +//These are discarding the possible vargs you could send to the function, so those +//that require vargs must not use these defines. +#define db_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),(k),(f))) ) +#define idb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_i2key(k),(f))) ) +#define uidb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_ui2key(k),(f))) ) +#define strdb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_str2key(k),(f))) ) + +// Database creation and destruction macros +#define idb_alloc(opt) db_alloc(__FILE__,__LINE__,DB_INT,(opt),sizeof(int)) +#define uidb_alloc(opt) db_alloc(__FILE__,__LINE__,DB_UINT,(opt),sizeof(unsigned int)) +#define strdb_alloc(opt,maxlen) db_alloc(__FILE__,__LINE__,DB_STRING,(opt),(maxlen)) +#define stridb_alloc(opt,maxlen) db_alloc(__FILE__,__LINE__,DB_ISTRING,(opt),(maxlen)) +#define db_destroy(db) ( (db)->destroy((db),NULL) ) +// Other macros +#define db_clear(db) ( (db)->clear(db,NULL) ) +#define db_size(db) ( (db)->size(db) ) +#define db_iterator(db) ( (db)->iterator(db) ) +#define dbi_first(dbi) ( db_data2ptr((dbi)->first(dbi,NULL)) ) +#define dbi_last(dbi) ( db_data2ptr((dbi)->last(dbi,NULL)) ) +#define dbi_next(dbi) ( db_data2ptr((dbi)->next(dbi,NULL)) ) +#define dbi_prev(dbi) ( db_data2ptr((dbi)->prev(dbi,NULL)) ) +#define dbi_remove(dbi) ( (dbi)->remove(dbi,NULL) ) +#define dbi_exists(dbi) ( (dbi)->exists(dbi) ) +#define dbi_destroy(dbi) ( (dbi)->destroy(dbi) ) + +/*****************************************************************************\ + * (2) Section with public functions. * + * db_fix_options - Fix the options for a type of database. * + * db_default_cmp - Get the default comparator for a type of database. * + * db_default_hash - Get the default hasher for a type of database. * + * db_default_release - Get the default releaser for a type of database * + * with the fixed options. * + * db_custom_release - Get the releaser that behaves as specified. * + * db_alloc - Allocate a new database. * + * db_i2key - Manual cast from 'int' to 'DBKey'. * + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. * + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. * + * db_i2data - Manual cast from 'int' to 'DBData'. * + * db_ui2data - Manual cast from 'unsigned int' to 'DBData'. * + * db_ptr2data - Manual cast from 'void*' to 'DBData'. * + * db_data2i - Gets 'int' value from 'DBData'. * + * db_data2ui - Gets 'unsigned int' value from 'DBData'. * + * db_data2ptr - Gets 'void*' value from 'DBData'. * + * db_init - Initializes the database system. * + * db_final - Finalizes the database system. * +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see #DBType + * @see #DBOptions + * @see #db_default_release(DBType,DBOptions) + */ +DBOptions db_fix_options(DBType type, DBOptions options); + +/** + * Returns the default comparator for the type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBComparator + */ +DBComparator db_default_cmp(DBType type); + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBHasher + */ +DBHasher db_default_hash(DBType type); + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the fixed options + * @public + * @see #DBType + * @see #DBOptions + * @see #DBReleaser + * @see #db_fix_options(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +DBReleaser db_default_release(DBType type, DBOptions options); + +/** + * Returns the releaser that behaves as <code>which</code> specifies. + * @param which Defines what the releaser releases + * @return Releaser for the specified release options + * @public + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +DBReleaser db_custom_release(DBRelease which); + +/** + * Allocate a new database of the specified type. + * It uses the default comparator, hasher and releaser of the specified + * database type and fixed options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases. If 0, the maximum number of maxlen is used (64K). + * @return The interface of the database + * @public + * @see #DBType + * @see #DBMap + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_fix_options(DBType,DBOptions) + */ +DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen); + +/** + * Manual cast from 'int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_i2key(int key); + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_ui2key(unsigned int key); + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_str2key(const char *key); + +/** + * Manual cast from 'int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_i2data(int data); + +/** + * Manual cast from 'unsigned int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ui2data(unsigned int data); + +/** + * Manual cast from 'void *' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ptr2data(void *data); + +/** + * Gets int type data from struct DBData. + * If data is not int type, returns 0. + * @param data Data + * @return Integer value of the data. + * @public + */ +int db_data2i(DBData *data); + +/** + * Gets unsigned int type data from struct DBData. + * If data is not unsigned int type, returns 0. + * @param data Data + * @return Unsigned int value of the data. + * @public + */ +unsigned int db_data2ui(DBData *data); + +/** + * Gets void* type data from struct DBData. + * If data is not void* type, returns NULL. + * @param data Data + * @return Void* value of the data. + * @public + */ +void* db_data2ptr(DBData *data); + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + */ +void db_init(void); + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see #db_init(void) + */ +void db_final(void); + +// Link DB System - From jAthena +struct linkdb_node { + struct linkdb_node *next; + struct linkdb_node *prev; + void *key; + void *data; +}; + +typedef void (*LinkDBFunc)(void* key, void* data, va_list args); + +void linkdb_insert ( struct linkdb_node** head, void *key, void* data); // 重複を考慮しない +void linkdb_replace( struct linkdb_node** head, void *key, void* data); // 重複を考慮する +void* linkdb_search ( struct linkdb_node** head, void *key); +void* linkdb_erase ( struct linkdb_node** head, void *key); +void linkdb_final ( struct linkdb_node** head ); +void linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ... ); + + + +/// Finds an entry in an array. +/// ex: ARR_FIND(0, size, i, list[i] == target); +/// +/// @param __start Starting index (ex: 0) +/// @param __end End index (ex: size of the array) +/// @param __var Index variable +/// @param __cmp Expression that returns true when the target entry is found +#define ARR_FIND(__start, __end, __var, __cmp) \ + do{ \ + for( (__var) = (__start); (__var) < (__end); ++(__var) ) \ + if( __cmp ) \ + break; \ + }while(0) + + + +/// Moves an entry of the array. +/// Use ARR_MOVERIGHT/ARR_MOVELEFT if __from and __to are direct numbers. +/// ex: ARR_MOVE(i, 0, list, int);// move index i to index 0 +/// +/// +/// @param __from Initial index of the entry +/// @param __to Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVE(__from, __to, __arr, __type) \ + do{ \ + if( (__from) != (__to) ) \ + { \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + if( (__from) < (__to) ) \ + memmove((__arr)+(__from), (__arr)+(__from)+1, ((__to)-(__from))*sizeof(__type)); \ + else if( (__from) > (__to) ) \ + memmove((__arr)+(__to)+1, (__arr)+(__to), ((__from)-(__to))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + } \ + }while(0) + + + +/// Moves an entry of the array to the right. +/// ex: ARR_MOVERIGHT(1, 4, list, int);// move index 1 to index 4 +/// +/// @param __from Initial index of the entry +/// @param __to Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVERIGHT(__from, __to, __arr, __type) \ + do{ \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + memmove((__arr)+(__from), (__arr)+(__from)+1, ((__to)-(__from))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + }while(0) + + + +/// Moves an entry of the array to the left. +/// ex: ARR_MOVELEFT(3, 0, list, int);// move index 3 to index 0 +/// +/// @param __from Initial index of the entry +/// @param __end Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVELEFT(__from, __to, __arr, __type) \ + do{ \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + memmove((__arr)+(__to)+1, (__arr)+(__to), ((__from)-(__to))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + }while(0) + + + +///////////////////////////////////////////////////////////////////// +// Vector library based on defines. (dynamic array) +// uses aMalloc, aRealloc, aFree + + + +/// Declares an anonymous vector struct. +/// +/// @param __type Type of data +#define VECTOR_DECL(__type) \ + struct { \ + size_t _max_; \ + size_t _len_; \ + __type* _data_; \ + } + + + +/// Declares a named vector struct. +/// +/// @param __name Structure name +/// @param __type Type of data +#define VECTOR_STRUCT_DECL(__name,__type) \ + struct __name { \ + size_t _max_; \ + size_t _len_; \ + __type* _data_; \ + } + + + +/// Declares and initializes an anonymous vector variable. +/// +/// @param __type Type of data +/// @param __var Variable name +#define VECTOR_VAR(__type,__var) \ + VECTOR_DECL(__type) __var = {0,0,NULL} + + + +/// Declares and initializes a named vector variable. +/// +/// @param __name Structure name +/// @param __var Variable name +#define VECTOR_STRUCT_VAR(__name,__var) \ + struct __name __var = {0,0,NULL} + + + +/// Initializes a vector. +/// +/// @param __vec Vector +#define VECTOR_INIT(__vec) \ + memset(&(__vec), 0, sizeof(__vec)) + + + +/// Returns the internal array of values. +/// +/// @param __vec Vector +/// @return Array of values +#define VECTOR_DATA(__vec) \ + ( (__vec)._data_ ) + + + +/// Returns the length of the vector. +/// +/// @param __vec Vector +/// @return Length +#define VECTOR_LENGTH(__vec) \ + ( (__vec)._len_ ) + + + +/// Returns the capacity of the vector. +/// +/// @param __vec Vector +/// @return Capacity +#define VECTOR_CAPACITY(__vec) \ + ( (__vec)._max_ ) + + + +/// Returns the value at the target index. +/// Assumes the index exists. +/// +/// @param __vec Vector +/// @param __idx Index +/// @return Value +#define VECTOR_INDEX(__vec,__idx) \ + ( VECTOR_DATA(__vec)[__idx] ) + + + +/// Returns the first value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return First value +#define VECTOR_FIRST(__vec) \ + ( VECTOR_INDEX(__vec,0) ) + + + +/// Returns the last value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return Last value +#define VECTOR_LAST(__vec) \ + ( VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)-1) ) + + + +/// Resizes the vector. +/// Excess values are discarded, new positions are zeroed. +/// +/// @param __vec Vector +/// @param __n Size +#define VECTOR_RESIZE(__vec,__n) \ + do{ \ + if( (__n) > VECTOR_CAPACITY(__vec) ) \ + { /* increase size */ \ + if( VECTOR_CAPACITY(__vec) == 0 ) SET_POINTER(VECTOR_DATA(__vec), aMalloc((__n)*sizeof(VECTOR_FIRST(__vec)))); /* allocate new */ \ + else SET_POINTER(VECTOR_DATA(__vec), aRealloc(VECTOR_DATA(__vec),(__n)*sizeof(VECTOR_FIRST(__vec)))); /* reallocate */ \ + memset(VECTOR_DATA(__vec)+VECTOR_LENGTH(__vec), 0, (VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec))*sizeof(VECTOR_FIRST(__vec))); /* clear new data */ \ + VECTOR_CAPACITY(__vec) = (__n); /* update capacity */ \ + } \ + else if( (__n) == 0 && VECTOR_CAPACITY(__vec) ) \ + { /* clear vector */ \ + aFree(VECTOR_DATA(__vec)); VECTOR_DATA(__vec) = NULL; /* free data */ \ + VECTOR_CAPACITY(__vec) = 0; /* clear capacity */ \ + VECTOR_LENGTH(__vec) = 0; /* clear length */ \ + } \ + else if( (__n) < VECTOR_CAPACITY(__vec) ) \ + { /* reduce size */ \ + SET_POINTER(VECTOR_DATA(__vec), aRealloc(VECTOR_DATA(__vec),(__n)*sizeof(VECTOR_FIRST(__vec)))); /* reallocate */ \ + VECTOR_CAPACITY(__vec) = (__n); /* update capacity */ \ + if( VECTOR_LENGTH(__vec) > (__n) ) VECTOR_LENGTH(__vec) = (__n); /* update length */ \ + } \ + }while(0) + + + +/// Ensures that the array has the target number of empty positions. +/// Increases the capacity in multiples of __step. +/// +/// @param __vec Vector +/// @param __n Empty positions +/// @param __step Increase +#define VECTOR_ENSURE(__vec,__n,__step) \ + do{ \ + size_t _empty_ = VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec); \ + while( (__n) > _empty_ ) _empty_ += (__step); \ + if( _empty_ != VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec) ) VECTOR_RESIZE(__vec,_empty_+VECTOR_LENGTH(__vec)); \ + }while(0) + + + +/// Inserts a zeroed value in the target index. +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +#define VECTOR_INSERTZEROED(__vec,__idx) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+1),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + memset(&VECTOR_INDEX(__vec,__idx), 0, sizeof(VECTOR_INDEX(__vec,__idx))); /* set zeroed value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the target index. (using the '=' operator) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __val Value +#define VECTOR_INSERT(__vec,__idx,__val) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+1),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + VECTOR_INDEX(__vec,__idx) = (__val); /* set value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the target index. (using memcpy) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __val Value +#define VECTOR_INSERTCOPY(__vec,__idx,__val) \ + VECTOR_INSERTARRAY(__vec,__idx,&(__val),1) + + + +/// Inserts the values of the array in the target index. (using memcpy) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __pval Array of values +/// @param __n Number of values +#define VECTOR_INSERTARRAY(__vec,__idx,__pval,__n) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+(__n)),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + memcpy(&VECTOR_INDEX(__vec,__idx), (__pval), (__n)*sizeof(VECTOR_FIRST(__vec))); /* set values */ \ + VECTOR_LENGTH(__vec) += (__n); /* increase length */ \ + }while(0) + + + +/// Inserts a zeroed value in the end of the vector. +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +#define VECTOR_PUSHZEROED(__vec) \ + do{ \ + memset(&VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)), 0, sizeof(VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)))); /* set zeroed value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + +/// Inserts a value in the end of the vector. (using the '=' operator) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __val Value +#define VECTOR_PUSH(__vec,__val) \ + do{ \ + VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)) = (__val); /* set value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the end of the vector. (using memcpy) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __val Value +#define VECTOR_PUSHCOPY(__vec,__val) \ + VECTOR_PUSHARRAY(__vec,&(__val),1) + + + +/// Inserts the values of the array in the end of the vector. (using memcpy) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __pval Array of values +/// @param __n Number of values +#define VECTOR_PUSHARRAY(__vec,__pval,__n) \ + do{ \ + memcpy(&VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)), (__pval), (__n)*sizeof(VECTOR_FIRST(__vec))); /* set values */ \ + VECTOR_LENGTH(__vec) += (__n); /* increase length */ \ + }while(0) + + + +/// Removes and returns the last value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return Removed value +#define VECTOR_POP(__vec) \ + ( VECTOR_INDEX(__vec,--VECTOR_LENGTH(__vec)) ) + + + +/// Removes the last N values of the vector and returns the value of the last pop. +/// Assumes there are enough values. +/// +/// @param __vec Vector +/// @param __n Number of pops +/// @return Last removed value +#define VECTOR_POPN(__vec,__n) \ + ( VECTOR_INDEX(__vec,(VECTOR_LENGTH(__vec)-=(__n))) ) + + + +/// Removes the target index from the vector. +/// Assumes the index is valid and there are enough values. +/// +/// @param __vec Vector +/// @param __idx Index +#define VECTOR_ERASE(__vec,__idx) \ + VECTOR_ERASEN(__vec,__idx,1) + + + +/// Removes N values from the target index of the vector. +/// Assumes the index is valid and there are enough values. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __n Number of values +#define VECTOR_ERASEN(__vec,__idx,__n) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec)-(__n) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,__idx),&VECTOR_INDEX(__vec,(__idx)+(__n)),(VECTOR_LENGTH(__vec)-((__idx)+(__n)))*sizeof(VECTOR_FIRST(__vec))); \ + VECTOR_LENGTH(__vec) -= (__n); /* decrease length */ \ + }while(0) + + + +/// Clears the vector, freeing allocated data. +/// +/// @param __vec Vector +#define VECTOR_CLEAR(__vec) \ + do{ \ + if( VECTOR_CAPACITY(__vec) ) \ + { \ + aFree(VECTOR_DATA(__vec)); VECTOR_DATA(__vec) = NULL; /* clear allocated array */ \ + VECTOR_CAPACITY(__vec) = 0; /* clear capacity */ \ + VECTOR_LENGTH(__vec) = 0; /* clear length */ \ + } \ + }while(0) + + + +///////////////////////////////////////////////////////////////////// +// Binary heap library based on defines. (uses the vector defines above) +// uses aMalloc, aRealloc, aFree + + + +/// Declares an anonymous binary heap struct. +/// +/// @param __type Type of data +#define BHEAP_DECL(__type) VECTOR_DECL(__type) + + + +/// Declares a named binary heap struct. +/// +/// @param __name Structure name +/// @param __type Type of data +#define BHEAP_STRUCT_DECL(__name,__type) VECTOR_STRUCT_DECL(__name,__type) + + + +/// Declares and initializes an anonymous binary heap variable. +/// +/// @param __type Type of data +/// @param __var Variable name +#define BHEAP_VAR(__type,__var) VECTOR_VAR(__type,__var) + + + +/// Declares and initializes a named binary heap variable. +/// +/// @param __name Structure name +/// @param __var Variable name +#define BHEAP_STRUCT_VAR(__name,__var) VECTOR_STRUCT_VAR(__name,__var) + + + +/// Initializes a heap. +/// +/// @param __heap Binary heap +#define BHEAP_INIT(__heap) VECTOR_INIT(__heap) + + + +/// Returns the internal array of values. +/// +/// @param __heap Binary heap +/// @return Array of values +#define BHEAP_DATA(__heap) VECTOR_DATA(__heap) + + + +/// Returns the length of the heap. +/// +/// @param __heap Binary heap +/// @return Length +#define BHEAP_LENGTH(__heap) VECTOR_LENGTH(__heap) + + + +/// Returns the capacity of the heap. +/// +/// @param __heap Binary heap +/// @return Capacity +#define BHEAP_CAPACITY(__heap) VECTOR_CAPACITY(__heap) + + + +/// Ensures that the heap has the target number of empty positions. +/// Increases the capacity in multiples of __step. +/// +/// @param __heap Binary heap +/// @param __n Empty positions +/// @param __step Increase +#define BHEAP_ENSURE(__heap,__n,__step) VECTOR_ENSURE(__heap,__n,__step) + + + +/// Returns the top value of the heap. +/// Assumes the heap is not empty. +/// +/// @param __heap Binary heap +/// @return Value at the top +#define BHEAP_PEEK(__heap) VECTOR_INDEX(__heap,0) + + + +/// Inserts a value in the heap. (using the '=' operator) +/// Assumes there is enough capacity. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __val Value +/// @param __topcmp Comparator +#define BHEAP_PUSH(__heap,__val,__topcmp) \ + do{ \ + size_t _i_ = VECTOR_LENGTH(__heap); \ + VECTOR_PUSH(__heap,__val); /* insert at end */ \ + while( _i_ ) \ + { /* restore heap property in parents */ \ + size_t _parent_ = (_i_-1)/2; \ + if( __topcmp(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)) < 0 ) \ + break; /* done */ \ + swap(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)); \ + _i_ = _parent_; \ + } \ + }while(0) + + + +/// Removes the top value of the heap. (using the '=' operator) +/// Assumes the heap is not empty. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __topcmp Comparator +#define BHEAP_POP(__heap,__topcmp) BHEAP_POPINDEX(__heap,0,__topcmp) + + + +/// Removes the target value of the heap. (using the '=' operator) +/// Assumes the index exists. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __idx Index +/// @param __topcmp Comparator +#define BHEAP_POPINDEX(__heap,__idx,__topcmp) \ + do{ \ + size_t _i_ = __idx; \ + VECTOR_INDEX(__heap,__idx) = VECTOR_POP(__heap); /* put last at index */ \ + while( _i_ ) \ + { /* restore heap property in parents */ \ + size_t _parent_ = (_i_-1)/2; \ + if( __topcmp(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)) < 0 ) \ + break; /* done */ \ + swap(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)); \ + _i_ = _parent_; \ + } \ + while( _i_ < VECTOR_LENGTH(__heap) ) \ + { /* restore heap property in childs */ \ + size_t _lchild_ = _i_*2 + 1; \ + size_t _rchild_ = _i_*2 + 2; \ + if( (_lchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_lchild_)) <= 0) && \ + (_rchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_rchild_)) <= 0) ) \ + break; /* done */ \ + else if( _rchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_lchild_),VECTOR_INDEX(__heap,_rchild_)) <= 0 ) \ + { /* left child */ \ + swap(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_lchild_)); \ + _i_ = _lchild_; \ + } \ + else \ + { /* right child */ \ + swap(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_rchild_)); \ + _i_ = _rchild_; \ + } \ + } \ + }while(0) + + + +/// Clears the binary heap, freeing allocated data. +/// +/// @param __heap Binary heap +#define BHEAP_CLEAR(__heap) VECTOR_CLEAR(__heap) + + + +/// Generic comparator for a min-heap. (minimum value at top) +/// Returns -1 if v1 is smaller, 1 if v2 is smaller, 0 if equal. +/// +/// @param v1 First value +/// @param v2 Second value +/// @return negative if v1 is top, positive if v2 is top, 0 if equal +#define BHEAP_MINTOPCMP(v1,v2) ( v1 == v2 ? 0 : v1 < v2 ? -1 : 1 ) + + + +/// Generic comparator for a max-heap. (maximum value at top) +/// Returns -1 if v1 is bigger, 1 if v2 is bigger, 0 if equal. +/// +/// @param v1 First value +/// @param v2 Second value +/// @return negative if v1 is top, positive if v2 is top, 0 if equal +#define BHEAP_MAXTOPCMP(v1,v2) ( v1 == v2 ? 0 : v1 > v2 ? -1 : 1 ) + + + +#endif /* _DB_H_ */ diff --git a/src/common/des.c b/src/common/des.c new file mode 100644 index 000000000..917fc33e0 --- /dev/null +++ b/src/common/des.c @@ -0,0 +1,218 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#include "../common/cbasetypes.h" +#include "../common/des.h" + + +/// DES (Data Encryption Standard) algorithm, modified version. +/// @see http://www.eathena.ws/board/index.php?autocom=bugtracker&showbug=5099. +/// @see http://en.wikipedia.org/wiki/Data_Encryption_Standard +/// @see http://en.wikipedia.org/wiki/DES_supplementary_material + + +/// Bitmask for accessing individual bits of a byte. +static const uint8_t mask[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + + +/// Initial permutation (IP). +static void IP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t ip_table[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(ip_table); ++i ) + { + uint8_t j = ip_table[i] - 1; + if( src->b[(j >> 3) & 7] & mask[j & 7] ) + tmp .b[(i >> 3) & 7] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Final permutation (IP^-1). +static void FP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t fp_table[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(fp_table); ++i ) + { + uint8_t j = fp_table[i] - 1; + if( src->b[(j >> 3) & 7] & mask[j & 7] ) + tmp .b[(i >> 3) & 7] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Expansion (E). +/// Expands upper four 8-bits (32b) into eight 6-bits (48b). +static void E(BIT64* src) +{ + BIT64 tmp = {{0}}; + +if( false ) +{// original + static const uint8_t expand_table[48] = { + 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(expand_table); ++i ) + { + uint8_t j = expand_table[i] - 1; + if( src->b[j / 8 + 4] & mask[j % 8] ) + tmp .b[i / 6 + 0] |= mask[i % 6]; + } +} +else +{// optimized + tmp.b[0] = ((src->b[7]<<5) | (src->b[4]>>3)) & 0x3f; // ..0 vutsr + tmp.b[1] = ((src->b[4]<<1) | (src->b[5]>>7)) & 0x3f; // ..srqpo n + tmp.b[2] = ((src->b[4]<<5) | (src->b[5]>>3)) & 0x3f; // ..o nmlkj + tmp.b[3] = ((src->b[5]<<1) | (src->b[6]>>7)) & 0x3f; // ..kjihg f + tmp.b[4] = ((src->b[5]<<5) | (src->b[6]>>3)) & 0x3f; // ..g fedcb + tmp.b[5] = ((src->b[6]<<1) | (src->b[7]>>7)) & 0x3f; // ..cba98 7 + tmp.b[6] = ((src->b[6]<<5) | (src->b[7]>>3)) & 0x3f; // ..8 76543 + tmp.b[7] = ((src->b[7]<<1) | (src->b[4]>>7)) & 0x3f; // ..43210 v +} + + *src = tmp; +} + + +/// Transposition (P-BOX). +static void TP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t tp_table[32] = { + 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(tp_table); ++i ) + { + uint8_t j = tp_table[i] - 1; + if( src->b[(j >> 3) + 0] & mask[j & 7] ) + tmp .b[(i >> 3) + 4] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Substitution boxes (S-boxes). +/// NOTE: This implementation was optimized to process two nibbles in one step (twice as fast). +static void SBOX(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t s_table[4][64] = { + { + 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e, + 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85, + 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72, + 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9, + },{ + 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3, + 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19, + 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78, + 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce, + },{ + 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15, + 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68, + 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda, + 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d, + },{ + 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4, + 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62, + 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d, + 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb, + } + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(s_table); ++i ) + { + tmp.b[i] = (s_table[i][src->b[i*2+0]] & 0xf0) + | (s_table[i][src->b[i*2+1]] & 0x0f); + } + + *src = tmp; +} + + +/// DES round function. +/// XORs src[0..3] with TP(SBOX(E(src[4..7]))). +static void RoundFunction(BIT64* src) +{ + BIT64 tmp = *src; + E(&tmp); + SBOX(&tmp); + TP(&tmp); + + src->b[0] ^= tmp.b[4]; + src->b[1] ^= tmp.b[5]; + src->b[2] ^= tmp.b[6]; + src->b[3] ^= tmp.b[7]; +} + + +void des_decrypt_block(BIT64* block) +{ + IP(block); + RoundFunction(block); + FP(block); +} + + +void des_decrypt(unsigned char* data, size_t size) +{ + BIT64* p = (BIT64*)data; + size_t i; + + for( i = 0; i*8 < size; i += 8 ) + des_decrypt_block(p); +} diff --git a/src/common/des.h b/src/common/des.h new file mode 100644 index 000000000..e42136436 --- /dev/null +++ b/src/common/des.h @@ -0,0 +1,15 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _DES_H_ +#define _DES_H_ + + +/// One 64-bit block. +typedef struct BIT64 { uint8_t b[8]; } BIT64; + + +void des_decrypt_block(BIT64* block); +void des_decrypt(unsigned char* data, size_t size); + + +#endif // _DES_H_ diff --git a/src/common/ers.c b/src/common/ers.c new file mode 100644 index 000000000..b94b0888d --- /dev/null +++ b/src/common/ers.c @@ -0,0 +1,292 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * <H1>Entry Reusage System</H1> * + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + * <H2>Advantages:</H2> * + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + * <H2>Disavantages:</H2> * + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * 1.0 - ERS Rework * + * * + * @version 1.0 - ERS Rework * + * @author GreenBox @ rAthena Project * + * @encoding US-ASCII * + * @see common#ers.h * +\*****************************************************************************/ +#include <stdlib.h> + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree +#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL +#include "ers.h" + +#ifndef DISABLE_ERS + +#define ERS_ROOT_SIZE 256 +#define ERS_BLOCK_ENTRIES 4096 + +struct ers_list +{ + struct ers_list *Next; +}; + +typedef struct ers_cache +{ + // Allocated object size, including ers_list size + unsigned int ObjectSize; + + // Number of ers_instances referencing this + int ReferenceCount; + + // Reuse linked list + struct ers_list *ReuseList; + + // Memory blocks array + unsigned char **Blocks; + + // Max number of blocks + unsigned int Max; + + // Free objects count + unsigned int Free; + + // Used objects count + unsigned int Used; + + // Linked list + struct ers_cache *Next, *Prev; +} ers_cache_t; + +typedef struct +{ + // Interface to ERS + struct eri VTable; + + // Name, used for debbuging purpouses + char *Name; + + // Misc options + enum ERSOptions Options; + + // Our cache + ers_cache_t *Cache; + + // Count of objects in use, used for detecting memory leaks + unsigned int Count; +} ers_instance_t; + + +// Array containing a pointer for all ers_cache structures +static ers_cache_t *CacheList; + +static ers_cache_t *ers_find_cache(unsigned int size) +{ + ers_cache_t *cache; + + for (cache = CacheList; cache; cache = cache->Next) + if (cache->ObjectSize == size) + return cache; + + CREATE(cache, ers_cache_t, 1); + cache->ObjectSize = size; + cache->ReferenceCount = 0; + cache->ReuseList = NULL; + cache->Blocks = NULL; + cache->Free = 0; + cache->Used = 0; + cache->Max = 0; + + if (CacheList == NULL) + { + CacheList = cache; + } + else + { + cache->Next = CacheList; + cache->Next->Prev = cache; + CacheList = cache; + CacheList->Prev = NULL; + } + + return cache; +} + +static void ers_free_cache(ers_cache_t *cache, bool remove) +{ + unsigned int i; + + for (i = 0; i < cache->Used; i++) + aFree(cache->Blocks[i]); + + if (cache->Next) + cache->Next->Prev = cache->Prev; + + if (cache->Prev) + cache->Prev->Next = cache->Next; + else + CacheList = cache->Next; + + aFree(cache->Blocks); + aFree(cache); +} + +static void *ers_obj_alloc_entry(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + void *ret; + + if (instance == NULL) + { + ShowError("ers_obj_alloc_entry: NULL object, aborting entry freeing.\n"); + return NULL; + } + + if (instance->Cache->ReuseList != NULL) + { + ret = (void *)((unsigned char *)instance->Cache->ReuseList + sizeof(struct ers_list)); + instance->Cache->ReuseList = instance->Cache->ReuseList->Next; + } + else if (instance->Cache->Free > 0) + { + instance->Cache->Free--; + ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)]; + } + else + { + if (instance->Cache->Used == instance->Cache->Max) + { + instance->Cache->Max = (instance->Cache->Max * 4) + 3; + RECREATE(instance->Cache->Blocks, unsigned char *, instance->Cache->Max); + } + + CREATE(instance->Cache->Blocks[instance->Cache->Used], unsigned char, instance->Cache->ObjectSize * ERS_BLOCK_ENTRIES); + instance->Cache->Used++; + + instance->Cache->Free = ERS_BLOCK_ENTRIES -1; + ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)]; + } + + instance->Count++; + + return ret; +} + +static void ers_obj_free_entry(ERS self, void *entry) +{ + ers_instance_t *instance = (ers_instance_t *)self; + struct ers_list *reuse = (struct ers_list *)((unsigned char *)entry - sizeof(struct ers_list)); + + if (instance == NULL) + { + ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n"); + return; + } + else if (entry == NULL) + { + ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n"); + return; + } + + reuse->Next = instance->Cache->ReuseList; + instance->Cache->ReuseList = reuse; + instance->Count--; +} + +static size_t ers_obj_entry_size(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + + if (instance == NULL) + { + ShowError("ers_obj_entry_size: NULL object, aborting entry freeing.\n"); + return 0; + } + + return instance->Cache->ObjectSize; +} + +static void ers_obj_destroy(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + + if (instance == NULL) + { + ShowError("ers_obj_destroy: NULL object, aborting entry freeing.\n"); + return; + } + + if (instance->Count > 0) + if (!(instance->Options & ERS_OPT_CLEAR)) + ShowWarning("Memory leak detected at ERS '%s', %d objects not freed.\n", instance->Name, instance->Count); + + if (--instance->Cache->ReferenceCount <= 0) + ers_free_cache(instance->Cache, true); + + aFree(instance); +} + +ERS ers_new(uint32 size, char *name, enum ERSOptions options) +{ + ers_instance_t *instance; + CREATE(instance, ers_instance_t, 1); + + size += sizeof(struct ers_list); + if (size % ERS_ALIGNED) + size += ERS_ALIGNED - size % ERS_ALIGNED; + + instance->VTable.alloc = ers_obj_alloc_entry; + instance->VTable.free = ers_obj_free_entry; + instance->VTable.entry_size = ers_obj_entry_size; + instance->VTable.destroy = ers_obj_destroy; + + instance->Name = name; + instance->Options = options; + + instance->Cache = ers_find_cache(size); + instance->Cache->ReferenceCount++; + + instance->Count = 0; + + return &instance->VTable; +} + +void ers_report(void) +{ + // FIXME: Someone use this? Is it really needed? +} + +void ers_force_destroy_all(void) +{ + ers_cache_t *cache; + + for (cache = CacheList; cache; cache = cache->Next) + ers_free_cache(cache, false); +} + +#endif diff --git a/src/common/ers.h b/src/common/ers.h new file mode 100644 index 000000000..dc66af5ef --- /dev/null +++ b/src/common/ers.h @@ -0,0 +1,172 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * <H1>Entry Reusage System</H1> * + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + * <H2>Advantages:</H2> * + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + * <H2>Disavantages:</H2> * + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * * + * @version 0.1 - Initial version * + * @author Flavio @ Amazon Project * + * @encoding US-ASCII * +\*****************************************************************************/ +#ifndef _ERS_H_ +#define _ERS_H_ + +#include "../common/cbasetypes.h" + +/*****************************************************************************\ + * (1) All public parts of the Entry Reusage System. * + * DISABLE_ERS - Define to disable this system. * + * ERS_ALIGNED - Alignment of the entries in the blocks. * + * ERS - Entry manager. * + * ers_new - Allocate an instance of an entry manager. * + * ers_report - Print a report about the current state. * + * ers_force_destroy_all - Force the destruction of all the managers. * +\*****************************************************************************/ + +/** + * Define this to disable the Entry Reusage System. + * All code except the typedef of ERInterface will be disabled. + * To allow a smooth transition, + */ +//#define DISABLE_ERS + +/** + * Entries are aligned to ERS_ALIGNED bytes in the blocks of entries. + * By default it aligns to one byte, using the "natural order" of the entries. + * This should NEVER be set to zero or less. + * If greater than one, some memory can be wasted. This should never be needed + * but is here just in case some aligment issues arise. + */ +#ifndef ERS_ALIGNED +# define ERS_ALIGNED 1 +#endif /* not ERS_ALIGN_ENTRY */ + +enum ERSOptions { + ERS_OPT_NONE = 0, + ERS_OPT_CLEAR = 1,/* silently clears any entries left in the manager upon destruction */ +}; + +/** + * Public interface of the entry manager. + * @param alloc Allocate an entry from this manager + * @param free Free an entry allocated from this manager + * @param entry_size Return the size of the entries of this manager + * @param destroy Destroy this instance of the manager + */ +typedef struct eri { + + /** + * Allocate an entry from this entry manager. + * If there are reusable entries available, it reuses one instead. + * @param self Interface of the entry manager + * @return An entry + */ + void *(*alloc)(struct eri *self); + + /** + * Free an entry allocated from this manager. + * WARNING: Does not check if the entry was allocated by this manager. + * Freeing such an entry can lead to unexpected behaviour. + * @param self Interface of the entry manager + * @param entry Entry to be freed + */ + void (*free)(struct eri *self, void *entry); + + /** + * Return the size of the entries allocated from this manager. + * @param self Interface of the entry manager + * @return Size of the entries of this manager in bytes + */ + size_t (*entry_size)(struct eri *self); + + /** + * Destroy this instance of the manager. + * The manager is actually only destroyed when all the instances are destroyed. + * When destroying the manager a warning is shown if the manager has + * missing/extra entries. + * @param self Interface of the entry manager + */ + void (*destroy)(struct eri *self); + +} *ERS; + +#ifdef DISABLE_ERS +// Use memory manager to allocate/free and disable other interface functions +# define ers_alloc(obj,type) (type *)aMalloc(sizeof(type)) +# define ers_free(obj,entry) aFree(entry) +# define ers_entry_size(obj) (size_t)0 +# define ers_destroy(obj) +// Disable the public functions +# define ers_new(size,name,options) NULL +# define ers_report() +# define ers_force_destroy_all() +#else /* not DISABLE_ERS */ +// These defines should be used to allow the code to keep working whenever +// the system is disabled +# define ers_alloc(obj,type) (type *)(obj)->alloc(obj) +# define ers_free(obj,entry) (obj)->free((obj),(entry)) +# define ers_entry_size(obj) (obj)->entry_size(obj) +# define ers_destroy(obj) (obj)->destroy(obj) + +/** + * Get a new instance of the manager that handles the specified entry size. + * Size has to greater than 0. + * If the specified size is smaller than a pointer, the size of a pointer is + * used instead. + * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of + * ERS_ALIGNED that is greater or equal to size is what's actually used. + * @param The requested size of the entry in bytes + * @return Interface of the object + */ +ERS ers_new(uint32 size, char *name, enum ERSOptions options); + +/** + * Print a report about the current state of the Entry Reusage System. + * Shows information about the global system and each entry manager. + * The number of entries are checked and a warning is shown if extra reusable + * entries are found. + * The extra entries are included in the count of reusable entries. + */ +void ers_report(void); + +/** + * Forcibly destroy all the entry managers, checking for nothing. + * The system is left as if no instances or entries had ever been allocated. + * All previous entries and instances of the managers become invalid. + * The use of this is NOT recommended. + * It should only be used in extreme situations to make shure all the memory + * allocated by this system is released. + */ +void ers_force_destroy_all(void); +#endif /* DISABLE_ERS / not DISABLE_ERS */ + +#endif /* _ERS_H_ */ diff --git a/src/common/evdp.h b/src/common/evdp.h new file mode 100644 index 000000000..bc3454686 --- /dev/null +++ b/src/common/evdp.h @@ -0,0 +1,168 @@ +#ifndef _rA_EVDP_H_ +#define _rA_EVDP_H_ + +#include "../common/cbasetypes.h" + +typedef struct EVDP_DATA EVDP_DATA; + + +//#idef EVDP_EPOLL +#include <sys/epoll.h> +struct EVDP_DATA{ + struct epoll_event ev_data; + bool ev_added; +}; +//#endif + + +enum EVDP_EVENTFLAGS{ + EVDP_EVENT_IN = 1, // Incomming data + EVDP_EVENT_OUT = 2, // Connection accepts writing. + EVDP_EVENT_HUP = 4 // Connection Closed. +}; + +typedef struct EVDP_EVENT{ + int32 events; // due to performance reasons, this should be the first member. + int32 fd; // Connection Identifier +} EVDP_EVENT; + + + +/** + * Network Event Dispatcher Initialization / Finalization routines + */ +void evdp_init(); +void evdp_final(); + + +/** + * Will Wait for events. + * + * @param *out_ev pointer to array in size at least of max_events. + * @param max_events max no of events to report with this call (coalesc) + * @param timeout_ticks max time to wait in ticks (milliseconds) + * + * @Note: + * The function will block until an event has occured on one of the monitored connections + * or the timeout of timeout_ticks has passed by. + * Upon successfull call (changed connections) this function will write the connection + * Identifier & event to the out_fds array. + * + * @return 0 -> Timeout, > 0 no of changed connections. + */ +int32 evdp_wait(EVDP_EVENT *out_fds, int32 max_events, int32 timeout_ticks); + + +/** + * Applys the given mask on the given connection. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * @param mask new event mask we're monitoring for. + */ +//void evdp_apply(int32 fd, EVDP_DATA *ep, int32 mask); + + +/** + * Adds a connection (listner) to the event notification system. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * + * @note: + * Listener type sockets are edge triggered, (see epoll manual for more information) + * - This basicaly means that youll receive one event, adn you have to accept until accept returns an error (nothing to accept) + * + * MONITORS by default: IN + * + * @return success indicator. + */ +bool evdp_addlistener(int32 fd, EVDP_DATA *ep); + +/** + * Adds a connection (client connectioN) to the event notification system + * + * @param fd connection identifier + * @param *ep event data pointr for the connection + * + * @note: + * + * MONITORS by default: IN, HUP + * + * @return success indicator. + */ +bool evdp_addclient(int32 fd, EVDP_DATA *ep); + +/** + * Adds a connection (pending / outgoing connection!) to the event notification system. + * + * @param fd connection identifier + * @param *ep event data pointer for the conneciton. + * + * @note: + * Outgoing connection type sockets are getting monitored for connection established + * successfull + * - if the connection has been established - we're generitng a writable notification .. (send) + * this is typical for BSD / posix conform network stacks. + * - Additinionally its edge triggered. + * + * @see evdp_outgoingconnection_established + * + * + * @return success indicator + */ +bool evdp_addconnecting(int32 fd, EVDP_DATA *ep); + +/** + * Adds an outgoing connection to the normal event notification system after it has been successfully established. + * + * @param fd connection identifier + * @param *ep event data pointer for the conneciton. + + * @note + * after this call, its handled like a normal "client" connection (incomming) + * + * @rturn success indicator + */ +bool evdp_outgoingconnection_established(int32 fd, EVDP_DATA *ep); + +/** + * Marks a connection to be monitored for writable. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * + * @note: + * the connection must be already added (as client or listener) + * + * + * @return sucess indicator + */ +bool evdp_writable_add(int32 fd, EVDP_DATA *ep); + +/** + * Removes the connection from writable notification monitoring + * + * @param fd connection identifier + * @param *ep event data pointr for the connection + * + */ +void evdp_writable_remove(int32 fd, EVDP_DATA *ep); + +/** + * Removes an connectio from the event notification system. + * + * @param fd connection iditentfir + * @param *ep event data pointer for th connection + * + * + * @note: + * this will also clear the given EVENT_DATA block + * so the connection slot is in an "initial" blank status / ready to get reused. + * + */ +void evdp_remove(int32 fd, EVDP_DATA *ep); + + + +#endif diff --git a/src/common/evdp_epoll.c b/src/common/evdp_epoll.c new file mode 100644 index 000000000..0357dfc66 --- /dev/null +++ b/src/common/evdp_epoll.c @@ -0,0 +1,232 @@ +// +// Event Dispatcher Abstraction for EPOLL +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/epoll.h> +#include <sys/fcntl.h> +#include <sys/socket.h> + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/evdp.h" + + +#define EPOLL_MAX_PER_CYCLE 10 // Max Events to coalesc. per cycle. + + +static int epoll_fd = -1; + + +void evdp_init(){ + + epoll_fd = epoll_create( EPOLL_MAX_PER_CYCLE ); + if(epoll_fd == -1){ + ShowFatalError("evdp [EPOLL]: Cannot create event dispatcher (errno: %u / %s)\n", errno, strerror(errno) ); + exit(1); + } + +}//end: evdp_init() + + +void evdp_final(){ + + if(epoll_fd != -1){ + close(epoll_fd); + epoll_fd = -1; + } + +}//end: evdp_final() + + +int32 evdp_wait(EVDP_EVENT *out_fds, int32 max_events, int32 timeout_ticks){ + struct epoll_event l_events[EPOLL_MAX_PER_CYCLE]; + register struct epoll_event *ev; + register int nfds, n; + + if(max_events > EPOLL_MAX_PER_CYCLE) + max_events = EPOLL_MAX_PER_CYCLE; + + nfds = epoll_wait( epoll_fd, l_events, max_events, timeout_ticks); + if(nfds == -1){ + // @TODO: check if core is in shutdown mode. if - ignroe error. + + ShowFatalError("evdp [EPOLL]: epoll_wait returned bad / unexpected status (errno: %u / %s)\n", errno, strerror(errno)); + exit(1); //.. + } + + // Loop thru all events and copy it to the local ra evdp_event.. struct. + for(n = 0; n < nfds; n++){ + ev = &l_events[n]; + + out_fds->fd = ev->data.fd; + out_fds->events = 0; // clear + + if(ev->events & EPOLLHUP) + out_fds->events |= EVDP_EVENT_HUP; + + if(ev->events & EPOLLIN) + out_fds->events |= EVDP_EVENT_IN; + + if(ev->events & EPOLLOUT) + out_fds->events |= EVDP_EVENT_OUT; + + out_fds++; + } + + return nfds; // 0 on timeout or > 0 .. +}//end: evdp_wait() + + +void evdp_remove(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added == true){ + + if( epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_remove - epoll_ctl (EPOLL_CTL_DEL) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + } + + ep->ev_data.events = 0; // clear struct. + ep->ev_data.data.fd = -1; // .. clear struct .. + + ep->ev_added = false; // not added! + } + + +}//end: evdp_remove() + + +bool evdp_addlistener(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLET|EPOLLIN; + ep->ev_data.data.fd = fd; + + // No check here for 'added ?' + // listeners cannot be added twice. + // + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0 ){ + ShowError("evdp [EPOLL]: evdp_addlistener - epoll_ctl (EPOLL_CTL_ADD) faield! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + return false; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addlistener() + + +bool evdp_addclient(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLIN | EPOLLHUP; + ep->ev_data.data.fd = fd; + + // No check for "added?" here, + // this function only gets called upon accpept. + // + + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_addclient - epoll_ctl (EPOLL_CTL_ADD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + return false; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addclient() + + +bool evdp_addconnecting(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLET | EPOLLOUT | EPOLLHUP; + ep->ev_data.data.fd = fd; + + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_addconnecting - epoll_ctl (EPOLL_CTL_ADD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addconnecting() + + +bool evdp_outgoingconnection_established(int32 fd, EVDP_DATA *ep){ + int32 saved_mask; + + if(ep->ev_added != true){ + // ! + ShowError("evdp [EPOLL]: evdp_outgoingconnection_established fd #%u is not added to event dispatcher! invalid call.\n", fd); + return false; + } + + saved_mask = ep->ev_data.events; + + ep->ev_data.events = EPOLLIN | EPOLLHUP; + + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0){ + ep->ev_data.events = saved_mask; // restore old mask. + ShowError("evdp [EPOLL]: evdp_outgoingconnection_established - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + return false; + } + + return true; +}//end: evdp_outgoingconnection_established() + + +bool evdp_writable_add(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added != true){ + ShowError("evdp [EPOLL]: evdp_writable_add - tried to add not added fd #%u\n",fd); + return false; + } + + if(! (ep->ev_data.events & EPOLLOUT) ){ // + + ep->ev_data.events |= EPOLLOUT; + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0 ){ + ShowError("evdp [EPOLL]: evdp_writable_add - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno: %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events &= ~EPOLLOUT; // remove from local flagmask due to failed syscall. + return false; + } + } + + return true; +}//end: evdp_writable_add() + + +void evdp_writable_remove(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added != true){ + ShowError("evdp [EPOLL]: evdp_writable_remove - tried to remove not added fd #%u\n", fd); + return; + } + + if( ep->ev_data.events & EPOLLOUT ){ + + ep->ev_data.events &= ~EPOLLOUT; + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_writable_remove - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events |= EPOLLOUT; // add back to local flagmask because of failed syscall. + return; + } + } + + return; +}//end: evdp_writable_remove() diff --git a/src/common/grfio.c b/src/common/grfio.c new file mode 100644 index 000000000..8f430cfb9 --- /dev/null +++ b/src/common/grfio.c @@ -0,0 +1,818 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/des.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "grfio.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <zlib.h> + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct _FILELIST { + int srclen; // compressed size + int srclen_aligned; + int declen; // original size + int srcpos; // position of entry in grf + int next; // index of next filelist entry with same hash (-1: end of entry chain) + char type; + char fn[128-4*5]; // file name + char* fnd; // if the file was cloned, contains name of original file + char gentry; // read grf file select +} FILELIST; + +#define FILELIST_TYPE_FILE 0x01 // entry is a file +#define FILELIST_TYPE_ENCRYPT_HEADER 0x04 // encryption mode 1 (header DES only) +#define FILELIST_TYPE_ENCRYPT_MIXED 0x02 // encryption mode 0 (header DES + periodic DES/shuffle) + +//gentry ... > 0 : data read from a grf file (gentry_table[gentry-1]) +//gentry ... 0 : data read from a local file (data directory) +//gentry ... < 0 : entry "-(gentry)" is marked for a local file check +// - if local file exists, gentry will be set to 0 (thus using the local file) +// - if local file doesn't exist, sign is inverted (thus using the original file inside a grf) +// (NOTE: this case is only used once (during startup) and only if GRFIO_LOCAL is enabled) +// (NOTE: probably meant to be used to override grf contents by files in the data directory) +//#define GRFIO_LOCAL + + +// stores info about every loaded file +FILELIST* filelist = NULL; +int filelist_entrys = 0; +int filelist_maxentry = 0; + +// stores grf file names +char** gentry_table = NULL; +int gentry_entrys = 0; +int gentry_maxentry = 0; + +// the path to the data directory +char data_dir[1024] = ""; + + +// little endian char array to uint conversion +static unsigned int getlong(unsigned char* p) +{ + return (p[0] << 0 | p[1] << 8 | p[2] << 16 | p[3] << 24); +} + + +static void NibbleSwap(unsigned char* src, int len) +{ + while( len > 0 ) + { + *src = (*src >> 4) | (*src << 4); + ++src; + --len; + } +} + + +/// Substitutes some specific values for others, leaves rest intact. Obfuscation. +/// NOTE: Operation is symmetric (calling it twice gives back the original input). +static uint8_t grf_substitution(uint8_t in) +{ + uint8_t out; + + switch( in ) + { + case 0x00: out = 0x2B; break; + case 0x2B: out = 0x00; break; + case 0x6C: out = 0x80; break; + case 0x01: out = 0x68; break; + case 0x68: out = 0x01; break; + case 0x48: out = 0x77; break; + case 0x60: out = 0xFF; break; + case 0x77: out = 0x48; break; + case 0xB9: out = 0xC0; break; + case 0xC0: out = 0xB9; break; + case 0xFE: out = 0xEB; break; + case 0xEB: out = 0xFE; break; + case 0x80: out = 0x6C; break; + case 0xFF: out = 0x60; break; + default: out = in; break; + } + + return out; +} + +/* this is not used anywhere, is it ok to delete? */ +//static void grf_shuffle_enc(BIT64* src) { +// BIT64 out; +// +// out.b[0] = src->b[3]; +// out.b[1] = src->b[4]; +// out.b[2] = src->b[5]; +// out.b[3] = src->b[0]; +// out.b[4] = src->b[1]; +// out.b[5] = src->b[6]; +// out.b[6] = src->b[2]; +// out.b[7] = grf_substitution(src->b[7]); +// +// *src = out; +//} + + +static void grf_shuffle_dec(BIT64* src) +{ + BIT64 out; + + out.b[0] = src->b[3]; + out.b[1] = src->b[4]; + out.b[2] = src->b[6]; + out.b[3] = src->b[0]; + out.b[4] = src->b[1]; + out.b[5] = src->b[2]; + out.b[6] = src->b[5]; + out.b[7] = grf_substitution(src->b[7]); + + *src = out; +} + + +static void grf_decode_header(unsigned char* buf, size_t len) +{ + BIT64* p = (BIT64*)buf; + size_t nblocks = len / sizeof(BIT64); + size_t i; + + // first 20 blocks are all des-encrypted + for( i = 0; i < 20 && i < nblocks; ++i ) + des_decrypt_block(&p[i]); + + // the rest is plaintext, done. +} + + +static void grf_decode_full(unsigned char* buf, size_t len, int cycle) +{ + BIT64* p = (BIT64*)buf; + size_t nblocks = len / sizeof(BIT64); + int dcycle, scycle; + size_t i, j; + + // first 20 blocks are all des-encrypted + for( i = 0; i < 20 && i < nblocks; ++i ) + des_decrypt_block(&p[i]); + + // after that only one of every 'dcycle' blocks is des-encrypted + dcycle = cycle; + + // and one of every 'scycle' plaintext blocks is shuffled (starting from the 0th but skipping the 0th) + scycle = 7; + + // so decrypt/de-shuffle periodically + j = -1; // 0, adjusted to fit the ++j step + for( i = 20; i < nblocks; ++i ) + { + if( i % dcycle == 0 ) + {// decrypt block + des_decrypt_block(&p[i]); + continue; + } + + ++j; + if( j % scycle == 0 && j != 0 ) + {// de-shuffle block + grf_shuffle_dec(&p[i]); + continue; + } + + // plaintext, do nothing. + } +} + + +/// Decodes grf data. +/// @param buf data to decode (in-place) +/// @param len length of the data +/// @param entry_type flags associated with the data +/// @param entry_len true (unaligned) length of the data +static void grf_decode(unsigned char* buf, size_t len, char entry_type, int entry_len) +{ + if( entry_type & FILELIST_TYPE_ENCRYPT_MIXED ) + {// fully encrypted + int digits; + int cycle; + int i; + + // compute number of digits of the entry length + digits = 1; + for( i = 10; i <= entry_len; i *= 10 ) + ++digits; + + // choose size of gap between two encrypted blocks + // digits: 0 1 2 3 4 5 6 7 8 9 ... + // cycle: 1 1 1 4 5 14 15 22 23 24 ... + cycle = ( digits < 3 ) ? 1 + : ( digits < 5 ) ? digits + 1 + : ( digits < 7 ) ? digits + 9 + : digits + 15; + + grf_decode_full(buf, len, cycle); + } + else + if( entry_type & FILELIST_TYPE_ENCRYPT_HEADER ) + {// header encrypted + grf_decode_header(buf, len); + } + else + {// plaintext + ; + } +} + + +/****************************************************** + *** Zlib Subroutines *** + ******************************************************/ + +/// zlib crc32 +unsigned long grfio_crc32(const unsigned char* buf, unsigned int len) +{ + return crc32(crc32(0L, Z_NULL, 0), buf, len); +} + + +/// zlib uncompress +int decode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen) +{ + return uncompress((Bytef*)dest, destLen, (const Bytef*)source, sourceLen); +} + + +/// zlib compress +int encode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen) +{ + return compress((Bytef*)dest, destLen, (const Bytef*)source, sourceLen); +} + + +/*********************************************************** + *** File List Subroutines *** + ***********************************************************/ +// file list hash table +int filelist_hash[256]; + +// initializes the table that holds the first elements of all hash chains +static void hashinit(void) +{ + int i; + for (i = 0; i < 256; i++) + filelist_hash[i] = -1; +} + +// hashes a filename string into a number from {0..255} +static int filehash(const char* fname) +{ + unsigned int hash = 0; + while(*fname) { + hash = (hash<<1) + (hash>>7)*9 + TOLOWER(*fname); + fname++; + } + return hash & 255; +} + +// finds a FILELIST entry with the specified file name +static FILELIST* filelist_find(const char* fname) +{ + int hash, index; + + if (!filelist) + return NULL; + + hash = filelist_hash[filehash(fname)]; + for (index = hash; index != -1; index = filelist[index].next) + if(!strcmpi(filelist[index].fn, fname)) + break; + + return (index >= 0) ? &filelist[index] : NULL; +} + +// returns the original file name +char* grfio_find_file(const char* fname) +{ + FILELIST *filelist = filelist_find(fname); + if (!filelist) return NULL; + return (!filelist->fnd ? filelist->fn : filelist->fnd); +} + +// adds a FILELIST entry into the list of loaded files +static FILELIST* filelist_add(FILELIST* entry) +{ + int hash; + + #define FILELIST_ADDS 1024 // number increment of file lists ` + + if (filelist_entrys >= filelist_maxentry) { + filelist = (FILELIST *)aRealloc(filelist, (filelist_maxentry + FILELIST_ADDS) * sizeof(FILELIST)); + memset(filelist + filelist_maxentry, '\0', FILELIST_ADDS * sizeof(FILELIST)); + filelist_maxentry += FILELIST_ADDS; + } + + memcpy (&filelist[filelist_entrys], entry, sizeof(FILELIST)); + + hash = filehash(entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys - 1]; +} + +// adds a new FILELIST entry or overwrites an existing one +static FILELIST* filelist_modify(FILELIST* entry) +{ + FILELIST* fentry = filelist_find(entry->fn); + if (fentry != NULL) { + int tmp = fentry->next; + memcpy(fentry, entry, sizeof(FILELIST)); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +// shrinks the file list array if too long +static void filelist_compact(void) +{ + if (filelist == NULL) + return; + + if (filelist_entrys < filelist_maxentry) { + filelist = (FILELIST *)aRealloc(filelist, filelist_entrys * sizeof(FILELIST)); + filelist_maxentry = filelist_entrys; + } +} + + +/*********************************************************** + *** Grfio Subroutines *** + ***********************************************************/ + + +/// Combines are resource path with the data folder location to create local resource path. +static void grfio_localpath_create(char* buffer, size_t size, const char* filename) +{ + unsigned int i; + size_t len; + + len = strlen(data_dir); + + if( data_dir[0] == '\0' || data_dir[len-1] == '/' || data_dir[len-1] == '\\' ) + { + safesnprintf(buffer, size, "%s%s", data_dir, filename); + } + else + { + safesnprintf(buffer, size, "%s/%s", data_dir, filename); + } + + // normalize path + for( i = 0; buffer[i] != '\0'; ++i ) + if( buffer[i] == '\\' ) + buffer[i] = '/'; +} + + +/// Reads a file into a newly allocated buffer (from grf or data directory). +void* grfio_reads(const char* fname, int* size) +{ + unsigned char* buf2 = NULL; + + FILELIST* entry = filelist_find(fname); + if( entry == NULL || entry->gentry <= 0 ) {// LocalFileCheck + char lfname[256]; + int declen; + FILE* in; + grfio_localpath_create(lfname, sizeof(lfname), ( entry && entry->fnd ) ? entry->fnd : fname); + + in = fopen(lfname, "rb"); + if( in != NULL ) { + fseek(in,0,SEEK_END); + declen = ftell(in); + fseek(in,0,SEEK_SET); + buf2 = (unsigned char *)aMalloc(declen+1); // +1 for resnametable zero-termination + if(fread(buf2, 1, declen, in) != declen) ShowError("An error occured in fread grfio_reads, fname=%s \n",fname); + fclose(in); + + if( size ) + *size = declen; + } else { + if (entry != NULL && entry->gentry < 0) { + entry->gentry = -entry->gentry; // local file checked + } else { + ShowError("grfio_reads: %s not found (local file: %s)\n", fname, lfname); + return NULL; + } + } + } + + if( entry != NULL && entry->gentry > 0 ) {// Archive[GRF] File Read + char* grfname = gentry_table[entry->gentry - 1]; + FILE* in = fopen(grfname, "rb"); + if( in != NULL ) { + int fsize = entry->srclen_aligned; + unsigned char *buf = (unsigned char *)aMalloc(fsize); + fseek(in, entry->srcpos, 0); + if(fread(buf, 1, fsize, in) != fsize) ShowError("An error occured in fread in grfio_reads, grfname=%s\n",grfname); + fclose(in); + + buf2 = (unsigned char *)aMalloc(entry->declen+1); // +1 for resnametable zero-termination + if( entry->type & FILELIST_TYPE_FILE ) + {// file + uLongf len; + grf_decode(buf, fsize, entry->type, entry->srclen); + len = entry->declen; + decode_zip(buf2, &len, buf, entry->srclen); + if (len != (uLong)entry->declen) { + ShowError("decode_zip size mismatch err: %d != %d\n", (int)len, entry->declen); + aFree(buf); + aFree(buf2); + return NULL; + } + } else {// directory? + memcpy(buf2, buf, entry->declen); + } + + if( size ) + *size = entry->declen; + + aFree(buf); + } else { + ShowError("grfio_reads: %s not found (GRF file: %s)\n", fname, grfname); + return NULL; + } + } + + return buf2; +} + + +/// Decodes encrypted filename from a version 01xx grf index. +static char* decode_filename(unsigned char* buf, int len) +{ + int lop; + for(lop=0;lop<len;lop+=8) { + NibbleSwap(&buf[lop],8); + des_decrypt(&buf[lop],8); + } + return (char*)buf; +} + + +/// Compares file extension against known large file types. +/// @return true if the file should undergo full mode 0 decryption, and true otherwise. +static bool isFullEncrypt(const char* fname) +{ + static const char extensions[4][5] = { ".gnd", ".gat", ".act", ".str" }; + size_t i; + + const char* ext = strrchr(fname, '.'); + if( ext != NULL ) + for( i = 0; i < ARRAYLENGTH(extensions); ++i ) + if( strcmpi(ext, extensions[i]) == 0 ) + return false; + + return true; +} + + +/// Loads all entries in the specified grf file into the filelist. +/// @param gentry index of the grf file name in the gentry_table +static int grfio_entryread(const char* grfname, int gentry) +{ + long grf_size,list_size; + unsigned char grf_header[0x2e]; + int entry,entrys,ofs,grf_version; + unsigned char *grf_filelist; + + FILE* fp = fopen(grfname, "rb"); + if( fp == NULL ) { + ShowWarning("GRF data file not found: '%s'\n",grfname); + return 1; // 1:not found error + } else + ShowInfo("GRF data file found: '%s'\n",grfname); + + fseek(fp,0,SEEK_END); + grf_size = ftell(fp); + fseek(fp,0,SEEK_SET); + + if(fread(grf_header,1,0x2e,fp) != 0x2e) { ShowError("Couldn't read all grf_header element of %s \n", grfname); } + if( strcmp((const char*)grf_header,"Master of Magic") != 0 || fseek(fp,getlong(grf_header+0x1e),SEEK_CUR) != 0 ) { + fclose(fp); + ShowError("GRF %s read error\n", grfname); + return 2; // 2:file format error + } + + grf_version = getlong(grf_header+0x2a) >> 8; + + if( grf_version == 0x01 ) {// ****** Grf version 01xx ****** + list_size = grf_size - ftell(fp); + grf_filelist = (unsigned char *) aMalloc(list_size); + if(fread(grf_filelist,1,list_size,fp) != list_size) { ShowError("Couldn't read all grf_filelist element of %s \n", grfname); } + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for( entry = 0, ofs = 0; entry < entrys; ++entry ) { + FILELIST aentry; + + int ofs2 = ofs+getlong(grf_filelist+ofs)+4; + unsigned char type = grf_filelist[ofs2+12]; + if( type & FILELIST_TYPE_FILE ) { + char* fname = decode_filename(grf_filelist+ofs+6, grf_filelist[ofs]-6); + int srclen = getlong(grf_filelist+ofs2+0) - getlong(grf_filelist+ofs2+8) - 715; + + if( strlen(fname) > sizeof(aentry.fn) - 1 ) { + ShowFatalError("GRF file name %s is too long\n", fname); + aFree(grf_filelist); + exit(EXIT_FAILURE); + } + + type |= ( isFullEncrypt(fname) ) ? FILELIST_TYPE_ENCRYPT_MIXED : FILELIST_TYPE_ENCRYPT_HEADER; + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4)-37579; + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.type = type; + safestrncpy(aentry.fn, fname, sizeof(aentry.fn)); + aentry.fnd = NULL; +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + + ofs = ofs2 + 17; + } + + aFree(grf_filelist); + } else if( grf_version == 0x02 ) {// ****** Grf version 02xx ****** + unsigned char eheader[8]; + unsigned char *rBuf; + uLongf rSize, eSize; + + if(fread(eheader,1,8,fp) != 8) ShowError("An error occured in fread while reading eheader buffer\n"); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if( (long)rSize > grf_size-ftell(fp) ) { + fclose(fp); + ShowError("Illegal data format: GRF compress entry size\n"); + return 4; + } + + rBuf = (unsigned char *)aMalloc(rSize); // Get a Read Size + grf_filelist = (unsigned char *)aMalloc(eSize); // Get a Extend Size + if(fread(rBuf,1,rSize,fp) != rSize) ShowError("An error occured in fread \n"); + fclose(fp); + decode_zip(grf_filelist, &eSize, rBuf, rSize); // Decode function + aFree(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for( entry = 0, ofs = 0; entry < entrys; ++entry ) { + FILELIST aentry; + + char* fname = (char*)(grf_filelist+ofs); + int ofs2 = ofs + (int)strlen(fname)+1; + int type = grf_filelist[ofs2+12]; + + if( strlen(fname) > sizeof(aentry.fn)-1 ) { + ShowFatalError("GRF file name %s is too long\n", fname); + aFree(grf_filelist); + exit(EXIT_FAILURE); + } + + if( type & FILELIST_TYPE_FILE ) {// file + aentry.srclen = getlong(grf_filelist+ofs2+0); + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.type = type; + safestrncpy(aentry.fn, fname, sizeof(aentry.fn)); + aentry.fnd = NULL; +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + + ofs = ofs2 + 17; + } + + aFree(grf_filelist); + } else {// ****** Grf Other version ****** + fclose(fp); + ShowError("GRF version %04x not supported\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_compact(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + + +static bool grfio_parse_restable_row(const char* row) +{ + char w1[256], w2[256]; + char src[256], dst[256]; + char local[256]; + FILELIST* entry; + + if( sscanf(row, "%[^#\r\n]#%[^#\r\n]#", w1, w2) != 2 ) + return false; + + if( strstr(w2, ".gat") == NULL && strstr(w2, ".rsw") == NULL ) + return false; // we only need the maps' GAT and RSW files + + sprintf(src, "data\\%s", w1); + sprintf(dst, "data\\%s", w2); + + entry = filelist_find(dst); + if( entry != NULL ) + {// alias for GRF resource + FILELIST fentry; + memcpy(&fentry, entry, sizeof(FILELIST)); + safestrncpy(fentry.fn, src, sizeof(fentry.fn)); + fentry.fnd = aStrdup(dst); + filelist_modify(&fentry); + return true; + } + + grfio_localpath_create(local, sizeof(local), dst); + if( exists(local) ) + {// alias for local resource + FILELIST fentry; + memset(&fentry, 0, sizeof(fentry)); + safestrncpy(fentry.fn, src, sizeof(fentry.fn)); + fentry.fnd = aStrdup(dst); + filelist_modify(&fentry); + return true; + } + + return false; +} + + +/// Grfio Resource file check. +static void grfio_resourcecheck(void) +{ + char restable[256]; + char *ptr, *buf; + int size; + FILE* fp; + int i = 0; + + // read resnametable from data directory and return if successful + grfio_localpath_create(restable, sizeof(restable), "data\\resnametable.txt"); + + fp = fopen(restable, "rb"); + if( fp != NULL ) + { + char line[256]; + while( fgets(line, sizeof(line), fp) ) + { + if( grfio_parse_restable_row(line) ) + ++i; + } + + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, "resnametable.txt"); + return; // we're done here! + } + + // read resnametable from loaded GRF's, only if it cannot be loaded from the data directory + buf = (char *)grfio_reads("data\\resnametable.txt", &size); + if( buf != NULL ) + { + buf[size] = '\0'; + + ptr = buf; + while( ptr - buf < size ) + { + if( grfio_parse_restable_row(ptr) ) + ++i; + + ptr = strchr(ptr, '\n'); + if( ptr == NULL ) break; + ptr++; + } + + aFree(buf); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, "data\\resnametable.txt"); + return; + } +} + + +/// Reads a grf file and adds it to the list. +static int grfio_add(const char* fname) +{ + if( gentry_entrys >= gentry_maxentry ) + { + #define GENTRY_ADDS 4 // The number increment of gentry_table entries + gentry_maxentry += GENTRY_ADDS; + gentry_table = (char**)aRealloc(gentry_table, gentry_maxentry * sizeof(char*)); + memset(gentry_table + (gentry_maxentry - GENTRY_ADDS), 0, sizeof(char*) * GENTRY_ADDS); + } + + gentry_table[gentry_entrys++] = aStrdup(fname); + + return grfio_entryread(fname, gentry_entrys - 1); +} + + +/// Finalizes grfio. +void grfio_final(void) +{ + if (filelist != NULL) { + int i; + for (i = 0; i < filelist_entrys; i++) + if (filelist[i].fnd != NULL) + aFree(filelist[i].fnd); + + aFree(filelist); + filelist = NULL; + } + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table != NULL) { + int i; + for (i = 0; i < gentry_entrys; i++) + if (gentry_table[i] != NULL) + aFree(gentry_table[i]); + + aFree(gentry_table); + gentry_table = NULL; + } + gentry_entrys = gentry_maxentry = 0; +} + + +/// Initializes grfio. +void grfio_init(const char* fname) +{ + FILE* data_conf; + int grf_num = 0; + + hashinit(); // hash table initialization + + data_conf = fopen(fname, "r"); + if( data_conf != NULL ) + { + char line[1024]; + while( fgets(line, sizeof(line), data_conf) ) + { + char w1[1024], w2[1024]; + + if( line[0] == '/' && line[1] == '/' ) + continue; // skip comments + + if( sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2 ) + continue; // skip unrecognized lines + + // Entry table reading + if( strcmp(w1, "grf") == 0 ) // GRF file + { + if( grfio_add(w2) == 0 ) + ++grf_num; + } + else if( strcmp(w1,"data_dir") == 0 ) // Data directory + { + safestrncpy(data_dir, w2, sizeof(data_dir)); + } + } + + fclose(data_conf); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", fname); + } + + if( grf_num == 0 ) + ShowInfo("No GRF loaded, using default data directory\n"); + + // Unneccessary area release of filelist + filelist_compact(); + + // Resource check + grfio_resourcecheck(); +} diff --git a/src/common/grfio.h b/src/common/grfio.h new file mode 100644 index 000000000..c5a56a14e --- /dev/null +++ b/src/common/grfio.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GRFIO_H_ +#define _GRFIO_H_ + +void grfio_init(const char* fname); +void grfio_final(void); +void* grfio_reads(const char* fname, int* size); +char* grfio_find_file(const char* fname); +#define grfio_read(fn) grfio_reads(fn, NULL) + +unsigned long grfio_crc32(const unsigned char *buf, unsigned int len); +int decode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen); +int encode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen); + +#endif /* _GRFIO_H_ */ diff --git a/src/common/malloc.c b/src/common/malloc.c new file mode 100644 index 000000000..9976a28d5 --- /dev/null +++ b/src/common/malloc.c @@ -0,0 +1,718 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/malloc.h" +#include "../common/core.h" +#include "../common/showmsg.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +////////////// Memory Libraries ////////////////// + +#if defined(MEMWATCH) + +# include <string.h> +# include "memwatch.h" +# define MALLOC(n,file,line,func) mwMalloc((n),(file),(line)) +# define CALLOC(m,n,file,line,func) mwCalloc((m),(n),(file),(line)) +# define REALLOC(p,n,file,line,func) mwRealloc((p),(n),(file),(line)) +# define STRDUP(p,file,line,func) mwStrdup((p),(file),(line)) +# define FREE(p,file,line,func) mwFree((p),(file),(line)) +# define MEMORY_USAGE() 0 +# define MEMORY_VERIFY(ptr) mwIsSafeAddr(ptr, 1) +# define MEMORY_CHECK() CHECK() + +#elif defined(DMALLOC) + +# include <string.h> +# include <stdlib.h> +# include "dmalloc.h" +# define MALLOC(n,file,line,func) dmalloc_malloc((file),(line),(n),DMALLOC_FUNC_MALLOC,0,0) +# define CALLOC(m,n,file,line,func) dmalloc_malloc((file),(line),(m)*(n),DMALLOC_FUNC_CALLOC,0,0) +# define REALLOC(p,n,file,line,func) dmalloc_realloc((file),(line),(p),(n),DMALLOC_FUNC_REALLOC,0) +# define STRDUP(p,file,line,func) strdup(p) +# define FREE(p,file,line,func) free(p) +# define MEMORY_USAGE() dmalloc_memory_allocated() +# define MEMORY_VERIFY(ptr) (dmalloc_verify(ptr) == DMALLOC_VERIFY_NOERROR) +# define MEMORY_CHECK() dmalloc_log_stats(); dmalloc_log_unfreed() + +#elif defined(GCOLLECT) + +# include "gc.h" +# ifdef GC_ADD_CALLER +# define RETURN_ADDR 0, +# else +# define RETURN_ADDR +# endif +# define MALLOC(n,file,line,func) GC_debug_malloc((n), RETURN_ADDR (file),(line)) +# define CALLOC(m,n,file,line,func) GC_debug_malloc((m)*(n), RETURN_ADDR (file),(line)) +# define REALLOC(p,n,file,line,func) GC_debug_realloc((p),(n), RETURN_ADDR (file),(line)) +# define STRDUP(p,file,line,func) GC_debug_strdup((p), RETURN_ADDR (file),(line)) +# define FREE(p,file,line,func) GC_debug_free(p) +# define MEMORY_USAGE() GC_get_heap_size() +# define MEMORY_VERIFY(ptr) (GC_base(ptr) != NULL) +# define MEMORY_CHECK() GC_gcollect() + +#else + +# define MALLOC(n,file,line,func) malloc(n) +# define CALLOC(m,n,file,line,func) calloc((m),(n)) +# define REALLOC(p,n,file,line,func) realloc((p),(n)) +# define STRDUP(p,file,line,func) strdup(p) +# define FREE(p,file,line,func) free(p) +# define MEMORY_USAGE() 0 +# define MEMORY_VERIFY(ptr) true +# define MEMORY_CHECK() + +#endif + +void* aMalloc_(size_t size, const char *file, int line, const char *func) +{ + void *ret = MALLOC(size, file, line, func); + // ShowMessage("%s:%d: in func %s: aMalloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aMalloc error out of memory!\n",file,line,func); + exit(EXIT_FAILURE); + } + + return ret; +} +void* aCalloc_(size_t num, size_t size, const char *file, int line, const char *func) +{ + void *ret = CALLOC(num, size, file, line, func); + // ShowMessage("%s:%d: in func %s: aCalloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aCalloc error out of memory!\n", file, line, func); + exit(EXIT_FAILURE); + } + return ret; +} +void* aRealloc_(void *p, size_t size, const char *file, int line, const char *func) +{ + void *ret = REALLOC(p, size, file, line, func); + // ShowMessage("%s:%d: in func %s: aRealloc %p %d\n",file,line,func,p,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aRealloc error out of memory!\n",file,line,func); + exit(EXIT_FAILURE); + } + return ret; +} +char* aStrdup_(const char *p, const char *file, int line, const char *func) +{ + char *ret = STRDUP(p, file, line, func); + // ShowMessage("%s:%d: in func %s: aStrdup %p\n",file,line,func,p); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aStrdup error out of memory!\n", file, line, func); + exit(EXIT_FAILURE); + } + return ret; +} +void aFree_(void *p, const char *file, int line, const char *func) +{ + // ShowMessage("%s:%d: in func %s: aFree %p\n",file,line,func,p); + if (p) + FREE(p, file, line, func); + + p = NULL; +} + + +#ifdef USE_MEMMGR + +#if defined(DEBUG) +#define DEBUG_MEMMGR +#endif + +/* USE_MEMMGR */ + +/* + * メモリマネージャ + * malloc , free の処理を効率的に出来るようにしたもの。 + * 複雑な処理を行っているので、若干重くなるかもしれません。 + * + * データ構造など(説明下手ですいません^^; ) + * ・メモリを複数の「ブロック」に分けて、さらにブロックを複数の「ユニット」 + * に分けています。ユニットのサイズは、1ブロックの容量を複数個に均等配分 + * したものです。たとえば、1ユニット32KBの場合、ブロック1つは32Byteのユ + * ニットが、1024個集まって出来ていたり、64Byteのユニットが 512個集まって + * 出来ていたりします。(padding,unit_head を除く) + * + * ・ブロック同士はリンクリスト(block_prev,block_next) でつながり、同じサイ + * ズを持つブロック同士もリンクリスト(hash_prev,hash_nect) でつな + * がっています。それにより、不要となったメモリの再利用が効率的に行えます。 + */ + +/* ブロックのアライメント */ +#define BLOCK_ALIGNMENT1 16 +#define BLOCK_ALIGNMENT2 64 + +/* ブロックに入るデータ量 */ +#define BLOCK_DATA_COUNT1 128 +#define BLOCK_DATA_COUNT2 608 + +/* ブロックの大きさ: 16*128 + 64*576 = 40KB */ +#define BLOCK_DATA_SIZE1 ( BLOCK_ALIGNMENT1 * BLOCK_DATA_COUNT1 ) +#define BLOCK_DATA_SIZE2 ( BLOCK_ALIGNMENT2 * BLOCK_DATA_COUNT2 ) +#define BLOCK_DATA_SIZE ( BLOCK_DATA_SIZE1 + BLOCK_DATA_SIZE2 ) + +/* 一度に確保するブロックの数。 */ +#define BLOCK_ALLOC 104 + +/* ブロック */ +struct block { + struct block* block_next; /* 次に確保した領域 */ + struct block* unfill_prev; /* 次の埋まっていない領域 */ + struct block* unfill_next; /* 次の埋まっていない領域 */ + unsigned short unit_size; /* ユニットの大きさ */ + unsigned short unit_hash; /* ユニットのハッシュ */ + unsigned short unit_count; /* ユニットの個数 */ + unsigned short unit_used; /* 使用ユニット数 */ + unsigned short unit_unfill; /* 未使用ユニットの場所 */ + unsigned short unit_maxused; /* 使用ユニットの最大値 */ + char data[ BLOCK_DATA_SIZE ]; +}; + +struct unit_head { + struct block *block; + const char* file; + unsigned short line; + unsigned short size; + long checksum; +}; + +static struct block* hash_unfill[BLOCK_DATA_COUNT1 + BLOCK_DATA_COUNT2 + 1]; +static struct block* block_first, *block_last, block_head; + +/* メモリを使い回せない領域用のデータ */ +struct unit_head_large { + size_t size; + struct unit_head_large* prev; + struct unit_head_large* next; + struct unit_head unit_head; +}; + +static struct unit_head_large *unit_head_large_first = NULL; + +static struct block* block_malloc(unsigned short hash); +static void block_free(struct block* p); +static size_t memmgr_usage_bytes; + +#define block2unit(p, n) ((struct unit_head*)(&(p)->data[ p->unit_size * (n) ])) +#define memmgr_assert(v) do { if(!(v)) { ShowError("Memory manager: assertion '" #v "' failed!\n"); } } while(0) + +static unsigned short size2hash( size_t size ) +{ + if( size <= BLOCK_DATA_SIZE1 ) { + return (unsigned short)(size + BLOCK_ALIGNMENT1 - 1) / BLOCK_ALIGNMENT1; + } else if( size <= BLOCK_DATA_SIZE ){ + return (unsigned short)(size - BLOCK_DATA_SIZE1 + BLOCK_ALIGNMENT2 - 1) / BLOCK_ALIGNMENT2 + + BLOCK_DATA_COUNT1; + } else { + return 0xffff; // ブロック長を超える場合は hash にしない + } +} + +static size_t hash2size( unsigned short hash ) +{ + if( hash <= BLOCK_DATA_COUNT1) { + return hash * BLOCK_ALIGNMENT1; + } else { + return (hash - BLOCK_DATA_COUNT1) * BLOCK_ALIGNMENT2 + BLOCK_DATA_SIZE1; + } +} + +void* _mmalloc(size_t size, const char *file, int line, const char *func ) +{ + struct block *block; + short size_hash = size2hash( size ); + struct unit_head *head; + + if (((long) size) < 0) { + ShowError("_mmalloc: %d\n", size); + return NULL; + } + + if(size == 0) { + return NULL; + } + memmgr_usage_bytes += size; + + /* ブロック長を超える領域の確保には、malloc() を用いる */ + /* その際、unit_head.block に NULL を代入して区別する */ + if(hash2size(size_hash) > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { + struct unit_head_large* p = (struct unit_head_large*)MALLOC(sizeof(struct unit_head_large)+size,file,line,func); + if(p != NULL) { + p->size = size; + p->unit_head.block = NULL; + p->unit_head.size = 0; + p->unit_head.file = file; + p->unit_head.line = line; + p->prev = NULL; + if (unit_head_large_first == NULL) + p->next = NULL; + else { + unit_head_large_first->prev = p; + p->next = unit_head_large_first; + } + unit_head_large_first = p; + *(long*)((char*)p + sizeof(struct unit_head_large) - sizeof(long) + size) = 0xdeadbeaf; + return (char *)p + sizeof(struct unit_head_large) - sizeof(long); + } else { + ShowFatalError("Memory manager::memmgr_alloc failed (allocating %d+%d bytes at %s:%d).\n", sizeof(struct unit_head_large), size, file, line); + exit(EXIT_FAILURE); + } + } + + /* 同一サイズのブロックが確保されていない時、新たに確保する */ + if(hash_unfill[size_hash]) { + block = hash_unfill[size_hash]; + } else { + block = block_malloc(size_hash); + } + + if( block->unit_unfill == 0xFFFF ) { + // free済み領域が残っていない + memmgr_assert(block->unit_used < block->unit_count); + memmgr_assert(block->unit_used == block->unit_maxused); + head = block2unit(block, block->unit_maxused); + block->unit_used++; + block->unit_maxused++; + } else { + head = block2unit(block, block->unit_unfill); + block->unit_unfill = head->size; + block->unit_used++; + } + + if( block->unit_unfill == 0xFFFF && block->unit_maxused >= block->unit_count) { + // ユニットを使い果たしたので、unfillリストから削除 + if( block->unfill_prev == &block_head) { + hash_unfill[ size_hash ] = block->unfill_next; + } else { + block->unfill_prev->unfill_next = block->unfill_next; + } + if( block->unfill_next ) { + block->unfill_next->unfill_prev = block->unfill_prev; + } + block->unfill_prev = NULL; + } + +#ifdef DEBUG_MEMMGR + { + size_t i, sz = hash2size( size_hash ); + for( i=0; i<sz; i++ ) + { + if( ((unsigned char*)head)[ sizeof(struct unit_head) - sizeof(long) + i] != 0xfd ) + { + if( head->line != 0xfdfd ) + { + ShowError("Memory manager: freed-data is changed. (freed in %s line %d)\n", head->file,head->line); + } + else + { + ShowError("Memory manager: not-allocated-data is changed.\n"); + } + break; + } + } + memset( (char *)head + sizeof(struct unit_head) - sizeof(long), 0xcd, sz ); + } +#endif + + head->block = block; + head->file = file; + head->line = line; + head->size = (unsigned short)size; + *(long*)((char*)head + sizeof(struct unit_head) - sizeof(long) + size) = 0xdeadbeaf; + return (char *)head + sizeof(struct unit_head) - sizeof(long); +} + +void* _mcalloc(size_t num, size_t size, const char *file, int line, const char *func ) +{ + void *p = _mmalloc(num * size,file,line,func); + memset(p,0,num * size); + return p; +} + +void* _mrealloc(void *memblock, size_t size, const char *file, int line, const char *func ) +{ + size_t old_size; + if(memblock == NULL) { + return _mmalloc(size,file,line,func); + } + + old_size = ((struct unit_head *)((char *)memblock - sizeof(struct unit_head) + sizeof(long)))->size; + if( old_size == 0 ) { + old_size = ((struct unit_head_large *)((char *)memblock - sizeof(struct unit_head_large) + sizeof(long)))->size; + } + if(old_size > size) { + // サイズ縮小 -> そのまま返す(手抜き) + return memblock; + } else { + // サイズ拡大 + void *p = _mmalloc(size,file,line,func); + if(p != NULL) { + memcpy(p,memblock,old_size); + } + _mfree(memblock,file,line,func); + return p; + } +} + +char* _mstrdup(const char *p, const char *file, int line, const char *func ) +{ + if(p == NULL) { + return NULL; + } else { + size_t len = strlen(p); + char *string = (char *)_mmalloc(len + 1,file,line,func); + memcpy(string,p,len+1); + return string; + } +} + +void _mfree(void *ptr, const char *file, int line, const char *func ) +{ + struct unit_head *head; + + if (ptr == NULL) + return; + + head = (struct unit_head *)((char *)ptr - sizeof(struct unit_head) + sizeof(long)); + if(head->size == 0) { + /* malloc() で直に確保された領域 */ + struct unit_head_large *head_large = (struct unit_head_large *)((char *)ptr - sizeof(struct unit_head_large) + sizeof(long)); + if( + *(long*)((char*)head_large + sizeof(struct unit_head_large) - sizeof(long) + head_large->size) + != 0xdeadbeaf) + { + ShowError("Memory manager: args of aFree 0x%p is overflowed pointer %s line %d\n", ptr, file, line); + } else { + head->size = 0xFFFF; + if(head_large->prev) { + head_large->prev->next = head_large->next; + } else { + unit_head_large_first = head_large->next; + } + if(head_large->next) { + head_large->next->prev = head_large->prev; + } + memmgr_usage_bytes -= head_large->size; +#ifdef DEBUG_MEMMGR + // set freed memory to 0xfd + memset(ptr, 0xfd, head_large->size); +#endif + FREE(head_large,file,line,func); + } + } else { + /* ユニット解放 */ + struct block *block = head->block; + if( (char*)head - (char*)block > sizeof(struct block) ) { + ShowError("Memory manager: args of aFree 0x%p is invalid pointer %s line %d\n", ptr, file, line); + } else if(head->block == NULL) { + ShowError("Memory manager: args of aFree 0x%p is freed pointer %s:%d@%s\n", ptr, file, line, func); + } else if(*(long*)((char*)head + sizeof(struct unit_head) - sizeof(long) + head->size) != 0xdeadbeaf) { + ShowError("Memory manager: args of aFree 0x%p is overflowed pointer %s line %d\n", ptr, file, line); + } else { + memmgr_usage_bytes -= head->size; + head->block = NULL; +#ifdef DEBUG_MEMMGR + memset(ptr, 0xfd, block->unit_size - sizeof(struct unit_head) + sizeof(long) ); + head->file = file; + head->line = line; +#endif + memmgr_assert( block->unit_used > 0 ); + if(--block->unit_used == 0) { + /* ブロックの解放 */ + block_free(block); + } else { + if( block->unfill_prev == NULL) { + // unfill リストに追加 + if( hash_unfill[ block->unit_hash ] ) { + hash_unfill[ block->unit_hash ]->unfill_prev = block; + } + block->unfill_prev = &block_head; + block->unfill_next = hash_unfill[ block->unit_hash ]; + hash_unfill[ block->unit_hash ] = block; + } + head->size = block->unit_unfill; + block->unit_unfill = (unsigned short)(((uintptr_t)head - (uintptr_t)block->data) / block->unit_size); + } + } + } +} + +/* ブロックを確保する */ +static struct block* block_malloc(unsigned short hash) +{ + int i; + struct block *p; + if(hash_unfill[0] != NULL) { + /* ブロック用の領域は確保済み */ + p = hash_unfill[0]; + hash_unfill[0] = hash_unfill[0]->unfill_next; + } else { + /* ブロック用の領域を新たに確保する */ + p = (struct block*)MALLOC(sizeof(struct block) * (BLOCK_ALLOC), __FILE__, __LINE__, __func__ ); + if(p == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(EXIT_FAILURE); + } + + if(block_first == NULL) { + /* 初回確保 */ + block_first = p; + } else { + block_last->block_next = p; + } + block_last = &p[BLOCK_ALLOC - 1]; + block_last->block_next = NULL; + /* ブロックを連結させる */ + for(i=0;i<BLOCK_ALLOC;i++) { + if(i != 0) { + // p[0] はこれから使うのでリンクには加えない + p[i].unfill_next = hash_unfill[0]; + hash_unfill[0] = &p[i]; + p[i].unfill_prev = NULL; + p[i].unit_used = 0; + } + if(i != BLOCK_ALLOC -1) { + p[i].block_next = &p[i+1]; + } + } + } + + // unfill に追加 + memmgr_assert(hash_unfill[ hash ] == NULL); + hash_unfill[ hash ] = p; + p->unfill_prev = &block_head; + p->unfill_next = NULL; + p->unit_size = (unsigned short)(hash2size( hash ) + sizeof(struct unit_head)); + p->unit_hash = hash; + p->unit_count = BLOCK_DATA_SIZE / p->unit_size; + p->unit_used = 0; + p->unit_unfill = 0xFFFF; + p->unit_maxused = 0; +#ifdef DEBUG_MEMMGR + memset( p->data, 0xfd, sizeof(p->data) ); +#endif + return p; +} + +static void block_free(struct block* p) +{ + if( p->unfill_prev ) { + if( p->unfill_prev == &block_head) { + hash_unfill[ p->unit_hash ] = p->unfill_next; + } else { + p->unfill_prev->unfill_next = p->unfill_next; + } + if( p->unfill_next ) { + p->unfill_next->unfill_prev = p->unfill_prev; + } + p->unfill_prev = NULL; + } + + p->unfill_next = hash_unfill[0]; + hash_unfill[0] = p; +} + +size_t memmgr_usage (void) +{ + return memmgr_usage_bytes / 1024; +} + +#ifdef LOG_MEMMGR +static char memmer_logfile[128]; +static FILE *log_fp; + +static void memmgr_log (char *buf) +{ + if( !log_fp ) + { + time_t raw; + struct tm* t; + + log_fp = fopen(memmer_logfile,"at"); + if (!log_fp) log_fp = stdout; + + time(&raw); + t = localtime(&raw); + fprintf(log_fp, "\nMemory manager: Memory leaks found at %d/%02d/%02d %02dh%02dm%02ds (Revision %s).\n", + (t->tm_year+1900), (t->tm_mon+1), t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, get_svn_revision()); + } + fprintf(log_fp, "%s", buf); + return; +} +#endif /* LOG_MEMMGR */ + +/// Returns true if the memory location is active. +/// Active means it is allocated and points to a usable part. +/// +/// @param ptr Pointer to the memory +/// @return true if the memory is active +bool memmgr_verify(void* ptr) +{ + struct block* block = block_first; + struct unit_head_large* large = unit_head_large_first; + + if( ptr == NULL ) + return false;// never valid + + // search small blocks + while( block ) + { + if( (char*)ptr >= (char*)block && (char*)ptr < ((char*)block) + sizeof(struct block) ) + {// found memory block + if( block->unit_used && (char*)ptr >= block->data ) + {// memory block is being used and ptr points to a sub-unit + size_t i = (size_t)((char*)ptr - block->data)/block->unit_size; + struct unit_head* head = block2unit(block, i); + if( i < block->unit_maxused && head->block != NULL ) + {// memory unit is allocated, check if ptr points to the usable part + return ( (char*)ptr >= ((char*)head) + sizeof(struct unit_head) - sizeof(long) + && (char*)ptr < ((char*)head) + sizeof(struct unit_head) - sizeof(long) + head->size ); + } + } + return false; + } + block = block->block_next; + } + + // search large blocks + while( large ) + { + if( (char*)ptr >= (char*)large && (char*)ptr < ((char*)large) + large->size ) + {// found memory block, check if ptr points to the usable part + return ( (char*)ptr >= ((char*)large) + sizeof(struct unit_head_large) - sizeof(long) + && (char*)ptr < ((char*)large) + sizeof(struct unit_head_large) - sizeof(long) + large->size ); + } + large = large->next; + } + return false; +} + +static void memmgr_final (void) +{ + struct block *block = block_first; + struct unit_head_large *large = unit_head_large_first; + +#ifdef LOG_MEMMGR + int count = 0; +#endif /* LOG_MEMMGR */ + + while (block) { + if (block->unit_used) { + int i; + for (i = 0; i < block->unit_maxused; i++) { + struct unit_head *head = block2unit(block, i); + if(head->block != NULL) { + char* ptr = (char *)head + sizeof(struct unit_head) - sizeof(long); +#ifdef LOG_MEMMGR + char buf[1024]; + sprintf (buf, + "%04d : %s line %d size %lu address 0x%p\n", ++count, + head->file, head->line, (unsigned long)head->size, ptr); + memmgr_log (buf); +#endif /* LOG_MEMMGR */ + // get block pointer and free it [celest] + _mfree(ptr, ALC_MARK); + } + } + } + block = block->block_next; + } + + while(large) { + struct unit_head_large *large2; +#ifdef LOG_MEMMGR + char buf[1024]; + sprintf (buf, + "%04d : %s line %d size %lu address 0x%p\n", ++count, + large->unit_head.file, large->unit_head.line, (unsigned long)large->size, &large->unit_head.checksum); + memmgr_log (buf); +#endif /* LOG_MEMMGR */ + large2 = large->next; + FREE(large,file,line,func); + large = large2; + } +#ifdef LOG_MEMMGR + if(count == 0) { + ShowInfo("Memory manager: No memory leaks found.\n"); + } else { + ShowWarning("Memory manager: Memory leaks found and fixed.\n"); + fclose(log_fp); + } +#endif /* LOG_MEMMGR */ +} + +static void memmgr_init (void) +{ +#ifdef LOG_MEMMGR + sprintf(memmer_logfile, "log/%s.leaks", SERVER_NAME); + ShowStatus("Memory manager initialised: "CL_WHITE"%s"CL_RESET"\n", memmer_logfile); + memset(hash_unfill, 0, sizeof(hash_unfill)); +#endif /* LOG_MEMMGR */ +} +#endif /* USE_MEMMGR */ + + +/*====================================== + * Initialise + *-------------------------------------- + */ + + +/// Tests the memory for errors and memory leaks. +void malloc_memory_check(void) +{ + MEMORY_CHECK(); +} + + +/// Returns true if a pointer is valid. +/// The check is best-effort, false positives are possible. +bool malloc_verify_ptr(void* ptr) +{ +#ifdef USE_MEMMGR + return memmgr_verify(ptr) && MEMORY_VERIFY(ptr); +#else + return MEMORY_VERIFY(ptr); +#endif +} + + +size_t malloc_usage (void) +{ +#ifdef USE_MEMMGR + return memmgr_usage (); +#else + return MEMORY_USAGE(); +#endif +} + +void malloc_final (void) +{ +#ifdef USE_MEMMGR + memmgr_final (); +#endif + MEMORY_CHECK(); +} + +void malloc_init (void) +{ +#if defined(DMALLOC) && defined(CYGWIN) + // http://dmalloc.com/docs/latest/online/dmalloc_19.html + dmalloc_debug_setup(getenv("DMALLOC_OPTIONS")); +#endif +#ifdef GCOLLECT + // don't garbage collect, only report inaccessible memory that was not deallocated + GC_find_leak = 1; + GC_INIT(); +#endif +#ifdef USE_MEMMGR + memmgr_init (); +#endif +} diff --git a/src/common/malloc.h b/src/common/malloc.h new file mode 100644 index 000000000..6b4e8e5c4 --- /dev/null +++ b/src/common/malloc.h @@ -0,0 +1,92 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#include "../common/cbasetypes.h" + +#define ALC_MARK __FILE__, __LINE__, __func__ + + +// default use of the built-in memory manager +#if !defined(NO_MEMMGR) && !defined(USE_MEMMGR) +#if defined(MEMWATCH) || defined(DMALLOC) || defined(GCOLLECT) +// disable built-in memory manager when using another memory library +#define NO_MEMMGR +#else +// use built-in memory manager by default +#define USE_MEMMGR +#endif +#endif + + +////////////////////////////////////////////////////////////////////// +// Athena's built-in Memory Manager +#ifdef USE_MEMMGR + +// Enable memory manager logging by default +#define LOG_MEMMGR + +// no logging for minicore +#if defined(MINICORE) && defined(LOG_MEMMGR) +#undef LOG_MEMMGR +#endif + +# define aMalloc(n) _mmalloc(n,ALC_MARK) +# define aCalloc(m,n) _mcalloc(m,n,ALC_MARK) +# define aRealloc(p,n) _mrealloc(p,n,ALC_MARK) +# define aStrdup(p) _mstrdup(p,ALC_MARK) +# define aFree(p) _mfree(p,ALC_MARK) + + void* _mmalloc (size_t size, const char *file, int line, const char *func); + void* _mcalloc (size_t num, size_t size, const char *file, int line, const char *func); + void* _mrealloc (void *p, size_t size, const char *file, int line, const char *func); + char* _mstrdup (const char *p, const char *file, int line, const char *func); + void _mfree (void *p, const char *file, int line, const char *func); + +#else + +# define aMalloc(n) aMalloc_((n),ALC_MARK) +# define aCalloc(m,n) aCalloc_((m),(n),ALC_MARK) +# define aRealloc(p,n) aRealloc_(p,n,ALC_MARK) +# define aStrdup(p) aStrdup_(p,ALC_MARK) +# define aFree(p) aFree_(p,ALC_MARK) + + void* aMalloc_ (size_t size, const char *file, int line, const char *func); + void* aCalloc_ (size_t num, size_t size, const char *file, int line, const char *func); + void* aRealloc_ (void *p, size_t size, const char *file, int line, const char *func); + char* aStrdup_ (const char *p, const char *file, int line, const char *func); + void aFree_ (void *p, const char *file, int line, const char *func); + +#endif + +/////////////// Buffer Creation ///////////////// +// Full credit for this goes to Shinomori [Ajarn] + +#ifdef __GNUC__ // GCC has variable length arrays + + #define CREATE_BUFFER(name, type, size) type name[size] + #define DELETE_BUFFER(name) + +#else // others don't, so we emulate them + + #define CREATE_BUFFER(name, type, size) type *name = (type *) aCalloc (size, sizeof(type)) + #define DELETE_BUFFER(name) aFree(name) + +#endif + +////////////// Others ////////////////////////// +// should be merged with any of above later +#define CREATE(result, type, number) (result) = (type *) aCalloc ((number), sizeof(type)) +#define RECREATE(result, type, number) (result) = (type *) aRealloc ((result), sizeof(type) * (number)) + +//////////////////////////////////////////////// + +void malloc_memory_check(void); +bool malloc_verify_ptr(void* ptr); +size_t malloc_usage (void); +void malloc_init (void); +void malloc_final (void); + +#endif /* _MALLOC_H_ */ diff --git a/src/common/mapindex.c b/src/common/mapindex.c new file mode 100644 index 000000000..d46047833 --- /dev/null +++ b/src/common/mapindex.c @@ -0,0 +1,185 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../common/strlib.h" +#include "mapindex.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +struct _indexes { + char name[MAP_NAME_LENGTH]; //Stores map name +} indexes[MAX_MAPINDEX]; + +int max_index = 0; + +char mapindex_cfgfile[80] = "db/map_index.txt"; + +#define mapindex_exists(id) (indexes[id].name[0] != '\0') + +/// Retrieves the map name from 'string' (removing .gat extension if present). +/// Result gets placed either into 'buf' or in a static local buffer. +const char* mapindex_getmapname(const char* string, char* output) +{ + static char buf[MAP_NAME_LENGTH]; + char* dest = (output != NULL) ? output : buf; + + size_t len = strnlen(string, MAP_NAME_LENGTH_EXT); + if (len == MAP_NAME_LENGTH_EXT) { + ShowWarning("(mapindex_normalize_name) Map name '%*s' is too long!\n", 2*MAP_NAME_LENGTH_EXT, string); + len--; + } + if (len >= 4 && stricmp(&string[len-4], ".gat") == 0) + len -= 4; // strip .gat extension + + len = min(len, MAP_NAME_LENGTH-1); + strncpy(dest, string, len+1); + memset(&dest[len], '\0', MAP_NAME_LENGTH-len); + + return dest; +} + +/// Retrieves the map name from 'string' (adding .gat extension if not already present). +/// Result gets placed either into 'buf' or in a static local buffer. +const char* mapindex_getmapname_ext(const char* string, char* output) +{ + static char buf[MAP_NAME_LENGTH_EXT]; + char* dest = (output != NULL) ? output : buf; + + size_t len; + + strcpy(buf,string); + sscanf(string,"%*[^#]%*[#]%s",buf); + + len = safestrnlen(buf, MAP_NAME_LENGTH); + + if (len == MAP_NAME_LENGTH) { + ShowWarning("(mapindex_normalize_name) Map name '%*s' is too long!\n", 2*MAP_NAME_LENGTH, buf); + len--; + } + strncpy(dest, buf, len+1); + + if (len < 4 || stricmp(&dest[len-4], ".gat") != 0) { + strcpy(&dest[len], ".gat"); + len += 4; // add .gat extension + } + + memset(&dest[len], '\0', MAP_NAME_LENGTH_EXT-len); + + return dest; +} + +/// Adds a map to the specified index +/// Returns 1 if successful, 0 oherwise +int mapindex_addmap(int index, const char* name) +{ + char map_name[MAP_NAME_LENGTH]; + + if (index == -1){ + for (index = 1; index < max_index; index++) + { + //if (strcmp(indexes[index].name,"#CLEARED#")==0) + if (indexes[index].name[0] == '\0') + break; + } + } + + if (index < 0 || index >= MAX_MAPINDEX) { + ShowError("(mapindex_add) Map index (%d) for \"%s\" out of range (max is %d)\n", index, name, MAX_MAPINDEX); + return 0; + } + + mapindex_getmapname(name, map_name); + + if (map_name[0] == '\0') { + ShowError("(mapindex_add) Cannot add maps with no name.\n"); + return 0; + } + + if (strlen(map_name) >= MAP_NAME_LENGTH) { + ShowError("(mapindex_add) Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + return 0; + } + + if (mapindex_exists(index)) + ShowWarning("(mapindex_add) Overriding index %d: map \"%s\" -> \"%s\"\n", index, indexes[index].name, map_name); + + safestrncpy(indexes[index].name, map_name, MAP_NAME_LENGTH); + if (max_index <= index) + max_index = index+1; + + return index; +} + +unsigned short mapindex_name2id(const char* name) +{ + //TODO: Perhaps use a db to speed this up? [Skotlex] + int i; + + char map_name[MAP_NAME_LENGTH]; + mapindex_getmapname(name, map_name); + + for (i = 1; i < max_index; i++) + { + if (strcmpi(indexes[i].name,map_name)==0) + return i; + } + ShowDebug("mapindex_name2id: Map \"%s\" not found in index list!\n", map_name); + return 0; +} + +const char* mapindex_id2name(unsigned short id) +{ + if (id > MAX_MAPINDEX || !mapindex_exists(id)) { + ShowDebug("mapindex_id2name: Requested name for non-existant map index [%d] in cache.\n", id); + return indexes[0].name; // dummy empty string so that the callee doesn't crash + } + return indexes[id].name; +} + +void mapindex_init(void) +{ + FILE *fp; + char line[1024]; + int last_index = -1; + int index; + char map_name[1024]; + + memset (&indexes, 0, sizeof (indexes)); + fp=fopen(mapindex_cfgfile,"r"); + if(fp==NULL){ + ShowFatalError("Unable to read mapindex config file %s!\n", mapindex_cfgfile); + exit(EXIT_FAILURE); //Server can't really run without this file. + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + + switch (sscanf(line, "%1023s\t%d", map_name, &index)) + { + case 1: //Map with no ID given, auto-assign + index = last_index+1; + case 2: //Map with ID given + mapindex_addmap(index,map_name); + break; + default: + continue; + } + last_index = index; + } + fclose(fp); +} + +int mapindex_removemap(int index){ + indexes[index].name[0] = '\0'; + return 0; +} + +void mapindex_final(void) +{ +} diff --git a/src/common/mapindex.h b/src/common/mapindex.h new file mode 100644 index 000000000..75cb254c0 --- /dev/null +++ b/src/common/mapindex.h @@ -0,0 +1,60 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MAPINDEX_H_ +#define _MAPINDEX_H_ + +//File in charge of assigning a numberic ID to each map in existance for space saving when passing map info between servers. +extern char mapindex_cfgfile[80]; + +#define MAX_MAPINDEX 2000 + +//Some definitions for the mayor city maps. +#define MAP_PRONTERA "prontera" +#define MAP_GEFFEN "geffen" +#define MAP_MORROC "morocc" +#define MAP_ALBERTA "alberta" +#define MAP_PAYON "payon" +#define MAP_IZLUDE "izlude" +#define MAP_ALDEBARAN "aldebaran" +#define MAP_LUTIE "xmas" +#define MAP_COMODO "comodo" +#define MAP_YUNO "yuno" +#define MAP_AMATSU "amatsu" +#define MAP_GONRYUN "gonryun" +#define MAP_UMBALA "umbala" +#define MAP_NIFLHEIM "niflheim" +#define MAP_LOUYANG "louyang" +#define MAP_JAWAII "jawaii" +#define MAP_AYOTHAYA "ayothaya" +#define MAP_EINBROCH "einbroch" +#define MAP_LIGHTHALZEN "lighthalzen" +#define MAP_EINBECH "einbech" +#define MAP_HUGEL "hugel" +#define MAP_RACHEL "rachel" +#define MAP_VEINS "veins" +#define MAP_JAIL "sec_pri" +#define MAP_NOVICE "new_1-1" +#define MAP_MOSCOVIA "moscovia" +#define MAP_MIDCAMP "mid_camp" +#define MAP_MANUK "manuk" +#define MAP_SPLENDIDE "splendide" +#define MAP_BRASILIS "brasilis" +#define MAP_DICASTES "dicastes01" +#define MAP_MORA "mora" +#define MAP_DEWATA "dewata" +#define MAP_MALANGDO "malangdo" +#define MAP_MALAYA "malaya" +#define MAP_ECLAGE "eclage" + +const char* mapindex_getmapname(const char* string, char* output); +const char* mapindex_getmapname_ext(const char* string, char* output); +unsigned short mapindex_name2id(const char*); +const char* mapindex_id2name(unsigned short); +void mapindex_init(void); +void mapindex_final(void); + +int mapindex_addmap(int index, const char* name); +int mapindex_removemap(int index); + +#endif /* _MAPINDEX_H_ */ diff --git a/src/common/md5calc.c b/src/common/md5calc.c new file mode 100644 index 000000000..05fde42cc --- /dev/null +++ b/src/common/md5calc.c @@ -0,0 +1,240 @@ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "../common/random.h" +#include "md5calc.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// String Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +static void MD5_String2binary(const char * string, unsigned char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = (unsigned int)strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_Binary(const char * string, unsigned char * output) +{ + MD5_String2binary(string,output); +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + +/** output is a sequence of non-zero characters to be used as password salt. */ +void MD5_Salt(unsigned int len, char * output) +{ + unsigned int i; + for( i = 0; i < len; ++i ) + output[i] = (char)(1 + rnd() % 255); + +} diff --git a/src/common/md5calc.h b/src/common/md5calc.h new file mode 100644 index 000000000..323affa2c --- /dev/null +++ b/src/common/md5calc.h @@ -0,0 +1,8 @@ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_Binary(const char * string, unsigned char * output); +void MD5_Salt(unsigned int len, char * output); + +#endif /* _MD5CALC_H_ */ diff --git a/src/common/mempool.c b/src/common/mempool.c new file mode 100644 index 000000000..5eccbf178 --- /dev/null +++ b/src/common/mempool.c @@ -0,0 +1,568 @@ + +// +// Memory Pool Implementation (Threadsafe) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef WIN32 +#include "../common/winapi.h" +#else +#include <unistd.h> +#endif + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/mempool.h" +#include "../common/atomic.h" +#include "../common/spinlock.h" +#include "../common/thread.h" +#include "../common/malloc.h" +#include "../common/mutex.h" + +#define ALIGN16 ra_align(16) +#define ALIGN_TO(x, a) (x + ( a - ( x % a) ) ) +#define ALIGN_TO_16(x) ALIGN_TO(x, 16) + +#undef MEMPOOL_DEBUG +#define MEMPOOLASSERT + + +#define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) ) +#define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) ) +struct ra_align(16) node{ + void *next; + void *segment; +#ifdef MEMPOOLASSERT + bool used; + uint64 magic; + #define NODE_MAGIC 0xBEEF00EAEACAFE07ll +#endif +}; + + +// The Pointer to this struct is the base address of the segment itself. +struct pool_segment{ + mempool pool; // pool, this segment belongs to + struct pool_segment *next; + int64 num_nodes_total; + int64 num_bytes; +}; + + +struct mempool{ + // Settings + char *name; + uint64 elem_size; + uint64 elem_realloc_step; + int64 elem_realloc_thresh; + + // Callbacks that get called for every node that gets allocated + // Example usage: initialization of mutex/lock for each node. + memPoolOnNodeAllocationProc onalloc; + memPoolOnNodeDeallocationProc ondealloc; + + // Locks + SPIN_LOCK segmentLock; + SPIN_LOCK nodeLock; + + + // Internal + struct pool_segment *segments; + struct node *free_list; + + volatile int64 num_nodes_total; + volatile int64 num_nodes_free; + + volatile int64 num_segments; + volatile int64 num_bytes_total; + + volatile int64 peak_nodes_used; // Peak Node Usage + volatile int64 num_realloc_events; // Number of reallocations done. (allocate additional nodes) + + // list (used for global management such as allocator..) + struct mempool *next; +} ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken .. + + +/// +// Implementation: +// +static void segment_allocate_add(mempool p, uint64 count); + +static SPIN_LOCK l_mempoolListLock; +static mempool l_mempoolList = NULL; +static rAthread l_async_thread = NULL; +static ramutex l_async_lock = NULL; +static racond l_async_cond = NULL; +static volatile int32 l_async_terminate = 0; + +static void *mempool_async_allocator(void *x){ + mempool p; + + + while(1){ + if(l_async_terminate > 0) + break; + + EnterSpinLock(&l_mempoolListLock); + + for(p = l_mempoolList; p != NULL; p = p->next){ + + if(p->num_nodes_free < p->elem_realloc_thresh){ + // add new segment. + segment_allocate_add(p, p->elem_realloc_step); + // increase stats counter + InterlockedIncrement64(&p->num_realloc_events); + } + + } + + LeaveSpinLock(&l_mempoolListLock); + + ramutex_lock( l_async_lock ); + racond_wait( l_async_cond, l_async_lock, -1 ); + ramutex_unlock( l_async_lock ); + } + + + return NULL; +}//end: mempool_async_allocator() + + +void mempool_init(){ + + if( rand()%2 + 1 ) + return; + + if(sizeof(struct node)%16 != 0 ){ + ShowFatalError("mempool_init: struct node alignment failure. %u != multiple of 16\n", sizeof(struct node)); + exit(EXIT_FAILURE); + } + + // Global List start + InitializeSpinLock(&l_mempoolListLock); + l_mempoolList = NULL; + + // Initialize mutex + stuff needed for async allocator worker. + l_async_terminate = 0; + l_async_lock = ramutex_create(); + l_async_cond = racond_create(); + + l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 1024*1024, RAT_PRIO_NORMAL); + if(l_async_thread == NULL){ + ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n"); + exit(EXIT_FAILURE); + } + +}//end: mempool_init() + + +void mempool_final(){ + mempool p, pn; + + if( rand()%2 + 1 ) + return; + + ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n"); + + // Terminate worker / wait until its terminated. + InterlockedIncrement(&l_async_terminate); + racond_signal(l_async_cond); + rathread_wait(l_async_thread, NULL); + + // Destroy cond var and mutex. + racond_destroy( l_async_cond ); + ramutex_destroy( l_async_lock ); + + // Free remaining mempools + // ((bugged code! this should halppen, every mempool should + // be freed by the subsystem that has allocated it.) + // + EnterSpinLock(&l_mempoolListLock); + p = l_mempoolList; + while(1){ + if(p == NULL) + break; + + pn = p->next; + + ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name); + mempool_destroy(p); + + p = pn; + } + LeaveSpinLock(&l_mempoolListLock); + +}//end: mempool_final() + + +static void segment_allocate_add(mempool p, uint64 count){ + + // Required Memory: + // sz( segment ) + // count * sz( real_node_size ) + // + // where real node size is: + // ALIGN_TO_16( sz( node ) ) + p->elem_size + // so the nodes usable address is nodebase + ALIGN_TO_16(sz(node)) + // + size_t total_sz; + struct pool_segment *seg = NULL; + struct node *nodeList = NULL; + struct node *node = NULL; + char *ptr = NULL; + uint64 i; + + total_sz = ALIGN_TO_16( sizeof(struct pool_segment) ) + + ( (size_t)count * (sizeof(struct node) + (size_t)p->elem_size) ) ; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f); +#endif + + // allocate! (spin forever until weve got the memory.) + i=0; + while(1){ + ptr = (char*)aMalloc(total_sz); + if(ptr != NULL) break; + + i++; // increase failcount. + if(!(i & 7)){ + ShowWarning("Mempool [%s] Segment AllocateAdd => System seems to be Out of Memory (%0.2f MiB). Try #%u\n", (float)total_sz/1024.f/1024.f, i); +#ifdef WIN32 + Sleep(1000); +#else + sleep(1); +#endif + }else{ + rathread_yield(); /// allow/force vuln. ctxswitch + } + }//endwhile: allocation spinloop. + + // Clear Memory. + memset(ptr, 0x00, total_sz); + + // Initialize segment struct. + seg = (struct pool_segment*)ptr; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + + seg->pool = p; + seg->num_nodes_total = count; + seg->num_bytes = total_sz; + + + // Initialze nodes! + nodeList = NULL; + for(i = 0; i < count; i++){ + node = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; + + node->segment = seg; +#ifdef MEMPOOLASSERT + node->used = false; + node->magic = NODE_MAGIC; +#endif + + if(p->onalloc != NULL) p->onalloc( NODE_TO_DATA(node) ); + + node->next = nodeList; + nodeList = node; + } + + + + // Link in Segment. + EnterSpinLock(&p->segmentLock); + seg->next = p->segments; + p->segments = seg; + LeaveSpinLock(&p->segmentLock); + + // Link in Nodes + EnterSpinLock(&p->nodeLock); + nodeList->next = p->free_list; + p->free_list = nodeList; + LeaveSpinLock(&p->nodeLock); + + + // Increase Stats: + InterlockedExchangeAdd64(&p->num_nodes_total, count); + InterlockedExchangeAdd64(&p->num_nodes_free, count); + InterlockedIncrement64(&p->num_segments); + InterlockedExchangeAdd64(&p->num_bytes_total, total_sz); + +}//end: segment_allocate_add() + + +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc){ + //.. + uint64 realloc_thresh; + mempool pool; + pool = (mempool)aCalloc( 1, sizeof(struct mempool) ); + + if(pool == NULL){ + ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool) ); + exit(EXIT_FAILURE); + } + + // Check minimum initial count / realloc count requirements. + if(initial_count < 50) + initial_count = 50; + if(realloc_count < 50) + realloc_count = 50; + + // Set Reallocation threshold to 5% of realloc_count, at least 10. + realloc_thresh = (realloc_count/100)*5; // + if(realloc_thresh < 10) + realloc_thresh = 10; + + // Initialize members.. + pool->name = aStrdup(name); + pool->elem_size = ALIGN_TO_16(elem_size); + pool->elem_realloc_step = realloc_count; + pool->elem_realloc_thresh = realloc_thresh; + pool->onalloc = onNodeAlloc; + pool->ondealloc = onNodeDealloc; + + InitializeSpinLock(&pool->segmentLock); + InitializeSpinLock(&pool->nodeLock); + + // Initial Statistic values: + pool->num_nodes_total = 0; + pool->num_nodes_free = 0; + pool->num_segments = 0; + pool->num_bytes_total = 0; + pool->peak_nodes_used = 0; + pool->num_realloc_events = 0; + + // +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Init (ElemSize: %u, Initial Count: %u, Realloc Count: %u)\n", pool->name, pool->elem_size, initial_count, pool->elem_realloc_step); +#endif + + // Allocate first segment directly :) + segment_allocate_add(pool, initial_count); + + + // Add Pool to the global pool list + EnterSpinLock(&l_mempoolListLock); + pool->next = l_mempoolList; + l_mempoolList = pool; + LeaveSpinLock(&l_mempoolListLock); + + + return pool; +}//end: mempool_create() + + +void mempool_destroy(mempool p){ + struct pool_segment *seg, *segnext; + struct node *niter; + mempool piter, pprev; + char *ptr; + int64 i; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Destroy\n", p->name); +#endif + + // Unlink from global list. + EnterSpinLock(&l_mempoolListLock); + piter = l_mempoolList; + pprev = l_mempoolList; + while(1){ + if(piter == NULL) + break; + + + if(piter == p){ + // unlink from list, + // + if(pprev == l_mempoolList){ + // this (p) is list begin. so set next as head. + l_mempoolList = p->next; + }else{ + // replace prevs next wuth our next. + pprev->next = p->next; + } + break; + } + + pprev = piter; + piter = piter->next; + } + + p->next = NULL; + LeaveSpinLock(&l_mempoolListLock); + + + // Get both locks. + EnterSpinLock(&p->segmentLock); + EnterSpinLock(&p->nodeLock); + + + if(p->num_nodes_free != p->num_nodes_total) + ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free) ); + + // Free All Segments (this will also free all nodes) + // The segment pointer is the base pointer to the whole segment. + seg = p->segments; + while(1){ + if(seg == NULL) + break; + + segnext = seg->next; + + // .. + if(p->ondealloc != NULL){ + // walk over the segment, and call dealloc callback! + ptr = (char*)seg; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + for(i = 0; i < seg->num_nodes_total; i++){ + niter = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; +#ifdef MEMPOOLASSERT + if(niter->magic != NODE_MAGIC){ + ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter); + continue; + } +#endif + + p->ondealloc( NODE_TO_DATA(niter) ); + + + } + }//endif: ondealloc callback? + + // simple .. + aFree(seg); + + seg = segnext; + } + + // Clear node ptr + p->free_list = NULL; + InterlockedExchange64(&p->num_nodes_free, 0); + InterlockedExchange64(&p->num_nodes_total, 0); + InterlockedExchange64(&p->num_segments, 0); + InterlockedExchange64(&p->num_bytes_total, 0); + + LeaveSpinLock(&p->nodeLock); + LeaveSpinLock(&p->segmentLock); + + // Free pool itself :D + aFree(p->name); + aFree(p); + +}//end: mempool_destroy() + + +void *mempool_node_get(mempool p){ + struct node *node; + int64 num_used; + + if(p->num_nodes_free < p->elem_realloc_thresh) + racond_signal(l_async_cond); + + while(1){ + + EnterSpinLock(&p->nodeLock); + + node = p->free_list; + if(node != NULL) + p->free_list = node->next; + + LeaveSpinLock(&p->nodeLock); + + if(node != NULL) + break; + + rathread_yield(); + } + + InterlockedDecrement64(&p->num_nodes_free); + + // Update peak value + num_used = (p->num_nodes_total - p->num_nodes_free); + if(num_used > p->peak_nodes_used){ + InterlockedExchange64(&p->peak_nodes_used, num_used); + } + +#ifdef MEMPOOLASSERT + node->used = true; +#endif + + return NODE_TO_DATA(node); +}//end: mempool_node_get() + + +void mempool_node_put(mempool p, void *data){ + struct node *node; + + node = DATA_TO_NODE(data); +#ifdef MEMPOOLASSERT + if(node->magic != NODE_MAGIC){ + ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name, data); + return; // lost, + } + + { + struct pool_segment *node_seg = node->segment; + if(node_seg->pool != p){ + ShowError("Mempool [%s] node_put faild, given node (data address %p) doesnt belongs to this pool. ( Node Origin is [%s] )\n", p->name, data, node_seg->pool); + return; + } + } + + // reset used flag. + node->used = false; +#endif + + // + EnterSpinLock(&p->nodeLock); + node->next = p->free_list; + p->free_list = node; + LeaveSpinLock(&p->nodeLock); + + InterlockedIncrement64(&p->num_nodes_free); + +}//end: mempool_node_put() + + +mempool_stats mempool_get_stats(mempool pool){ + mempool_stats stats; + + // initialize all with zeros + memset(&stats, 0x00, sizeof(mempool_stats)); + + stats.num_nodes_total = pool->num_nodes_total; + stats.num_nodes_free = pool->num_nodes_free; + stats.num_nodes_used = (stats.num_nodes_total - stats.num_nodes_free); + stats.num_segments = pool->num_segments; + stats.num_realloc_events= pool->num_realloc_events; + stats.peak_nodes_used = pool->peak_nodes_used; + stats.num_bytes_total = pool->num_bytes_total; + + // Pushing such a large block over the stack as return value isnt nice + // but lazy :) and should be okay in this case (Stats / Debug..) + // if you dont like it - feel free and refactor it. + return stats; +}//end: mempool_get_stats() + diff --git a/src/common/mempool.h b/src/common/mempool.h new file mode 100644 index 000000000..aeaebe7fe --- /dev/null +++ b/src/common/mempool.h @@ -0,0 +1,100 @@ +#ifndef _rA_MEMPOOL_H_ +#define _rA_MEMPOOL_H_ + +#include "../common/cbasetypes.h" + +typedef struct mempool *mempool; + +typedef void (*memPoolOnNodeAllocationProc)(void *ptr); +typedef void (*memPoolOnNodeDeallocationProc)(void *ptr); + +typedef struct mempool_stats{ + int64 num_nodes_total; + int64 num_nodes_free; + int64 num_nodes_used; + + int64 num_segments; + int64 num_realloc_events; + + int64 peak_nodes_used; + + int64 num_bytes_total; +} mempool_stats; + + +// +void mempool_init(); +void mempool_final(); + + +/** + * Creates a new Mempool + * + * @param name - Name of the pool (used for debug / error messages) + * @param elem_size - size of each element + * @param initial_count - preallocation count + * @param realloc_count - #no of nodes being allocated when pool is running empty. + * @param onNodeAlloc - Node Allocation callback (see @note!) + * @param onNodeDealloc - Node Deallocation callback (see @note!) + * + * @note: + * The onNode(De)alloc callbacks are only called once during segment allocation + * (pool initialization / rallocation ) + * you can use this callbacks for example to initlaize a mutex or somethingelse + * you definitly need during runtime + * + * @return not NULL + */ +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc); + + +/** + * Destroys a Mempool + * + * @param pool - the mempool to destroy + * + * @note: + * Everything gets deallocated, regardless if everything was freed properly! + * So you have to ensure that all references are cleared properly! + */ +void mempool_destroy(mempool pool); + + +/** + * Gets a new / empty node from the given mempool. + * + * @param pool - the pool to get an empty node from. + * + * @return Address of empty Node + */ +void *mempool_node_get(mempool pool); + + +/** + * Returns the given node to the given mempool + * + * @param pool - the pool to put the node, to + * @param node - the node to return + */ +void mempool_node_put(mempool pool, void *node); + + +/** + * Returns Statistics for the given mempool + * + * @param pool - the pool to get thats for + * + * @note: i dont like pushing masses of values over the stack, too - but its lazy and okay for stats. (blacksirius) + * + * @return stats struct + */ +mempool_stats mempool_get_stats(mempool pool); + + +#endif diff --git a/src/common/mmo.h b/src/common/mmo.h new file mode 100644 index 000000000..493f87691 --- /dev/null +++ b/src/common/mmo.h @@ -0,0 +1,750 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include "cbasetypes.h" +#include <time.h> + +// server->client protocol version +// 0 - pre-? +// 1 - ? - 0x196 +// 2 - ? - 0x78, 0x79 +// 3 - ? - 0x1c8, 0x1c9, 0x1de +// 4 - ? - 0x1d7, 0x1d8, 0x1d9, 0x1da +// 5 - 2003-12-18aSakexe+ - 0x1ee, 0x1ef, 0x1f0, ?0x1c4, 0x1c5? +// 6 - 2004-03-02aSakexe+ - 0x1f4, 0x1f5 +// 7 - 2005-04-11aSakexe+ - 0x229, 0x22a, 0x22b, 0x22c +// 20061023 - 2006-10-23aSakexe+ - 0x6b, 0x6d +// 20070521 - 2007-05-21aSakexe+ - 0x283 +// 20070821 - 2007-08-21aSakexe+ - 0x2c5 +// 20070918 - 2007-09-18aSakexe+ - 0x2d7, 0x2d9, 0x2da +// 20071106 - 2007-11-06aSakexe+ - 0x78, 0x7c, 0x22c +// 20080102 - 2008-01-02aSakexe+ - 0x2ec, 0x2ed , 0x2ee +// 20081126 - 2008-11-26aSakexe+ - 0x1a2 +// 20090408 - 2009-04-08aSakexe+ - 0x44a (dont use as it overlaps with RE client packets) +// 20080827 - 2008-08-27aRagexeRE+ - First RE Client +// 20081217 - 2008-12-17aRagexeRE+ - 0x6d (Note: This one still use old Char Info Packet Structure) +// 20081218 - 2008-12-17bRagexeRE+ - 0x6d (Note: From this one client use new Char Info Packet Structure) +// 20090603 - 2009-06-03aRagexeRE+ - 0x7d7, 0x7d8, 0x7d9, 0x7da +// 20090617 - 2009-06-17aRagexeRE+ - 0x7d9 +// 20090922 - 2009-09-22aRagexeRE+ - 0x7e5, 0x7e7, 0x7e8, 0x7e9 +// 20091103 - 2009-11-03aRagexeRE+ - 0x7f7, 0x7f8, 0x7f9 +// 20100105 - 2010-01-05aRagexeRE+ - 0x133, 0x800, 0x801 +// 20100126 - 2010-01-26aRagexeRE+ - 0x80e +// 20100223 - 2010-02-23aRagexeRE+ - 0x80f +// 20100413 - 2010-04-13aRagexeRE+ - 0x6b +// 20100629 - 2010-06-29aRagexeRE+ - 0x2d0, 0xaa, 0x2d1, 0x2d2 +// 20100721 - 2010-07-21aRagexeRE+ - 0x6b, 0x6d +// 20100727 - 2010-07-27aRagexeRE+ - 0x6b, 0x6d +// 20100803 - 2010-08-03aRagexeRE+ - 0x6b, 0x6d, 0x827, 0x828, 0x829, 0x82a, 0x82b, 0x82c, 0x842, 0x843 +// 20101124 - 2010-11-24aRagexeRE+ - 0x856, 0x857, 0x858 +// 20110111 - 2011-01-11aRagexeRE+ - 0x6b, 0x6d +// 20110928 - 2011-09-28aRagexeRE+ - 0x6b, 0x6d +// 20111025 - 2011-10-25aRagexeRE+ - 0x6b, 0x6d +// 20120307 - 2012-03-07aRagexeRE+ - 0x970 + +#ifndef PACKETVER + #define PACKETVER 20120410 + //#define PACKETVER 20111116 +#endif + +//Remove/Comment this line to disable sc_data saving. [Skotlex] +#define ENABLE_SC_SAVING +//Remove/Comment this line to disable server-side hot-key saving support [Skotlex] +//Note that newer clients no longer save hotkeys in the registry! +#define HOTKEY_SAVING + +#if PACKETVER < 20090603 + // (27 = 9 skills x 3 bars) (0x02b9,191) + #define MAX_HOTKEYS 27 +#elif PACKETVER < 20090617 + // (36 = 9 skills x 4 bars) (0x07d9,254) + #define MAX_HOTKEYS 36 +#else + // (38 = 9 skills x 4 bars & 2 Quickslots)(0x07d9,268) + #define MAX_HOTKEYS 38 +#endif + +#define MAX_MAP_PER_SERVER 1500 // Increased to allow creation of Instance Maps +#define MAX_INVENTORY 100 +//Max number of characters per account. Note that changing this setting alone is not enough if the client is not hexed to support more characters as well. +#define MAX_CHARS 9 +//Number of slots carded equipment can have. Never set to less than 4 as they are also used to keep the data of forged items/equipment. [Skotlex] +//Note: The client seems unable to receive data for more than 4 slots due to all related packets having a fixed size. +#define MAX_SLOTS 4 +//Max amount of a single stacked item +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 +#define MAX_FAME 1000000000 +#define MAX_CART 100 +#define MAX_SKILL 3100 +#define GLOBAL_REG_NUM 256 // max permanent character variables per char +#define ACCOUNT_REG_NUM 64 // max permanent local account variables per account +#define ACCOUNT_REG2_NUM 16 // max permanent global account variables per account +//Should hold the max of GLOBAL/ACCOUNT/ACCOUNT2 (needed for some arrays that hold all three) +#define MAX_REG_NUM 256 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 600 +#define MAX_GUILD_STORAGE 600 +#define MAX_PARTY 12 +#define MAX_GUILD 16+10*6 // increased max guild members +6 per 1 extension levels [Lupus] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPULSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 15 // increased max guild skills because of new skills [Sara-chan] +#define MAX_GUILDLEVEL 50 +#define MAX_GUARDIANS 8 //Local max per castle. [Skotlex] +#define MAX_QUEST_DB 2200 //Max quests that the server will load +#define MAX_QUEST_OBJECTIVES 3 //Max quest objectives for a quest + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +//For character names, title names, guilds, maps, etc. +//Includes null-terminator as it is the length of the array. +#define NAME_LENGTH (23 + 1) +//For item names, which tend to have much longer names. +#define ITEM_NAME_LENGTH 50 +//For Map Names, which the client considers to be 16 in length including the .gat extension +#define MAP_NAME_LENGTH (11 + 1) +#define MAP_NAME_LENGTH_EXT (MAP_NAME_LENGTH + 4) + +#define MAX_FRIENDS 40 +#define MAX_MEMOPOINTS 3 + +//Size of the fame list arrays. +#define MAX_FAME_LIST 10 + +//Limits to avoid ID collision with other game objects +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 +#define START_CHAR_NUM 150000 + +//Guilds +#define MAX_GUILDMES1 60 +#define MAX_GUILDMES2 120 + +//Base Homun skill. +#define HM_SKILLBASE 8001 +#define MAX_HOMUNSKILL 43 +#define MAX_HOMUNCULUS_CLASS 52 //[orn], Increased to 60 from 16 to allow new Homun-S. +#define HM_CLASS_BASE 6001 +#define HM_CLASS_MAX (HM_CLASS_BASE+MAX_HOMUNCULUS_CLASS-1) + +//Mail System +#define MAIL_MAX_INBOX 30 +#define MAIL_TITLE_LENGTH 40 +#define MAIL_BODY_LENGTH 200 + +//Mercenary System +#define MC_SKILLBASE 8201 +#define MAX_MERCSKILL 40 +#define MAX_MERCENARY_CLASS 44 + +//Elemental System +#define MAX_ELEMENTALSKILL 42 +#define EL_SKILLBASE 8401 +#define MAX_ELESKILLTREE 3 +#define MAX_ELEMENTAL_CLASS 12 +#define EL_CLASS_BASE 2114 +#define EL_CLASS_MAX (EL_CLASS_BASE+MAX_ELEMENTAL_CLASS-1) + +enum item_types { + IT_HEALING = 0, + IT_UNKNOWN, //1 + IT_USABLE, //2 + IT_ETC, //3 + IT_WEAPON, //4 + IT_ARMOR, //5 + IT_CARD, //6 + IT_PETEGG, //7 + IT_PETARMOR,//8 + IT_UNKNOWN2,//9 + IT_AMMO, //10 + IT_DELAYCONSUME,//11 + IT_CASH = 18, + IT_MAX +}; + + +//Questlog system [Kevin] [Inkfish] +typedef enum quest_state { Q_INACTIVE, Q_ACTIVE, Q_COMPLETE } quest_state; + +struct quest { + int quest_id; + unsigned int time; + int count[MAX_QUEST_OBJECTIVES]; + quest_state state; +}; + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; // location(s) where item is equipped (using enum equip_pos for bitmasking) + char identify; + char refine; + char attribute; + short card[MAX_SLOTS]; + unsigned int expire_time; + char favorite; + uint64 unique_id; +}; + +struct point { + unsigned short map; + short x,y; +}; + +enum e_skill_flag +{ + SKILL_FLAG_PERMANENT, + SKILL_FLAG_TEMPORARY, + SKILL_FLAG_PLAGIARIZED, + SKILL_FLAG_REPLACED_LV_0, // temporary skill overshadowing permanent skill of level 'N - SKILL_FLAG_REPLACED_LV_0', + SKILL_FLAG_PERM_GRANTED, // permanent, granted through someway e.g. script + //... +}; + +struct s_skill { + unsigned short id; + unsigned char lv; + unsigned char flag; // see enum e_skill_flag +}; + +struct global_reg { + char str[32]; + char value[256]; +}; + +//Holds array of global registries, used by the char server and converter. +struct accreg { + int account_id, char_id; + int reg_num; + struct global_reg reg[MAX_REG_NUM]; +}; + +//For saving status changes across sessions. [Skotlex] +struct status_change_data { + unsigned short type; //SC_type + long val1, val2, val3, val4, tick; //Remaining duration. +}; + +struct storage_data { + int storage_amount; + struct item items[MAX_STORAGE]; +}; + +struct guild_storage { + int dirty; + int guild_id; + short storage_status; + short storage_amount; + struct item items[MAX_GUILD_STORAGE]; + unsigned short lock; +}; + +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class_; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[NAME_LENGTH]; + char rename_flag; + char incuvate; +}; + +struct s_homunculus { //[orn] + char name[NAME_LENGTH]; + int hom_id; + int char_id; + short class_; + short prev_class; + int hp,max_hp,sp,max_sp; + unsigned int intimacy; //[orn] + short hunger; + struct s_skill hskill[MAX_HOMUNSKILL]; //albator + short skillpts; + short level; + unsigned int exp; + short rename_flag; + short vaporize; //albator + int str ; + int agi ; + int vit ; + int int_ ; + int dex ; + int luk ; + + char spiritball; //for homun S [lighta] +}; + +struct s_mercenary { + int mercenary_id; + int char_id; + short class_; + int hp, sp; + unsigned int kill_count; + unsigned int life_time; +}; + +struct s_elemental { + int elemental_id; + int char_id; + short class_; + int mode; + int hp, sp, max_hp, max_sp, matk, atk, atk2; + short hit, flee, amotion, def, mdef; + int life_time; +}; + +struct s_friend { + int account_id; + int char_id; + char name[NAME_LENGTH]; +}; + +#ifdef HOTKEY_SAVING +struct hotkey { + unsigned int id; + unsigned short lv; + unsigned char type; // 0: item, 1: skill +}; +#endif + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + int father; + int mother; + int child; + + unsigned int base_exp,job_exp; + int zeny; + + short class_; + unsigned int status_point,skill_point; + int hp,max_hp,sp,max_sp; + unsigned int option; + short manner; + unsigned char karma; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id,hom_id,mer_id,ele_id; + int fame; + + // Mercenary Guilds Rank + int arch_faith, arch_calls; + int spear_faith, spear_calls; + int sword_faith, sword_calls; + + short weapon; // enum weapon_type + short shield; // view-id + short head_top,head_mid,head_bottom; + short robe; + + char name[NAME_LENGTH]; + unsigned int base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char slot,sex; + + uint32 mapip; + uint16 mapport; + + struct point last_point,save_point,memo_point[MAX_MEMOPOINTS]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct storage_data storage; + struct s_skill skill[MAX_SKILL]; + + struct s_friend friends[MAX_FRIENDS]; //New friend system [Skotlex] +#ifdef HOTKEY_SAVING + struct hotkey hotkeys[MAX_HOTKEYS]; +#endif + bool show_equip; + short rename; + + time_t delete_date; +}; + +typedef enum mail_status { + MAIL_NEW, + MAIL_UNREAD, + MAIL_READ, +} mail_status; + +struct mail_message { + int id; + int send_id; + char send_name[NAME_LENGTH]; + int dest_id; + char dest_name[NAME_LENGTH]; + char title[MAIL_TITLE_LENGTH]; + char body[MAIL_BODY_LENGTH]; + + mail_status status; + time_t timestamp; // marks when the message was sent + + int zeny; + struct item item; +}; + +struct mail_data { + short amount; + bool full; + short unchecked, unread; + struct mail_message msg[MAIL_MAX_INBOX]; +}; + +struct auction_data { + unsigned int auction_id; + int seller_id; + char seller_name[NAME_LENGTH]; + int buyer_id; + char buyer_name[NAME_LENGTH]; + + struct item item; + // This data is required for searching, as itemdb is not read by char server + char item_name[ITEM_NAME_LENGTH]; + short type; + + unsigned short hours; + int price, buynow; + time_t timestamp; // auction's end time + int auction_end_timer; +}; + +struct registry { + int global_num; + struct global_reg global[GLOBAL_REG_NUM]; + int account_num; + struct global_reg account[ACCOUNT_REG_NUM]; + int account2_num; + struct global_reg account2[ACCOUNT_REG2_NUM]; +}; + +struct party_member { + int account_id; + int char_id; + char name[NAME_LENGTH]; + unsigned short class_; + unsigned short map; + unsigned short lv; + unsigned leader : 1, + online : 1; +}; + +struct party { + int party_id; + char name[NAME_LENGTH]; + unsigned char count; //Count of online characters. + unsigned exp : 1, + item : 2; //&1: Party-Share (round-robin), &2: pickup style: shared. + struct party_member member[MAX_PARTY]; +}; + +struct map_session_data; +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class_,lv; + uint64 exp; + int exp_payper; + short online,position; + char name[NAME_LENGTH]; + struct map_session_data *sd; + unsigned char modified; +}; + +struct guild_position { + char name[NAME_LENGTH]; + int mode; + int exp_mode; + unsigned char modified; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[NAME_LENGTH]; +}; + +struct guild_expulsion { + char name[NAME_LENGTH]; + char mes[40]; + int account_id; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + uint64 exp; + unsigned int next_exp; + int skill_point; + char name[NAME_LENGTH],master[NAME_LENGTH]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[MAX_GUILDMES1],mes2[MAX_GUILDMES2]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_expulsion expulsion[MAX_GUILDEXPULSION]; + struct guild_skill skill[MAX_GUILDSKILL]; + + unsigned short save_flag; // for TXT saving +}; + +struct guild_castle { + int castle_id; + int mapindex; + char castle_name[NAME_LENGTH]; + char castle_event[NAME_LENGTH]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + struct { + unsigned visible : 1; + int id; // object id + } guardian[MAX_GUARDIANS]; + int* temp_guardians; // ids of temporary guardians (mobs) + int temp_guardians_max; +}; + +struct fame_list { + int id; + int fame; + char name[NAME_LENGTH]; +}; + +enum { //Change Guild Infos + GBI_EXP =1, // Guild Experience (EXP) + GBI_GUILDLV, // Guild level + GBI_SKILLPOINT, // Guild skillpoints + GBI_SKILLLV, // Guild skill_lv ?? seem unused +}; + +enum { //Change Member Infos + GMI_POSITION =0, + GMI_EXP, + GMI_HAIR, + GMI_HAIR_COLOR, + GMI_GENDER, + GMI_CLASS, + GMI_LEVEL, +}; + +enum { + GD_SKILLBASE=10000, + GD_APPROVAL=10000, + GD_KAFRACONTRACT=10001, + GD_GUARDRESEARCH=10002, + GD_GUARDUP=10003, + GD_EXTENSION=10004, + GD_GLORYGUILD=10005, + GD_LEADERSHIP=10006, + GD_GLORYWOUNDS=10007, + GD_SOULCOLD=10008, + GD_HAWKEYES=10009, + GD_BATTLEORDER=10010, + GD_REGENERATION=10011, + GD_RESTORE=10012, + GD_EMERGENCYCALL=10013, + GD_DEVELOPMENT=10014, + GD_MAX, +}; + + +//These mark the ID of the jobs, as expected by the client. [Skotlex] +enum { + JOB_NOVICE, + JOB_SWORDMAN, + JOB_MAGE, + JOB_ARCHER, + JOB_ACOLYTE, + JOB_MERCHANT, + JOB_THIEF, + JOB_KNIGHT, + JOB_PRIEST, + JOB_WIZARD, + JOB_BLACKSMITH, + JOB_HUNTER, + JOB_ASSASSIN, + JOB_KNIGHT2, + JOB_CRUSADER, + JOB_MONK, + JOB_SAGE, + JOB_ROGUE, + JOB_ALCHEMIST, + JOB_BARD, + JOB_DANCER, + JOB_CRUSADER2, + JOB_WEDDING, + JOB_SUPER_NOVICE, + JOB_GUNSLINGER, + JOB_NINJA, + JOB_XMAS, + JOB_SUMMER, + JOB_MAX_BASIC, + + JOB_NOVICE_HIGH = 4001, + JOB_SWORDMAN_HIGH, + JOB_MAGE_HIGH, + JOB_ARCHER_HIGH, + JOB_ACOLYTE_HIGH, + JOB_MERCHANT_HIGH, + JOB_THIEF_HIGH, + JOB_LORD_KNIGHT, + JOB_HIGH_PRIEST, + JOB_HIGH_WIZARD, + JOB_WHITESMITH, + JOB_SNIPER, + JOB_ASSASSIN_CROSS, + JOB_LORD_KNIGHT2, + JOB_PALADIN, + JOB_CHAMPION, + JOB_PROFESSOR, + JOB_STALKER, + JOB_CREATOR, + JOB_CLOWN, + JOB_GYPSY, + JOB_PALADIN2, + + JOB_BABY, + JOB_BABY_SWORDMAN, + JOB_BABY_MAGE, + JOB_BABY_ARCHER, + JOB_BABY_ACOLYTE, + JOB_BABY_MERCHANT, + JOB_BABY_THIEF, + JOB_BABY_KNIGHT, + JOB_BABY_PRIEST, + JOB_BABY_WIZARD, + JOB_BABY_BLACKSMITH, + JOB_BABY_HUNTER, + JOB_BABY_ASSASSIN, + JOB_BABY_KNIGHT2, + JOB_BABY_CRUSADER, + JOB_BABY_MONK, + JOB_BABY_SAGE, + JOB_BABY_ROGUE, + JOB_BABY_ALCHEMIST, + JOB_BABY_BARD, + JOB_BABY_DANCER, + JOB_BABY_CRUSADER2, + JOB_SUPER_BABY, + + JOB_TAEKWON, + JOB_STAR_GLADIATOR, + JOB_STAR_GLADIATOR2, + JOB_SOUL_LINKER, + + JOB_GANGSI, + JOB_DEATH_KNIGHT, + JOB_DARK_COLLECTOR, + + JOB_RUNE_KNIGHT = 4054, + JOB_WARLOCK, + JOB_RANGER, + JOB_ARCH_BISHOP, + JOB_MECHANIC, + JOB_GUILLOTINE_CROSS, + + JOB_RUNE_KNIGHT_T, + JOB_WARLOCK_T, + JOB_RANGER_T, + JOB_ARCH_BISHOP_T, + JOB_MECHANIC_T, + JOB_GUILLOTINE_CROSS_T, + + JOB_ROYAL_GUARD, + JOB_SORCERER, + JOB_MINSTREL, + JOB_WANDERER, + JOB_SURA, + JOB_GENETIC, + JOB_SHADOW_CHASER, + + JOB_ROYAL_GUARD_T, + JOB_SORCERER_T, + JOB_MINSTREL_T, + JOB_WANDERER_T, + JOB_SURA_T, + JOB_GENETIC_T, + JOB_SHADOW_CHASER_T, + + JOB_RUNE_KNIGHT2, + JOB_RUNE_KNIGHT_T2, + JOB_ROYAL_GUARD2, + JOB_ROYAL_GUARD_T2, + JOB_RANGER2, + JOB_RANGER_T2, + JOB_MECHANIC2, + JOB_MECHANIC_T2, + + JOB_BABY_RUNE = 4096, + JOB_BABY_WARLOCK, + JOB_BABY_RANGER, + JOB_BABY_BISHOP, + JOB_BABY_MECHANIC, + JOB_BABY_CROSS, + + JOB_BABY_GUARD, + JOB_BABY_SORCERER, + JOB_BABY_MINSTREL, + JOB_BABY_WANDERER, + JOB_BABY_SURA, + JOB_BABY_GENETIC, + JOB_BABY_CHASER, + + JOB_BABY_RUNE2, + JOB_BABY_GUARD2, + JOB_BABY_RANGER2, + JOB_BABY_MECHANIC2, + + JOB_SUPER_NOVICE_E = 4190, + JOB_SUPER_BABY_E, + + JOB_KAGEROU = 4211, + JOB_OBORO, + + JOB_MAX, +}; + +enum { + SEX_FEMALE = 0, + SEX_MALE, + SEX_SERVER +}; + +// sanity checks... +#if MAX_ZENY > INT_MAX +#error MAX_ZENY is too big +#endif + +#endif /* _MMO_H_ */ diff --git a/src/common/mutex.c b/src/common/mutex.c new file mode 100644 index 000000000..6b4f55119 --- /dev/null +++ b/src/common/mutex.c @@ -0,0 +1,247 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifdef WIN32 +#include "../common/winapi.h" +#else +#include <pthread.h> +#include <time.h> +#include <sys/time.h> +#endif + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/mutex.h" + +struct ramutex{ +#ifdef WIN32 + CRITICAL_SECTION hMutex; +#else + pthread_mutex_t hMutex; +#endif +}; + + +struct racond{ +#ifdef WIN32 + HANDLE events[2]; + ra_align(8) volatile LONG nWaiters; + CRITICAL_SECTION waiters_lock; + +#define EVENT_COND_SIGNAL 0 +#define EVENT_COND_BROADCAST 1 + +#else + pthread_cond_t hCond; +#endif +}; + + +//////////////////// +// Mutex +// +// Implementation: +// + + +ramutex ramutex_create(){ + struct ramutex *m; + + m = (struct ramutex*)aMalloc( sizeof(struct ramutex) ); + if(m == NULL){ + ShowFatalError("ramutex_create: OOM while allocating %u bytes.\n", sizeof(struct ramutex)); + return NULL; + } + +#ifdef WIN32 + InitializeCriticalSection(&m->hMutex); +#else + pthread_mutex_init(&m->hMutex, NULL); +#endif + + return m; +}//end: ramutex_create() + + +void ramutex_destroy( ramutex m ){ + +#ifdef WIN32 + DeleteCriticalSection(&m->hMutex); +#else + pthread_mutex_destroy(&m->hMutex); +#endif + + aFree(m); + +}//end: ramutex_destroy() + + +void ramutex_lock( ramutex m ){ + +#ifdef WIN32 + EnterCriticalSection(&m->hMutex); +#else + pthread_mutex_lock(&m->hMutex); +#endif +}//end: ramutex_lock + + +bool ramutex_trylock( ramutex m ){ +#ifdef WIN32 + if(TryEnterCriticalSection(&m->hMutex) == TRUE) + return true; + + return false; +#else + if(pthread_mutex_trylock(&m->hMutex) == 0) + return true; + + return false; +#endif +}//end: ramutex_trylock() + + +void ramutex_unlock( ramutex m ){ +#ifdef WIN32 + LeaveCriticalSection(&m->hMutex); +#else + pthread_mutex_unlock(&m->hMutex); +#endif + +}//end: ramutex_unlock() + + + +/////////////// +// Condition Variables +// +// Implementation: +// + +racond racond_create(){ + struct racond *c; + + c = (struct racond*)aMalloc( sizeof(struct racond) ); + if(c == NULL){ + ShowFatalError("racond_create: OOM while allocating %u bytes\n", sizeof(struct racond)); + return NULL; + } + +#ifdef WIN32 + c->nWaiters = 0; + c->events[ EVENT_COND_SIGNAL ] = CreateEvent( NULL, FALSE, FALSE, NULL ); + c->events[ EVENT_COND_BROADCAST ] = CreateEvent( NULL, TRUE, FALSE, NULL ); + InitializeCriticalSection( &c->waiters_lock ); +#else + pthread_cond_init(&c->hCond, NULL); +#endif + + return c; +}//end: racond_create() + + +void racond_destroy( racond c ){ +#ifdef WIN32 + CloseHandle( c->events[ EVENT_COND_SIGNAL ] ); + CloseHandle( c->events[ EVENT_COND_BROADCAST ] ); + DeleteCriticalSection( &c->waiters_lock ); +#else + pthread_cond_destroy(&c->hCond); +#endif + + aFree(c); +}//end: racond_destroy() + + +void racond_wait( racond c, ramutex m, sysint timeout_ticks){ +#ifdef WIN32 + register DWORD ms; + int result; + bool is_last = false; + + + EnterCriticalSection(&c->waiters_lock); + c->nWaiters++; + LeaveCriticalSection(&c->waiters_lock); + + if(timeout_ticks < 0) + ms = INFINITE; + else + ms = (timeout_ticks > MAXDWORD) ? (MAXDWORD - 1) : (DWORD)timeout_ticks; + + + // we can release the mutex (m) here, cause win's + // manual reset events maintain state when used with + // SetEvent() + ramutex_unlock(m); + + result = WaitForMultipleObjects(2, c->events, FALSE, ms); + + + EnterCriticalSection(&c->waiters_lock); + c->nWaiters--; + if( (result == WAIT_OBJECT_0 + EVENT_COND_BROADCAST) && (c->nWaiters == 0) ) + is_last = true; // Broadcast called! + LeaveCriticalSection(&c->waiters_lock); + + + + // we are the last waiter that has to be notified, or to stop waiting + // so we have to do a manual reset + if(is_last == true) + ResetEvent( c->events[EVENT_COND_BROADCAST] ); + + + ramutex_lock(m); + +#else + if(timeout_ticks < 0){ + pthread_cond_wait( &c->hCond, &m->hMutex ); + }else{ + struct timespec wtime; + int64 exact_timeout = gettick() + timeout_ticks; + + wtime.tv_sec = exact_timeout/1000; + wtime.tv_nsec = (exact_timeout%1000)*1000000; + + pthread_cond_timedwait( &c->hCond, &m->hMutex, &wtime); + } + +#endif +}//end: racond_wait() + + +void racond_signal( racond c ){ +#ifdef WIN32 +// bool has_waiters = false; +// EnterCriticalSection(&c->waiters_lock); +// if(c->nWaiters > 0) +// has_waiters = true; +// LeaveCriticalSection(&c->waiters_lock); + +// if(has_waiters == true) + SetEvent( c->events[ EVENT_COND_SIGNAL ] ); +#else + pthread_cond_signal(&c->hCond); +#endif +}//end: racond_signal() + + +void racond_broadcast( racond c ){ +#ifdef WIN32 +// bool has_waiters = false; +// EnterCriticalSection(&c->waiters_lock); +// if(c->nWaiters > 0) +// has_waiters = true; +// LeaveCriticalSection(&c->waiters_lock); + +// if(has_waiters == true) + SetEvent( c->events[ EVENT_COND_BROADCAST ] ); +#else + pthread_cond_broadcast(&c->hCond); +#endif +}//end: racond_broadcast() + + diff --git a/src/common/mutex.h b/src/common/mutex.h new file mode 100644 index 000000000..1999627cd --- /dev/null +++ b/src/common/mutex.h @@ -0,0 +1,92 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_MUTEX_H_ +#define _rA_MUTEX_H_ + + +typedef struct ramutex *ramutex; // Mutex +typedef struct racond *racond; // Condition Var + +/** + * Creates a Mutex + * + * @return not NULL + */ +ramutex ramutex_create(); + +/** + * Destroys a Mutex + * + * @param m - the mutex to destroy + */ +void ramutex_destroy( ramutex m ); + +/** + * Gets a lock + * + * @param m - the mutex to lock + */ +void ramutex_lock( ramutex m); + +/** + * Trys to get the Lock + * + * @param m - the mutex try to lock + * + * @return boolean (true = got the lock) + */ +bool ramutex_trylock( ramutex m ); + +/** + * Unlocks a mutex + * + * @param m - the mutex to unlock + */ +void ramutex_unlock( ramutex m); + + +/** + * Creates a Condition variable + * + * @return not NULL + */ +racond racond_create(); + +/** + * Destroy a Condition variable + * + * @param c - the condition varaible to destroy + */ +void racond_destroy( racond c ); + +/** + * Waits Until state is signalled + * + * @param c - the condition var to wait for signalled state + * @param m - the mutex used for syncronization + * @param timeout_ticks - timeout in ticks ( -1 = INFINITE ) + */ +void racond_wait( racond c, ramutex m, sysint timeout_ticks); + +/** + * Sets the given condition var to signalled state + * + * @param c - condition var to set in signalled state. + * + * @note: + * Only one waiter gets notified. + */ +void racond_signal( racond c ); + +/** + * Sets notifys all waiting threads thats signalled. + * @param c - condition var to set in signalled state + * + * @note: + * All Waiters getting notified. + */ +void racond_broadcast( racond c ); + + +#endif diff --git a/src/common/netbuffer.c b/src/common/netbuffer.c new file mode 100644 index 000000000..57742d612 --- /dev/null +++ b/src/common/netbuffer.c @@ -0,0 +1,221 @@ + +// +// Network Buffer Subsystem (iobuffer) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "../common/cbasetypes.h" +#include "../common/atomic.h" +#include "../common/mempool.h" +#include "../common/showmsg.h" +#include "../common/raconf.h" +#include "../common/thread.h" +#include "../common/malloc.h" +#include "../common/core.h" + +#include "../common/netbuffer.h" + + +// +// Buffers are available in the following sizes: +// 48, 192, 2048, 8192 +// 65536 (inter server connects may use it for charstatus struct..) +// + + +/// +// Implementation: +// +static volatile int32 l_nEmergencyAllocations = 0; // stats. +static sysint l_nPools = 0; +static sysint *l_poolElemSize = NULL; +static mempool *l_pool = NULL; + + +void netbuffer_init(){ + char localsection[32]; + raconf conf; + sysint i; + + // Initialize Statistic counters: + l_nEmergencyAllocations = 0; + + // Set localsection name according to running serverype. + switch(SERVER_TYPE){ + case ATHENA_SERVER_LOGIN: strcpy(localsection, "login-netbuffer"); break; + case ATHENA_SERVER_CHAR: strcpy(localsection, "char-netbuffer"); break; + case ATHENA_SERVER_INTER: strcpy(localsection, "inter-netbuffer"); break; + case ATHENA_SERVER_MAP: strcpy(localsection, "map-netbuffer"); break; + default: strcpy(localsection, "unsupported_type"); break; + } + + + conf = raconf_parse("conf/network.conf"); + if(conf == NULL){ + ShowFatalError("Failed to Parse required Configuration (conf/network.conf)"); + exit(EXIT_FAILURE); + } + + // Get Values from config file + l_nPools = (sysint)raconf_getintEx(conf, localsection, "netbuffer", "num", 0); + if(l_nPools == 0){ + ShowFatalError("Netbuffer (network.conf) failure - requires at least 1 Pool.\n"); + exit(EXIT_FAILURE); + } + + // Allocate arrays. + l_poolElemSize = (sysint*)aCalloc( l_nPools, sizeof(sysint) ); + l_pool = (mempool*)aCalloc( l_nPools, sizeof(mempool) ); + + + for(i = 0; i < l_nPools; i++){ + int64 num_prealloc, num_realloc; + char key[32]; + + sprintf(key, "pool_%u_size", (uint32)i+1); + l_poolElemSize[i] = (sysint)raconf_getintEx(conf, localsection, "netbuffer", key, 4096); + if(l_poolElemSize[i] < 32){ + ShowWarning("Netbuffer (network.conf) failure - minimum allowed buffer size is 32 byte) - fixed.\n"); + l_poolElemSize[i] = 32; + } + + sprintf(key, "pool_%u_prealloc", (uint32)i+1); + num_prealloc = raconf_getintEx(conf, localsection, "netbuffer", key, 150); + + sprintf(key, "pool_%u_realloc_step", (uint32)i+1); + num_realloc = raconf_getintEx(conf, localsection, "netbuffer", key, 100); + + // Create Pool! + sprintf(key, "Netbuffer %u", (uint32)l_poolElemSize[i]); // name. + + // Info + ShowInfo("NetBuffer: Creating Pool %u (Prealloc: %u, Realloc Step: %u) - %0.2f MiB\n", l_poolElemSize[i], num_prealloc, num_realloc, (float)((sizeof(struct netbuf) + l_poolElemSize[i] - 32)* num_prealloc)/1024.0f/1024.0f); + + // + // Size Calculation: + // struct netbuf + requested buffer size - 32 (because the struct already contains 32 byte buffer space at the end of struct) + l_pool[i] = mempool_create(key, (sizeof(struct netbuf) + l_poolElemSize[i] - 32), num_prealloc, num_realloc, NULL, NULL); + if(l_pool[i] == NULL){ + ShowFatalError("Netbuffer: cannot create Pool for %u byte buffers.\n", l_poolElemSize[i]); + // @leak: clean everything :D + exit(EXIT_FAILURE); + } + + }// + + + raconf_destroy(conf); + +}//end: netbuffer_init() + + +void netbuffer_final(){ + sysint i; + + if(l_nPools > 0){ + /// .. finalize mempools + for(i = 0; i < l_nPools; i++){ + mempool_stats stats = mempool_get_stats(l_pool[i]); + + ShowInfo("Netbuffer: Freeing Pool %u (Peak Usage: %u, Realloc Events: %u)\n", l_poolElemSize[i], stats.peak_nodes_used, stats.num_realloc_events); + + mempool_destroy(l_pool[i]); + } + + if(l_nEmergencyAllocations > 0){ + ShowWarning("Netbuffer: did %u Emergency Allocations, please tune your network.conf!\n", l_nEmergencyAllocations); + l_nEmergencyAllocations = 0; + } + + aFree(l_poolElemSize); l_poolElemSize = NULL; + aFree(l_pool); l_pool = NULL; + l_nPools = 0; + } + + +}//end: netbuffer_final() + + +netbuf netbuffer_get( sysint sz ){ + sysint i; + netbuf nb = NULL; + + // Search an appropriate pool + for(i = 0; i < l_nPools; i++){ + if(sz <= l_poolElemSize[i]){ + // match + + nb = (netbuf)mempool_node_get(l_pool[i]); + nb->pool = i; + + break; + } + } + + // No Bufferpool found that mets there quirements?.. (thats bad..) + if(nb == NULL){ + ShowWarning("Netbuffer: get(%u): => no appropriate pool found - emergency allocation required.\n", sz); + ShowWarning("Please reconfigure your network.conf!"); + + InterlockedIncrement(&l_nEmergencyAllocations); + + // .. better to check (netbuf struct provides 32 byte bufferspace itself. + if(sz < 32) sz = 32; + + // allocate memory using malloc .. + while(1){ + nb = (netbuf) aMalloc( (sizeof(struct netbuf) + sz - 32) ); + if(nb != NULL){ + memset(nb, 0x00, (sizeof(struct netbuf) + sz - 32) ); // zero memory! (to enforce commit @ os.) + nb->pool = -1; // emergency alloc. + break; + } + + rathread_yield(); + }// spin allocation. + + } + + + nb->refcnt = 1; // Initial refcount is 1 + + return nb; +}//end: netbuffer_get() + + +void netbuffer_put( netbuf nb ){ + + // Decrement reference counter, if > 0 do nothing :) + if( InterlockedDecrement(&nb->refcnt) > 0 ) + return; + + // Is this buffer an emergency allocated buffer? + if(nb->pool == -1){ + aFree(nb); + return; + } + + + // Otherwise its a normal mempool based buffer + // return it to the according mempool: + mempool_node_put( l_pool[nb->pool], nb); + + +}//end: netbuffer_put() + + +void netbuffer_incref( netbuf nb ){ + + InterlockedIncrement(&nb->refcnt); + +}//end: netbuf_incref() diff --git a/src/common/netbuffer.h b/src/common/netbuffer.h new file mode 100644 index 000000000..844241226 --- /dev/null +++ b/src/common/netbuffer.h @@ -0,0 +1,83 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_NETBUFFER_H_ +#define _rA_NETBUFFER_H_ + +#include "../common/cbasetypes.h" + +typedef struct netbuf{ + sysint pool; // The pool ID this buffer belongs to, + // is set to -1 if its an emergency allocated buffer + + struct netbuf *next; // Used by Network system. + + volatile int32 refcnt; // Internal Refcount, it gets lowered every call to netbuffer_put, + // if its getting zero, the buffer will returned back to the pool + // and can be reused. + + int32 dataPos; // Current Offset + // Used only for Reading (recv job) + // write cases are using the sessions local datapos member due to + // shared write buffer support. + + int32 dataLen; // read buffer case: + // The length expected to read to. + // when this->dataPos == dateLen, read job has been completed. + // write buffer case: + // The lngth of data in te buffer + // when s->dataPos == dataLen, write job has been completed + // + // Note: + // leftBytes = (dateLen - dataPos) + // + // Due to shared buffer support + // dataPos gets not used in write case (each connection has its local offset) + // + + // The Bufferspace itself. + char buf[32]; +} *netbuf; + + +void netbuffer_init(); +void netbuffer_final(); + +/** + * Gets a netbuffer that has atleast (sz) byes space. + * + * @note: The netbuffer system guarantees that youll always recevie a buffer. + * no check for null is required! + * + * @param sz - minimum size needed. + * + * @return pointer to netbuf struct + */ +netbuf netbuffer_get( sysint sz ); + + +/** + * Returns the given netbuffer (decreases refcount, if its 0 - the buffer will get returned to the pool) + * + * @param buf - the buffer to return + */ +void netbuffer_put( netbuf buf ); + + +/** + * Increases the Refcount on the given buffer + * (used for areasends .. etc) + * + */ +void netbuffer_incref( netbuf buf ); + + +// Some Useful macros +#define NBUFP(netbuf,pos) (((uint8*)(netbuf->buf)) + (pos)) +#define NBUFB(netbuf,pos) (*(uint8*)((netbuf->buf) + (pos))) +#define NBUFW(netbuf,pos) (*(uint16*)((netbuf->buf) + (pos))) +#define NBUFL(netbuf,pos) (*(uint32*)((netbuf->buf) + (pos))) + + + +#endif diff --git a/src/common/network.c b/src/common/network.c new file mode 100644 index 000000000..1f1621363 --- /dev/null +++ b/src/common/network.c @@ -0,0 +1,1061 @@ +// +// Network Subsystem (previously known as socket system) +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// +//#ifdef HAVE_ACCETP4 +#define _GNU_SOURCE +//#endif + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/evdp.h" +#include "../common/netbuffer.h" + +#include "../common/network.h" + +#define ENABLE_IPV6 +#define HAVE_ACCEPT4 +#define EVENTS_PER_CYCLE 10 +#define PARANOID_CHECKS + +// Local Vars (settings..) +static int l_ListenBacklog = 64; + +// +// Global Session Array (previously exported as session[] +// +SESSION g_Session[MAXCONN]; + + +// +static bool onSend(int32 fd); + + +#define _network_free_netbuf_async( buf ) add_timer( 0, _network_async_free_netbuf_proc, 0, (intptr_t) buf) +static int _network_async_free_netbuf_proc(int tid, unsigned int tick, int id, intptr_t data){ + // netbuf is in data + netbuffer_put( (netbuf)data ); + + return 0; +}//end: _network_async_free_netbuf_proc() + + + +void network_init(){ + SESSION *s; + int32 i; + + memset(g_Session, 0x00, (sizeof(SESSION) * MAXCONN) ); + + for(i = 0; i < MAXCONN; i++){ + s = &g_Session[i]; + + s->type = NST_FREE; + s->disconnect_in_progress = false; + + } + + // Initialize the correspondig event dispatcher + evdp_init(); + + // + add_timer_func_list(_network_async_free_netbuf_proc, "_network_async_free_netbuf_proc"); + +}//end: network_init() + + +void network_final(){ + + // @TODO: + // .. disconnect and cleanup everything! + + evdp_final(); + +}//end: network_final() + + +void network_do(){ + struct EVDP_EVENT l_events[EVENTS_PER_CYCLE]; + register struct EVDP_EVENT *ev; + register int n, nfds; + register SESSION *s; + + nfds = evdp_wait( l_events, EVENTS_PER_CYCLE, 1000); // @TODO: timer_getnext() + + for(n = 0; n < nfds; n++){ + ev = &l_events[n]; + s = &g_Session[ ev->fd ]; + + if(ev->events & EVDP_EVENT_HUP){ + network_disconnect( ev->fd ); + continue; // no further event processing. + }// endif vent is HUP (disconnect) + + + if(ev->events & EVDP_EVENT_IN){ + + if(s->onRecv != NULL){ + if( false == s->onRecv(ev->fd) ){ + network_disconnect(ev->fd); + continue; // .. + } + }else{ + ShowError("network_do: fd #%u has no onRecv proc set. - disconnecting\n", ev->fd); + network_disconnect(ev->fd); + continue; + } + + }// endif event is IN (recv) + + + if(ev->events & EVDP_EVENT_OUT){ + if(s->onSend != NULL){ + if( false == s->onSend(ev->fd) ){ + network_disconnect(ev->fd); + continue; + } + }else{ + ShowError("network_do: fd #%u has no onSend proc set. - disconnecting\n", ev->fd); + network_disconnect(ev->fd); + continue; + } + }// endif event is OUT (send) + + }//endfor + +}//end: network_do() + + +static bool _setnonblock(int32 fd){ + int flags = fcntl(fd, F_GETFL, 0); + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) + return false; + + return true; +}//end: _setnonblock() + + +static bool _network_accept(int32 fd){ + SESSION *listener = &g_Session[fd]; + SESSION *s; + union{ + struct sockaddr_in v4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 v6; +#endif + } _addr; + int newfd; + socklen_t addrlen; + struct sockaddr *addr; + + // Accept until OS returns - nothing to accept anymore + // - this is required due to our EVDP abstraction. (which handles on listening sockets similar to epoll's EPOLLET flag.) + while(1){ +#ifdef ENABLE_IPV6 + if(listener->v6 == true){ + addrlen = sizeof(_addr.v6); + addr = (struct sockaddr*)&_addr.v6; + }else{ +#endif + addrlen = sizeof(_addr.v4); + addr = (struct sockaddr*)&_addr.v4; +#ifdef ENABLE_IPV6 + } +#endif + +#ifdef HAVE_ACCEPT4 + newfd = accept4(fd, addr, &addrlen, SOCK_NONBLOCK); +#else + newfd = accept(fd, addr, &addrlen); +#endif + + if(newfd == -1){ + if(errno == EAGAIN || errno == EWOULDBLOCK) + break; // this is fully valid & whished., se explaination on top of while(1) + + // Otherwis .. we have serious problems :( seems tahat our listner has gone away.. + // @TODO handle this .. + ShowError("_network_accept: accept() returned error. closing listener. (errno: %u / %s)\n", errno, strerror(errno)); + + return false; // will call disconnect after return. + //break; + } + +#ifndef HAVE_ACCEPT4 // no accept4 means, we have to set nonblock by ourself. .. + if(_setnonblock(newfd) == false){ + ShowError("_network_accept: failed to set newly accepted connection nonblocking (errno: %u / %s). - disconnecting.\n", errno, strerror(errno)); + close(newfd); + continue; + } +#endif + + // Check connection limits. + if(newfd >= MAXCONN){ + ShowError("_network_accept: failed to accept connection - MAXCONN (%u) exceeded.\n", MAXCONN); + close(newfd); + continue; // we have to loop over the events (and disconnect them too ..) but otherwise we would leak event notifications. + } + + + // Create new Session. + s = &g_Session[newfd]; + s->type = NST_CLIENT; + + // The new connection inherits listenr's handlers. + s->onDisconnect = listener->onDisconnect; + s->onConnect = listener->onConnect; // maybe useless but .. fear the future .. :~ + + // Register the new connection @ EVDP + if( evdp_addclient(newfd, &s->evdp_data) == false){ + ShowError("_network_accept: failed to accept connection - event subsystem returned an error.\n"); + close(newfd); + s->type = NST_FREE; + } + + // Call the onConnect handler on the listener. + if( listener->onConnect(newfd) == false ){ + // Resfused by onConnect handler.. + evdp_remove(newfd, &s->evdp_data); + + close(newfd); + s->type = NST_FREE; + + s->data = NULL; // be on the safe side ~ ! + continue; + } + + + } + + return true; +}//end: _network_accept() + + +void network_disconnect(int32 fd){ + SESSION *s = &g_Session[fd]; + netbuf b, bn; + + // Prevent recursive calls + // by wrong implemented on disconnect handlers.. and such.. + if(s->disconnect_in_progress == true) + return; + + s->disconnect_in_progress = true; + + + // Disconnect Todo: + // - Call onDisconnect Handler + // - Release all Assigned buffers. + // - remove from event system (notifications) + // - cleanup session structure + // - close connection. + // + + if(s->onDisconnect != NULL && + s->type != NST_LISTENER){ + + s->onDisconnect( fd ); + } + + // Read Buffer + if(s->read.buf != NULL){ + netbuffer_put(s->read.buf); + s->read.buf = NULL; + } + + // Write Buffer(s) + b = s->write.buf; + while(1){ + if(b == NULL) break; + + bn = b->next; + + netbuffer_put(b); + + b = bn; + } + s->write.buf = NULL; + s->write.buf_last = NULL; + + s->write.n_outstanding = 0; + s->write.max_outstanding = 0; + + + // Remove from event system. + evdp_remove(fd, &s->evdp_data); + + // Cleanup Session Structure. + s->type = NST_FREE; + s->data = NULL; // no application level data assigned + s->disconnect_in_progress = false; + + + // Close connection + close(fd); + +}//end: network_disconnect() + + +int32 network_addlistener(bool v6, const char *addr, uint16 port){ + SESSION *s; + int optval, fd; + +#if !defined(ENABLE_IPV6) + if(v6 == true){ + ShowError("network_addlistener(%c, '%s', %u): this release has no IPV6 support.\n", (v6==true?'t':'f'), addr, port); + return -1; + } +#endif + + +#ifdef ENABLE_IPV6 + if(v6 == true) + fd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + fd = socket(AF_INET, SOCK_STREAM, 0); + + // Error? + if(fd == -1){ + ShowError("network_addlistener(%c, '%s', %u): socket() failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + return -1; + } + + // Too many connections? + if(fd >= MAXCONN){ + ShowError("network_addlistener(%c, '%s', %u): cannot create listener, exceeds more than supported connections (%u).\n", (v6==true?'t':'f'), addr, port, MAXCONN); + close(fd); + return -1; + } + + + s = &g_Session[fd]; + if(s->type != NST_FREE){ // additional checks.. :) + ShowError("network_addlistener(%c, '%s', %u): failed, got fd #%u which is already in use in local session table?!\n", (v6==true?'t':'f'), addr, port, fd); + close(fd); + return -1; + } + + + // Fill ip addr structs +#ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&s->addr.v6, 0x00, sizeof(s->addr.v6)); + s->addr.v6.sin6_family = AF_INET6; + s->addr.v6.sin6_port = htons(port); + if(inet_pton(AF_INET6, addr, &s->addr.v6.sin6_addr) != 1){ + ShowError("network_addlistener(%c, '%s', %u): failed to parse the given IPV6 address.\n", (v6==true?'t':'f'), addr, port); + close(fd); + return -1; + } + + }else{ +#endif + memset(&s->addr.v4, 0x00, sizeof(s->addr.v4)); + s->addr.v4.sin_family = AF_INET; + s->addr.v4.sin_port = htons(port); + s->addr.v4.sin_addr.s_addr = inet_addr(addr); +#ifdef ENABLE_IPV6 + } +#endif + + + // if OS has support for SO_REUSEADDR, apply the flag + // so the address could be used when there're still time_wait sockets outstanding from previous application run. +#ifdef SO_REUSEADDR + optval=1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); +#endif + + // Bind +#ifdef ENABLE_IPV6 + if(v6 == true){ + if( bind(fd, (struct sockaddr*)&s->addr.v6, sizeof(s->addr.v6)) == -1) { + ShowError("network_addlistener(%c, '%s', %u): bind failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + }else{ +#endif + if( bind(fd, (struct sockaddr*)&s->addr.v4, sizeof(s->addr.v4)) == -1) { + ShowError("network_addlistener(%c, '%s', %u): bind failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } +#ifdef ENABLE_IPV6 + } +#endif + + if( listen(fd, l_ListenBacklog) == -1){ + ShowError("network_addlistener(%c, '%s', %u): listen failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Set to nonblock! + if(_setnonblock(fd) == false){ + ShowError("network_addlistener(%c, '%s', %u): cannot set to nonblock (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Rgister @ evdp. + if( evdp_addlistener(fd, &s->evdp_data) != true){ + ShowError("network_addlistener(%c, '%s', %u): eventdispatcher subsystem returned an error.\n", (v6==true?'t':'f'), addr, port); + close(fd); + return -1; + } + + + // Apply flags on Session array for this conneciton. + if(v6 == true) s->v6 = true; + else s->v6 = false; + + s->type = NST_LISTENER; + s->onRecv = _network_accept; + + ShowStatus("Added Listener on '%s':%u\n", addr, port, (v6==true ? "(ipv6)":"(ipv4)") ); + + return fd; +}//end: network_addlistener() + + +static bool _network_connect_establishedHandler(int32 fd){ + register SESSION *s = &g_Session[fd]; + int val; + socklen_t val_len; + + if(s->type == NST_FREE) + return true; // due to multiple non coalesced event notifications + // this can happen .. when a previous handled event has already disconnected the connection + // within the same cycle.. + + val = -1; + val_len = sizeof(val); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &val_len); + + if(val != 0){ + // :( .. cleanup session.. + s->type = NST_FREE; + s->onSend = NULL; + s->onConnect = NULL; + s->onDisconnect = NULL; + + evdp_remove(fd, &s->evdp_data); + close(fd); + + return true; // we CANT return false, + // becuase the normal disconnect procedure would execute the ondisconnect handler, which we dont want .. in this case. + }else{ + // ok + if(s->onConnect(fd) == false) { + // onConnect handler has refused the connection .. + // cleanup .. and ok + s->type = NST_FREE; + s->onSend = NULL; + s->onConnect = NULL; + s->onDisconnect = NULL; + + evdp_remove(fd, &s->evdp_data); + close(fd); + + return true; // we dnot want the ondisconnect handler to be executed, so its okay to handle this by ourself. + } + + // connection established ! + // + if( evdp_outgoingconnection_established(fd, &s->evdp_data) == false ){ + return false; // we want the normal disconnect procedure.. with call to ondisconnect handler. + } + + s->onSend = NULL; + + ShowStatus("#%u connection successfull!\n", fd); + } + + return true; +}//end: _network_connect_establishedHandler() + + +int32 network_connect(bool v6, + const char *addr, + uint16 port, + const char *from_addr, + uint16 from_port, + bool (*onConnectionEstablishedHandler)(int32 fd), + void (*onConnectionLooseHandler)(int32 fd) +){ + register SESSION *s; + int32 fd, optval, ret; + struct sockaddr_in ip4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 ip6; +#endif + +#ifdef ENABLE_IPV6 + if(v6 == true) + fd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + fd = socket(AF_INET, SOCK_STREAM, 0); + +#ifndef ENABLE_IPV6 + // check.. + if(v6 == true){ + ShowError("network_connect(%c, '%s', %u...): tried to create an ipv6 connection, IPV6 is not supported in this release.\n", (v6==true?'t':'f'), addr, port); + return -1; + } +#endif + + // check connection limits. + if(fd >= MAXCONN){ + ShowError("network_connect(%c, '%s', %u...): cannot create new connection, exceeeds more than supported connections (%u)\n", (v6==true?'t':'f'), addr, port ); + close(fd); + return -1; + } + + + // Originating IP/Port pair given ? + if(from_addr != NULL && *from_addr != 0){ + //.. + #ifdef SO_REUSEADDR + optval=1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + #endif + + #ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&ip6, 0x00, sizeof(ip6)); + ip6.sin6_family = AF_INET6; + ip6.sin6_port = htons(from_port); + + if(inet_pton(AF_INET6, from_addr, &ip6.sin6_addr) != 1){ + ShowError("network_connect(%c, '%s', %u...): cannot parse originating (from) IPV6 address (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + ret = bind(fd, (struct sockaddr*)&ip6, sizeof(ip6)); + }else{ + #endif + memset(&ip4, 0x00, sizeof(ip4)); + + ip4.sin_family = AF_INET; + ip4.sin_port = htons(from_port); + ip4.sin_addr.s_addr = inet_addr(from_addr); + ret = bind(fd, (struct sockaddr*)&ip4, sizeof(ip4)); + #ifdef ENABLE_IPV6 + } + #endif + + } + + + // Set non block + if(_setnonblock(fd) == false){ + ShowError("network_connect(%c, '%s', %u...): cannot set socket to nonblocking (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Create ip addr block to connect to .. +#ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&ip6, 0x00, sizeof(ip6)); + ip6.sin6_family = AF_INET6; + ip6.sin6_port = htons(port); + + if(inet_pton(AF_INET6, addr, &ip6.sin6_addr) != 1){ + ShowError("network_connect(%c, '%s', %u...): cannot parse destination IPV6 address (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + }else{ +#endif + memset(&ip4, 0x00, sizeof(ip4)); + + ip4.sin_family = AF_INET; + ip4.sin_port = htons(port); + ip4.sin_addr.s_addr = inet_addr(addr); +#ifdef ENABLE_IPV6 + } +#endif + + + // Assign Session.. + s = &g_Session[fd]; + s->type = NST_OUTGOING; + s->v6 = v6; + s->onConnect = onConnectionEstablishedHandler; + s->onDisconnect = onConnectionLooseHandler; + s->onRecv = NULL; + s->onSend = _network_connect_establishedHandler; +#ifdef ENABLE_IPV6 + if(v6 == true) + memcpy(&s->addr.v6, &ip6, sizeof(ip6)); + else +#endif + memcpy(&s->addr.v4, &ip4, sizeof(ip4)); + + // Register @ EVDP. as outgoing (see doc of the function) + if(evdp_addconnecting(fd, &s->evdp_data) == false){ + ShowError("network_connect(%c, '%s', %u...): eventdispatcher subsystem returned an error.\n", (v6==true?'t':'f'), addr, port); + + // cleanup session x.x.. + s->type = NST_FREE; + s->onConnect = NULL; + s->onDisconnect = NULL; + s->onSend = NULL; + + // close, return error code. + close(fd); + return -1; + } + + +#ifdef ENABLE_IPV6 + if(v6 == true) + ret = connect(fd, (struct sockaddr*)&ip6, sizeof(ip6)); + else +#endif + ret = connect(fd, (struct sockaddr*)&ip4, sizeof(ip4)); + + + // + if(ret != 0 && errno != EINPROGRESS){ + ShowWarning("network_connect(%c, '%s', %u...): connection failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + + // Cleanup session .. + s->type = NST_FREE; + s->onConnect = NULL; + s->onDisconnect = NULL; + s->onSend = NULL; + + // .. remove from evdp and close fd. + evdp_remove(fd, &s->evdp_data); + close(fd); + return -1; + } + + + // ! The Info Message :~D + ShowStatus("network_connect fd#%u (%s:%u) in progress.. \n", fd, addr, port); + +return fd; +}//end: network_connect() + + +static bool _onSend(int32 fd){ + register SESSION *s = &g_Session[fd]; + register netbuf buf, buf_next; + register uint32 szNeeded; + register int wLen; + + if(s->type == NST_FREE) + return true; // Possible due to multipl non coalsced event notifications + // so onSend gets called after disconnect caused by an previous vent. + // we can ignore the call to onSend, then. + + buf = s->write.buf; + while(1){ + if(buf == NULL) + break; + + buf_next = buf->next; + + + szNeeded = (buf->dataLen - s->write.dataPos); // using th session-local .dataPos member, due to shared write buffer support. + + // try to write. + wLen = write(fd, &buf->buf[s->write.dataPos], szNeeded); + if(wLen == 0){ + return false; // eof. + }else if(wLen == -1){ + if(errno == EAGAIN || errno == EWOULDBLOCK) + return true; // dont disconnect / try again later. + + // all other errors. . + return false; + } + + // Wrote data.. => + szNeeded -= wLen; + if(szNeeded > 0){ + // still data left .. + // + s->write.dataPos += wLen; // fix offset. + return true; + }else{ + // this buffer has been written successfully + // could be returned to pool. + netbuffer_put(buf); + s->write.n_outstanding--; // When threadsafe -> Interlocked here. + s->write.dataPos = 0; + } + + + buf = buf_next; + } + + // okay, + // reaching this part means: + // while interrupted by break - + // which means all buffers are written, nothing left + // + + s->write.buf_last = NULL; + s->write.buf = NULL; + s->write.n_outstanding = 0; + s->write.dataPos = 0; + + // Remove from event dispatcher (write notification) + // + evdp_writable_remove(fd, &s->evdp_data); + + return true; +}//end: _onSend() + + +static bool _onRORecv(int32 fd){ + register SESSION *s = &g_Session[fd]; + register uint32 szNeeded; + register char *p; + register int rLen; + + if(s->type == NST_FREE) + return true; // Possible due to multiple non coalesced events by evdp. + // simply ignore this call returning positive result. + + // Initialize p and szNeeded depending on change + // + switch(s->read.state){ + case NRS_WAITOP: + szNeeded = s->read.head_left; + p = ((char*)&s->read.head[0]) + (2-szNeeded); + break; + + case NRS_WAITLEN: + szNeeded = s->read.head_left; + p = ((char*)&s->read.head[1]) + (2-szNeeded); + break; + + case NRS_WAITDATA:{ + register netbuf buf = s->read.buf; + + szNeeded = (buf->dataLen - buf->dataPos); + p = (char*)&buf->buf[ buf->dataPos ]; + } + break; + + default: + // .. the impossible gets possible .. + ShowError("_onRORecv: fd #%u has unknown read.state (%d) - disconnecting\n", fd, s->read.state); + return false; + break; + } + + + // + + rLen = read(fd, p, szNeeded); + if(rLen == 0){ + // eof.. + return false; + }else if(rLen == -1){ + + if(errno == EAGAIN || errno == EWOULDBLOCK){ + // try again later .. (this case shouldnt happen, because we're event trigered.. but .. sometimes it happens :) + return true; + } + + // an additional interesting case would be + // EINTR, this 'could' be handled .. but: + // posix says that its possible that data gets currupted during irq + // or data gor read and not reported.., so we'd have a data loss.. + // (which shouldnt happen with stream based protocols such as tcp) + // its better to disonnect the client in that case. + + return false; + } + + // + // Got Data: + // next action also depends on current state .. + // + szNeeded -= rLen; + switch(s->read.state){ + case NRS_WAITOP: + + if(szNeeded > 0){ + // still data missing .. + s->read.head_left = szNeeded; + return true; // wait for completion. + }else{ + // complete .. + // next state depends on packet type. + + s->read.head[1] = ((uint16*)s->netparser_data)[ s->read.head[0] ]; // store lenght of packet by opcode head[0] to head[1] + + if(s->read.head[1] == ROPACKET_UNKNOWN){ + // unknown packet - disconnect + ShowWarning("_onRORecv: fd #%u got unlnown packet 0x%04x - disconnecting.\n", fd, s->read.head[0]); + return false; + } + else if(s->read.head[1] == ROPACKET_DYNLEN){ + // dynamic length + // next state: requrie len. + s->read.state = NRS_WAITLEN; + s->read.head_left = 2; + return true; // + } + else if(s->read.head[1] == 2){ + // packet has no data (only opcode) + register netbuf buf = netbuffer_get(2); // :D whoohoo its giant! + + NBUFW(buf, 0) = s->read.head[0]; // store opcode @ packet begin. + buf->dataPos = 2; + buf->dataLen = 2; + buf->next = NULL; + + // Back to initial state -> Need opcode. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // Call completion routine here. + s->onPacketComplete(fd, s->read.head[0], 2, buf); + + return true; // done :) + } + else{ + // paket needs .. data .. + register netbuf buf = netbuffer_get( s->read.head[1] ); + + NBUFW(buf, 0) = s->read.head[0]; // store opcode @ packet begin. + buf->dataPos = 2; + buf->dataLen = s->read.head[1]; + buf->next = NULL; + + // attach buffer. + s->read.buf = buf; + + // set state: + s->read.state = NRS_WAITDATA; + + return true; + } + + }//endif: szNeeded > 0 (opcode read completed?) + + break; + + + case NRS_WAITLEN: + + if(szNeeded > 0){ + // incomplete .. + s->read.head_left = szNeeded; + return true; + }else{ + + if(s->read.head[1] == 4){ + // packet has no data (only opcode + length) + register netbuf buf = netbuffer_get( 4 ); + + NBUFL(buf, 0) = *((uint32*)&s->read.head[0]); // copy Opcode + length to netbuffer using MOVL + buf->dataPos = 4; + buf->dataLen = 4; + buf->next = NULL; + + // set initial state (need opcode) + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // call completion routine. + s->onPacketComplete(fd, s->read.head[0], 4, buf); + + return true; + } + else if(s->read.head[1] < 4){ + // invalid header. + ShowWarning("_onRORecv: fd #%u invalid header - got packet 0x%04x, reported length < 4 - INVALID - disconnecting\n", fd, s->read.head[0]); + return false; + } + else{ + // Data needed + // next state -> waitdata! + register netbuf buf = netbuffer_get( s->read.head[1] ); + + NBUFL(buf, 0) = *((uint32*)&s->read.head[0]); // copy Opcode + length to netbuffer using MOVL + buf->dataPos = 4; + buf->dataLen = s->read.head[1]; + buf->next = NULL; + + // attach to session: + s->read.buf = buf; + s->read.state = NRS_WAITDATA; + + return true; + } + + }//endif: szNeeded > 0 (length read complete?) + + break; + + + case NRS_WAITDATA: + + if(szNeeded == 0){ + // Packet finished! + // compltion. + register netbuf buf = s->read.buf; + + // set initial state. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // Call completion routine. + s->onPacketComplete(fd, NBUFW(buf, 0), buf->dataLen, buf); + + return true; + }else{ + // still data needed + s->read.buf->dataPos += rLen; + + return true; + } + break; + + + // + default: + ShowError("_onRORecv: fd #%u has unknown read.state (%d) [2] - disconnecting\n", fd, s->read.state); + return false; + break; + } + + + return false; +}//end: _onRORecv() + + +void network_send(int32 fd, netbuf buf){ + register SESSION *s = &g_Session[fd]; + +#ifdef PARANOID_CHECKS + if(fd >= MAXCONN){ + ShowError("network_send: tried to attach buffer to connection idientifer #%u which is out of bounds.\n", fd); + _network_free_netbuf_async(buf); + return; + } +#endif + + + if(s->type == NST_FREE) + return; + + // Check Max Outstanding buffers limit. + if( (s->write.max_outstanding > 0) && + (s->write.n_outstanding >= s->write.max_outstanding) ){ + + ShowWarning("network_send: fd #%u max Outstanding buffers exceeded. - disconnecting.\n", fd); + network_disconnect(fd); + // + _network_free_netbuf_async(buf); + return; + } + + + // Attach to the end: + buf->next = NULL; + if(s->write.buf_last != NULL){ + s->write.buf_last->next = buf; + s->write.buf_last = buf; + + }else{ + // currently no buffer attached. + s->write.buf = s->write.buf_last = buf; + + // register @ evdp for writable notification. + evdp_writable_add(fd, &s->evdp_data); // + } + + + // + s->write.n_outstanding++; + +}//end: network_send() + + +void network_parser_set_ro(int32 fd, + int16 *packetlentable, + void (*onPacketCompleteProc)(int32 fd, uint16 op, uint16 len, netbuf buf) + ){ + register SESSION *s = &g_Session[fd]; + register netbuf b, nb; // used for potential free attached buffers. + + if(s->type == NST_FREE) + return; + + s->onPacketComplete = onPacketCompleteProc; + + s->onRecv = _onRORecv; // .. + s->onSend = _onSend; // Using the normal generic netbuf based send function. + + s->netparser_data = packetlentable; + + // Initial State -> Need Packet OPCode. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + + + // Detach (if..) all buffers. + if(s->read.buf != NULL){ + _network_free_netbuf_async(s->read.buf); // + s->read.buf = NULL; + } + + if(s->write.buf != NULL){ + b = s->write.buf; + while(1){ + nb = b->next; + + _network_free_netbuf_async(b); + + b = nb; + } + + s->write.buf = NULL; + s->write.buf_last = NULL; + s->write.n_outstanding = 0; + } + + // not changing any limits on outstanding .. + // + +}//end: network_parser_set_ro() diff --git a/src/common/network.h b/src/common/network.h new file mode 100644 index 000000000..d7b463a2f --- /dev/null +++ b/src/common/network.h @@ -0,0 +1,189 @@ +#ifndef _rA_NETWORK_H_ +#define _rA_NETWORK_H_ + +#include <netinet/in.h> +#include "../common/cbasetypes.h" +#include "../common/netbuffer.h" +#include "../common/evdp.h" + +#ifndef MAXCONN +#define MAXCONN 16384 +#endif + + +typedef struct SESSION{ + EVDP_DATA evdp_data; // Must be always the frist member! (some evdp's may rely on this fact) + + // Connection Type + enum{ NST_FREE=0, NST_LISTENER = 1, NST_CLIENT=2, NST_OUTGOING=3} type; + + // Flags / Settings. + bool v6; // is v6? + bool disconnect_in_progress; // To prevent stack overflows / recursive calls. + + + union{ // union to save memory. + struct sockaddr_in v4; + struct sockaddr_in6 v6; + }addr; + + + // "lowlevel" Handlers + // (Implemented by the protocol specific parser) + // + bool (*onRecv)(int32 fd); // return false = disconnect + bool (*onSend)(int32 fd); // return false = disconnect + + // Event Handlers for LISTENER type sockets + // + // onConnect gets Called when a connection has been + // successfully accepted. + // Session entry is available in this Handler! + // A returncode of false will reejct the connection (disconnect) + // Note: When rejecting a connection in onConnect by returning false + // The onDisconnect handler wont get called! + // Note: the onConnect Handler is also responsible for setting + // the appropriate netparser (which implements onRecv/onSend..) [protocol specific] + // + // onDisconnect gets called when a connection gets disconnected + // (by peer as well as by core) + // + bool (*onConnect)(int32 fd); // return false = disconnect (wont accept) + void (*onDisconnect)(int32 fd); + + + // + // Parser specific data + // + void *netparser_data; // incase of RO Packet Parser, pointer to packet len table (uint16array) + void (*onPacketComplete)(int32 fd, uint16 op, uint16 len, netbuf buf); + + + // + // Buffers + // + struct{ + enum NETREADSTATE { NRS_WAITOP = 0, NRS_WAITLEN = 1, NRS_WAITDATA = 2} state; + + uint32 head_left; + uint16 head[2]; + + netbuf buf; + } read; + + struct{ + uint32 max_outstanding; + uint32 n_outstanding; + + uint32 dataPos; + + netbuf buf, buf_last; + } write; + + // Application Level data Pointer + // (required for backward compatibility with previous athena socket system.) + void *data; + +} SESSION; + + +/** + * Subsystem Initialization / Finalization. + * + */ +void network_init(); +void network_final(); + + +/** + * Will do the net work :) .. + */ +void network_do(); + + +/** + * Adds a new listner. + * + * @param v6 v6 listner? + * @param *addr the address to listen on. + * @param port port to listen on + * + * @return -1 on error otherwise the identifier of the new listener. + */ +int32 network_addlistener(bool v6, const char *addr, uint16 port); + + +/** + * Tries to establish an outgoing connection. + * + * @param v6 operate with IPv6 addresses? + * @param addr the address to connect to + * @param port the port to connect to + * @param from_addr the address to connect from (local source / optional if auto -> NULL) + * @param from_port the port to connect from (local source / optional if auto -> 0) + * @param onConnectionEstablishedHandler callback that gets called when the connection is established. + * @param onConnectionLooseHandler callback that gets called when the connection gets disconnected (or the connection couldnt be established) + * + * @return -1 on error otherwise the identifier of the new connection + */ +int32 network_connect(bool v6, + const char *addr, + uint16 port, + const char *from_addr, + uint16 from_port, + bool (*onConnectionEstablishedHandler)(int32 fd), + void (*onConnectionLooseHandler)(int32 fd) +); + + + +/** + * Disconnects the given connection + * + * @param fd connection identifier. + * + * @Note: + * - onDisconnect callback gets called! + * - cleares (returns) all assigned buffers + * + */ +void network_disconnect(int32 fd); + + +/** + * Attach's a netbuffer at the end of sending queue to the given connection + * + * @param fd connection identifier + * @param buf netbuffer to attach. + */ +void network_send(int32 fd, netbuf buf); + + +/** + * Sets the parser to RO Protocol like Packet Parser. + * + * @param fd connection identifier + * @param *packetlentable pointer to array of uint16 in size of UINT16_MAX, + * @param onComplteProc callback for packet completion. + * + * @note: + * PacketLen Table Fromat: + * each element's offsets represents th ro opcode. + * value is length. + * a length of 0 means the packet is dynamic. + * a length of UINT16_MAX means the packet is unknown. + * + * Static Packets must contain their hader in len so (0x64 == 55 ..) + * + */ +void network_parser_set_ro(int32 fd, + int16 *packetlentable, + void (*onPacketCompleteProc)(int32 fd, uint16 op, uint16 len, netbuf buf) + ); +#define ROPACKET_UNKNOWN UINT16_MAX +#define ROPACKET_DYNLEN 0 + + + + +#endif diff --git a/src/common/nullpo.c b/src/common/nullpo.c new file mode 100644 index 000000000..4383109a7 --- /dev/null +++ b/src/common/nullpo.c @@ -0,0 +1,91 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "nullpo.h" +#include "../common/showmsg.h" +// #include "logs.h" // 布石してみる + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullチェック 及び 情報出力 + *--------------------------------------*/ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) +{ + va_list ap; + + if (target != NULL) + return 0; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); + return 1; +} + +int nullpo_chk(const char *file, int line, const char *func, const void *target) +{ + if (target != NULL) + return 0; + + nullpo_info_core(file, line, func, NULL, NULL); + return 1; +} + + +/*====================================== + * nullpo情報出力(外部呼出し向けラッパ) + *--------------------------------------*/ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); +} + +void nullpo_info(const char *file, int line, const char *func) +{ + nullpo_info_core(file, line, func, NULL, NULL); +} + + +/*====================================== + * nullpo情報出力(Main) + *--------------------------------------*/ +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap) +{ + if (file == NULL) + file = "??"; + + func = + func == NULL ? "unknown": + func[0] == '\0' ? "unknown": + func; + + ShowMessage("--- nullpo info --------------------------------------------\n"); + ShowMessage("%s:%d: in func `%s'\n", file, line, func); + if (fmt != NULL) + { + if (fmt[0] != '\0') + { + vprintf(fmt, ap); + + // 最後に改行したか確認 + if (fmt[strlen(fmt)-1] != '\n') + ShowMessage("\n"); + } + } + ShowMessage("--- end nullpo info ----------------------------------------\n"); + + // ここらでnullpoログをファイルに書き出せたら + // まとめて提出できるなと思っていたり。 +} diff --git a/src/common/nullpo.h b/src/common/nullpo.h new file mode 100644 index 000000000..8ee86a782 --- /dev/null +++ b/src/common/nullpo.h @@ -0,0 +1,225 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#include "../common/cbasetypes.h" + +#define NLP_MARK __FILE__, __LINE__, __func__ + +// enabled by default on debug builds +#if defined(DEBUG) && !defined(NULLPO_CHECK) +#define NULLPO_CHECK +#endif + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullチェック 及び 情報出力後 return + *・展開するとifとかreturn等が出るので + * 一行単体で使ってください。 + *・nullpo_ret(x = func()); + * のような使用法も想定しています。 + *-------------------------------------- + * nullpo_ret(t) + * 戻り値 0固定 + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retv(t) + * 戻り値 なし + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retr(ret, t) + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 0 + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 なし + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * 詳細情報出力用 + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ + +#if defined(NULLPO_CHECK) + +#define nullpo_ret(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(0);} + +#define nullpo_retv(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return;} + +#define nullpo_retr(ret, t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(ret);} + +#define nullpo_retb(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {break;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(0);} + +#define nullpo_retv_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return;} + +#define nullpo_retr_f(ret, t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(ret);} + +#define nullpo_retb_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {break;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(0);} + +#define nullpo_retv_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return;} + +#define nullpo_retr_f(ret, t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(ret);} + +#define nullpo_retb_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {break;} + +#else + +/* その他の場合・・・ orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// 良い方法が思いつかなかったので・・・苦肉の策です。 +// 一応ワーニングは出ないはず + +#define nullpo_ret(t) (void)(t) +#define nullpo_retv(t) (void)(t) +#define nullpo_retr(ret, t) (void)(t) +#define nullpo_retb(t) (void)(t) + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) (void)(t) +#define nullpo_retv_f(t, fmt, ...) (void)(t) +#define nullpo_retr_f(ret, t, fmt, ...) (void)(t) +#define nullpo_retb_f(t, fmt, ...) (void)(t) + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) (void)(t) +#define nullpo_retv_f(t, fmt, args...) (void)(t) +#define nullpo_retr_f(ret, t, fmt, args...) (void)(t) +#define nullpo_retb_f(t, fmt, args...) (void)(t) + +#else +/* その他の場合・・・ orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullチェック 及び 情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullチェック 及び 詳細な情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) + __attribute__((format(printf,5,6))); + + +/*====================================== + * nullpo_info + * nullpo情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpo詳細情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif /* _NULLPO_H_ */ diff --git a/src/common/raconf.c b/src/common/raconf.c new file mode 100644 index 000000000..2703560ff --- /dev/null +++ b/src/common/raconf.c @@ -0,0 +1,584 @@ +// +// Athena style config parser +// (would be better to have "one" implementation instead of .. 4 :) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) RAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/db.h" +#include "../common/malloc.h" + +#include "../common/raconf.h" + +#define SECTION_LEN 32 +#define VARNAME_LEN 64 + +struct raconf { + DBMap *db; +}; + + +struct conf_value{ + int64 intval; + bool bval; + double floatval; + size_t strval_len; // not includung \0 + char strval[16]; +}; + + + +static struct conf_value *makeValue(const char *key, char *val, size_t val_len){ + struct conf_value *v; + char *p; + size_t sz; + + sz = sizeof(struct conf_value); + if(val_len >= sizeof(v->strval)) + sz += (val_len - sizeof(v->strval) + 1); + + v = (struct conf_value*)aCalloc(1, sizeof(struct conf_value)); + if(v == NULL){ + ShowFatalError("raconf: makeValue => Out of Memory while allocating new node.\n"); + return NULL; + } + + memcpy(v->strval, val, val_len); + v->strval[val_len+1] = '\0'; + v->strval_len = val_len; + + + // Parse boolean value: + if((val_len == 4) && (strncmpi("true", val, 4) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("yes", val, 3) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("oui", val, 3) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("si", val, 2) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("ja", val, 2) == 0)) + v->bval = true; + else if((val_len == 1) && (*val == '1')) + v->bval = true; + else if((val_len == 5) && (strncmpi("false", val, 5) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 3) && (strncmpi("non", val, 3) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 4) && (strncmpi("nein", val, 4) == 0)) + v->bval = false; + else if((val_len == 1) && (*val == '0')) + v->bval = false; + else + v->bval = false; // assume false. + + // Parse number + // Supported formats: + // prefix: 0x hex . + // postix: h for hex + // b for bin (dual) + if( (val_len >= 1 && (val[val_len] == 'h')) || (val_len >= 2 && (val[0] == '0' && val[1] == 'x')) ){//HEX! + if(val[val_len] == 'h'){ + val[val_len]= '\0'; + v->intval = strtoull(val, NULL, 16); + val[val_len] = 'h'; + }else + v->intval = strtoull(&val[2], NULL, 16); + }else if( val_len >= 1 && (val[val_len] == 'b') ){ //BIN + val[val_len] = '\0'; + v->intval = strtoull(val, NULL, 2); + val[val_len] = 'b'; + }else if( *val >='0' && *val <= '9'){ // begins with normal digit, so assume its dec. + // is it float? + bool is_float = false; + + for(p = val; *p != '\0'; p++){ + if(*p == '.'){ + v->floatval = strtod(val, NULL); + v->intval = (int64) v->floatval; + is_float = true; + break; + } + } + + if(is_float == false){ + v->intval = strtoull(val, NULL, 10); + v->floatval = (double) v->intval; + } + }else{ + // Everything else: lets use boolean for fallback + if(v->bval == true) + v->intval = 1; + else + v->intval = 0; + } + + return v; +}//end: makeValue() + + +static bool configParse(raconf inst, const char *fileName){ + FILE *fp; + char line[4096]; + char currentSection[SECTION_LEN]; + char *p; + char c; + int linecnt; + size_t linelen; + size_t currentSection_len; + + fp = fopen(fileName, "r"); + if(fp == NULL){ + ShowError("configParse: cannot open '%s' for reading.\n", fileName); + return false; + } + + + // Start with empty section: + currentSection[0] = '\0'; + currentSection_len = 0; + + // + linecnt = 0; + while(1){ + linecnt++; + + if(fgets(line, sizeof(line), fp) != line) + break; + + linelen = strlen(line); + p = line; + + // Skip whitespaces from beginning (space and tab) + _line_begin_skip_whities: + c = *p; + if(c == ' ' || c == '\t'){ + p++; + linelen--; + goto _line_begin_skip_whities; + } + + // Remove linebreaks as (cr or lf) and whitespaces from line end! + _line_end_skip_whities_and_breaks: + c = p[linelen-1]; + if(c == '\r' || c == '\n' || c == ' ' || c == '\t'){ + p[--linelen] = '\0'; + goto _line_end_skip_whities_and_breaks; + } + + // Empty line? + // or line starts with comment (commented out)? + if(linelen == 0 || (p[0] == '/' && p[1] == '/') || p[0] == ';') + continue; + + // Variable names can contain: + // A-Za-z-_.0-9 + // + // Sections start with [ .. ] (INI Style) + // + c = *p; + + // check what we have.. :) + if(c == '['){ // got section! + // Got Section! + // Search for ] + char *start = (p+1); + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Section name in %s:%u (expected ']')\n", fileName, linecnt); + fclose(fp); + return false; + }else if(c == ']'){ // closing backet (section name termination) + if( (p - start + 1) > (sizeof(currentSection) ) ){ + ShowError("Syntax Error: Section name in %s:%u is too large (max Supported length: %u chars)\n", fileName, linecnt, sizeof(currentSection)-1); + fclose(fp); + return false; + } + + // Set section! + *p = '\0'; // add termination here. + memcpy(currentSection, start, (p-start)+1 ); // we'll copy \0, too! (we replaced the ] backet with \0.) + currentSection_len = (p-start); + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == ' ') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. (allowed char / specifier) + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Section name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse section name + + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // Got variable! + // Search for '=' or ':' wich termiantes the name + char *start = p; + char *valuestart = NULL; + size_t start_len; + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Variable name in %s:%u\n", fileName, linecnt); + fclose(fp); + return false; + }else if( (c == '=') || (c == ':') ){ + // got name termination + + *p = '\0'; // Terminate it so (start) will hold the pointer to the name. + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. allowed char + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Variable name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse var name + + start_len = (p-start); + if(start_len >= VARNAME_LEN){ + ShowError("%s:%u Variable length exceeds limit of %u Characters.\n", fileName, linecnt, VARNAME_LEN-1); + fclose(fp); + return false; + }else if(start_len == 0){ + ShowError("%s:%u Empty Variable name is not allowed.\n", fileName, linecnt); + fclose(fp); + return false; + } + + + valuestart = (p+1); + + + // Skip whitespace from begin of value (tab and space) + _skip_value_begin_whities: + c = *valuestart; + if(c == ' ' || c == '\t'){ + valuestart++; + goto _skip_value_begin_whities; + } + + // Scan for value termination, + // wich can be \0 or comment start (// or ; (INI) ) + // + p = valuestart; + while(1){ + c = *p; + if(c == '\0'){ + // Terminated by line end. + break; + }else if(c == '/' && p[1] == '/'){ + // terminated by c++ style comment. + *p = '\0'; + break; + }else if(c == ';'){ + // terminated by ini style comment. + *p = '\0'; + break; + } + + p++; + }//endwhile: search var value end. + + + // Strip whitespaces from end of value. + if(valuestart != p){ // not empty! + p--; + _strip_value_end_whities: + c = *p; + if(c == ' ' || c == '\t'){ + *p = '\0'; + p--; + goto _strip_value_end_whities; + } + p++; + } + + + // Buildin Hook: + if( stricmp(start, "import") == 0){ + if( configParse(inst, valuestart) != true){ + ShowError("%s:%u - Import of '%s' failed!\n", fileName, linecnt, valuestart); + } + }else{ + // put it to db. + struct conf_value *v, *o; + char key[ (SECTION_LEN+VARNAME_LEN+1+1) ]; //+1 for delimiter, +1 for termination. + size_t section_len; + + if(*currentSection == '\0'){ // empty / none + strncpy(key, "<unnamed>",9); + section_len = 9; + }else{ + strncpy(key, currentSection, currentSection_len); + section_len = currentSection_len; + } + + key[section_len] = '.'; // Delim + + strncpy(&key[section_len+1], start, start_len); + + key[section_len + start_len + 1] = '\0'; + + + v = makeValue(key, valuestart, (p-valuestart) ); + + // Try to get the old one before + o = strdb_get(inst->db, key); + if(o != NULL){ + strdb_remove(inst->db, key); + aFree(o); // + } + + strdb_put( inst->db, key, v); + } + + + }else{ + ShowError("Syntax Error: unexpected Character '%c' in %s:%u (offset %u)\n", c, fileName, linecnt, (p-line) ); + fclose(fp); + return false; + } + + + + } + + + + fclose(fp); + return true; +}//end: configParse() + + +#define MAKEKEY(dest, section, key) { size_t section_len, key_len; \ + if(section == NULL || *section == '\0'){ \ + strncpy(dest, "<unnamed>", 9); \ + section_len = 9; \ + }else{ \ + section_len = strlen(section); \ + strncpy(dest, section, section_len); \ + } \ + \ + dest[section_len] = '.'; \ + \ + key_len = strlen(key); \ + strncpy(&dest[section_len+1], key, key_len); \ + dest[section_len + key_len + 1] = '\0'; \ + } + + +raconf raconf_parse(const char *file_name){ + struct raconf *rc; + + rc = aCalloc(1, sizeof(struct raconf) ); + if(rc == NULL){ + ShowFatalError("raconf_parse: failed to allocate memory for new handle\n"); + return NULL; + } + + rc->db = strdb_alloc(DB_OPT_BASE | DB_OPT_DUP_KEY, 98); + // + + if(configParse(rc, file_name) != true){ + ShowError("Failed to Parse Configuration file '%s'\n", file_name); + } + + return rc; +}//end: raconf_parse() + + +void raconf_destroy(raconf rc){ + DBIterator *iter; + struct conf_value *v; + + // Clear all entrys in db. + iter = db_iterator(rc->db); + for( v = (struct conf_value*)dbi_first(iter); dbi_exists(iter); v = (struct conf_value*)dbi_next(iter) ){ + aFree(v); + } + dbi_destroy(iter); + + db_destroy(rc->db); + + aFree(rc); + +}//end: raconf_destroy() + +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->bval; +}//end: raconf_getbool() + + +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return (float)v->floatval; +}//end: raconf_getfloat() + + +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->intval; + +}//end: raconf_getint() + + +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->strval; +}//end: raconf_getstr() + + +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->bval; + } + + }else{ + return v->bval; + } +}//end: raconf_getboolEx() + + +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return (float)v->floatval; + } + + }else{ + return (float)v->floatval; + } + +}//end: raconf_getfloatEx() + + +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->intval; + } + + }else{ + return v->intval; + } + +}//end: raconf_getintEx() + + +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->strval; + } + + }else{ + return v->strval; + } + +}//end: raconf_getstrEx() diff --git a/src/common/raconf.h b/src/common/raconf.h new file mode 100644 index 000000000..68a2b51b2 --- /dev/null +++ b/src/common/raconf.h @@ -0,0 +1,59 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_CONF_H_ +#define _rA_CONF_H_ + +#include "../common/cbasetypes.h" + +// rAthena generic configuration file parser +// +// Config file Syntax is athena style +// extended with ini style support (including sections) +// +// Comments are started with // or ; (ini style) +// + +typedef struct raconf *raconf; + + +/** + * Parses a rAthna Configuration file + * + * @param file_name path to the file to parse + * + * @returns not NULL incase of success + */ +raconf raconf_parse(const char *file_name); + + +/** + * Frees a Handle received from raconf_parse + * + * @param rc - the handle to free + */ +void raconf_destroy(raconf rc); + + +/** + * Gets the value for Section / Key pair, if key not exists returns _default! + * + */ +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default); +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default); +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default); +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default); + +/** + * Gets the value for Section / Key pair, but has fallback section option if not found in section, + * if not found in both - default gets returned. + * + */ +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default); +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default); +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default); +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default); + + + +#endif diff --git a/src/common/random.c b/src/common/random.c new file mode 100644 index 000000000..5c048c7eb --- /dev/null +++ b/src/common/random.c @@ -0,0 +1,83 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/showmsg.h" +#include "../common/timer.h" // gettick +#include "random.h" +#if defined(WIN32) + #include "../common/winapi.h" +#elif defined(HAVE_GETPID) || defined(HAVE_GETTID) + #include <sys/types.h> + #include <unistd.h> +#endif +#include <time.h> // time +#include <mt19937ar.h> // init_genrand, genrand_int32, genrand_res53 + + +/// Initializes the random number generator with an appropriate seed. +void rnd_init(void) +{ + uint32 seed = gettick(); + seed += (uint32)time(NULL); +#if defined(WIN32) + seed += GetCurrentProcessId(); + seed += GetCurrentThreadId(); +#else +#if defined(HAVE_GETPID) + seed += (uint32)getpid(); +#endif // HAVE_GETPID +#if defined(HAVE_GETTID) + seed += (uint32)gettid(); +#endif // HAVE_GETTID +#endif + init_genrand(seed); +} + + +/// Initializes the random number generator. +void rnd_seed(uint32 seed) +{ + init_genrand(seed); +} + + +/// Generates a random number in the interval [0, SINT32_MAX] +int32 rnd(void) +{ + return (int32)genrand_int31(); +} + + +/// Generates a random number in the interval [0, dice_faces) +/// NOTE: interval is open ended, so dice_faces is excluded (unless it's 0) +uint32 rnd_roll(uint32 dice_faces) +{ + return (uint32)(rnd_uniform()*dice_faces); +} + + +/// Generates a random number in the interval [min, max] +/// Returns min if range is invalid. +int32 rnd_value(int32 min, int32 max) +{ + if( min >= max ) + return min; + return min + (int32)(rnd_uniform()*(max-min+1)); +} + + +/// Generates a random number in the interval [0.0, 1.0) +/// NOTE: interval is open ended, so 1.0 is excluded +double rnd_uniform(void) +{ + return ((uint32)genrand_int32())*(1.0/4294967296.0);// divided by 2^32 +} + + +/// Generates a random number in the interval [0.0, 1.0) with 53-bit resolution +/// NOTE: interval is open ended, so 1.0 is excluded +/// NOTE: 53 bits is the maximum precision of a double +double rnd_uniform53(void) +{ + return genrand_res53(); +} diff --git a/src/common/random.h b/src/common/random.h new file mode 100644 index 000000000..43dfd36c0 --- /dev/null +++ b/src/common/random.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _RANDOM_H_ +#define _RANDOM_H_ + +#include "../common/cbasetypes.h" + +void rnd_init(void); +void rnd_seed(uint32); + +int32 rnd(void);// [0, SINT32_MAX] +uint32 rnd_roll(uint32 dice_faces);// [0, dice_faces) +int32 rnd_value(int32 min, int32 max);// [min, max] +double rnd_uniform(void);// [0.0, 1.0) +double rnd_uniform53(void);// [0.0, 1.0) + +#endif /* _RANDOM_H_ */ diff --git a/src/common/showmsg.c b/src/common/showmsg.c new file mode 100644 index 000000000..609ae3c50 --- /dev/null +++ b/src/common/showmsg.c @@ -0,0 +1,892 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/strlib.h" // StringBuf +#include "showmsg.h" +#include "core.h" //[Ind] - For SERVER_TYPE + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <stdlib.h> // atexit + +#include "libconfig.h" + +#ifdef WIN32 + #include "../common/winapi.h" + + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log\\map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log\\char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log\\login-server.log" + #endif + #endif + #endif +#else + #include <unistd.h> + + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log/map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log/char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log/login-server.log" + #endif + #endif + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/// behavioral parameter. +/// when redirecting output: +/// if true prints escape sequences +/// if false removes the escape sequences +int stdout_with_ansisequence = 0; + +int msg_silent = 0; //Specifies how silent the console is. + +int console_msg_log = 0;//[Ind] msg error logging + +/////////////////////////////////////////////////////////////////////////////// +/// static/dynamic buffer for the messages + +#define SBUF_SIZE 2054 // never put less that what's required for the debug message + +#define NEWBUF(buf) \ + struct { \ + char s_[SBUF_SIZE]; \ + StringBuf *d_; \ + char *v_; \ + int l_; \ + } buf ={"",NULL,NULL,0}; \ +//define NEWBUF + +#define BUFVPRINTF(buf,fmt,args) \ + buf.l_ = vsnprintf(buf.s_, SBUF_SIZE, fmt, args); \ + if( buf.l_ >= 0 && buf.l_ < SBUF_SIZE ) \ + {/* static buffer */ \ + buf.v_ = buf.s_; \ + } \ + else \ + {/* dynamic buffer */ \ + buf.d_ = StringBuf_Malloc(); \ + buf.l_ = StringBuf_Vprintf(buf.d_, fmt, args); \ + buf.v_ = StringBuf_Value(buf.d_); \ + ShowDebug("showmsg: dynamic buffer used, increase the static buffer size to %d or more.\n", buf.l_+1);\ + } \ +//define BUFVPRINTF + +#define BUFVAL(buf) buf.v_ +#define BUFLEN(buf) buf.l_ + +#define FREEBUF(buf) \ + if( buf.d_ ) \ + { \ + StringBuf_Free(buf.d_); \ + buf.d_ = NULL; \ + } \ + buf.v_ = NULL; \ +//define FREEBUF + +/////////////////////////////////////////////////////////////////////////////// +#ifdef _WIN32 +// XXX adapted from eApp (comments are left untouched) [flaviojs] + +/////////////////////////////////////////////////////////////////////////////// +// ansi compatible printf with control sequence parser for windows +// fast hack, handle with care, not everything implemented +// +// \033[#;...;#m - Set Graphics Rendition (SGR) +// +// printf("\x1b[1;31;40m"); // Bright red on black +// printf("\x1b[3;33;45m"); // Blinking yellow on magenta (blink not implemented) +// printf("\x1b[1;30;47m"); // Bright black (grey) on dim white +// +// Style Foreground Background +// 1st Digit 2nd Digit 3rd Digit RGB +// 0 - Reset 30 - Black 40 - Black 000 +// 1 - FG Bright 31 - Red 41 - Red 100 +// 2 - Unknown 32 - Green 42 - Green 010 +// 3 - Blink 33 - Yellow 43 - Yellow 110 +// 4 - Underline 34 - Blue 44 - Blue 001 +// 5 - BG Bright 35 - Magenta 45 - Magenta 101 +// 6 - Unknown 36 - Cyan 46 - Cyan 011 +// 7 - Reverse 37 - White 47 - White 111 +// 8 - Concealed (invisible) +// +// \033[#A - Cursor Up (CUU) +// Moves the cursor up by the specified number of lines without changing columns. +// If the cursor is already on the top line, this sequence is ignored. \e[A is equivalent to \e[1A. +// +// \033[#B - Cursor Down (CUD) +// Moves the cursor down by the specified number of lines without changing columns. +// If the cursor is already on the bottom line, this sequence is ignored. \e[B is equivalent to \e[1B. +// +// \033[#C - Cursor Forward (CUF) +// Moves the cursor forward by the specified number of columns without changing lines. +// If the cursor is already in the rightmost column, this sequence is ignored. \e[C is equivalent to \e[1C. +// +// \033[#D - Cursor Backward (CUB) +// Moves the cursor back by the specified number of columns without changing lines. +// If the cursor is already in the leftmost column, this sequence is ignored. \e[D is equivalent to \e[1D. +// +// \033[#E - Cursor Next Line (CNL) +// Moves the cursor down the indicated # of rows, to column 1. \e[E is equivalent to \e[1E. +// +// \033[#F - Cursor Preceding Line (CPL) +// Moves the cursor up the indicated # of rows, to column 1. \e[F is equivalent to \e[1F. +// +// \033[#G - Cursor Horizontal Absolute (CHA) +// Moves the cursor to indicated column in current row. \e[G is equivalent to \e[1G. +// +// \033[#;#H - Cursor Position (CUP) +// Moves the cursor to the specified position. The first # specifies the line number, +// the second # specifies the column. If you do not specify a position, the cursor moves to the home position: +// the upper-left corner of the screen (line 1, column 1). +// +// \033[#;#f - Horizontal & Vertical Position +// (same as \033[#;#H) +// +// \033[s - Save Cursor Position (SCP) +// The current cursor position is saved. +// +// \033[u - Restore cursor position (RCP) +// Restores the cursor position saved with the (SCP) sequence \033[s. +// (addition, restore to 0,0 if nothinh was saved before) +// + +// \033[#J - Erase Display (ED) +// Clears the screen and moves to the home position +// \033[0J - Clears the screen from cursor to end of display. The cursor position is unchanged. (default) +// \033[1J - Clears the screen from start to cursor. The cursor position is unchanged. +// \033[2J - Clears the screen and moves the cursor to the home position (line 1, column 1). +// +// \033[#K - Erase Line (EL) +// Clears the current line from the cursor position +// \033[0K - Clears all characters from the cursor position to the end of the line (including the character at the cursor position). The cursor position is unchanged. (default) +// \033[1K - Clears all characters from start of line to the cursor position. (including the character at the cursor position). The cursor position is unchanged. +// \033[2K - Clears all characters of the whole line. The cursor position is unchanged. + + +/* +not implemented + +\033[#L +IL: Insert Lines: The cursor line and all lines below it move down # lines, leaving blank space. The cursor position is unchanged. The bottommost # lines are lost. \e[L is equivalent to \e[1L. +\033[#M +DL: Delete Line: The block of # lines at and below the cursor are deleted; all lines below them move up # lines to fill in the gap, leaving # blank lines at the bottom of the screen. The cursor position is unchanged. \e[M is equivalent to \e[1M. +\033[#\@ +ICH: Insert CHaracter: The cursor character and all characters to the right of it move right # columns, leaving behind blank space. The cursor position is unchanged. The rightmost # characters on the line are lost. \e[\@ is equivalent to \e[1\@. +\033[#P +DCH: Delete CHaracter: The block of # characters at and to the right of the cursor are deleted; all characters to the right of it move left # columns, leaving behind blank space. The cursor position is unchanged. \e[P is equivalent to \e[1P. + +Escape sequences for Select Character Set +*/ + +#define is_console(handle) (FILE_TYPE_CHAR==GetFileType(handle)) + +/////////////////////////////////////////////////////////////////////////////// +int VFPRINTF(HANDLE handle, const char *fmt, va_list argptr) +{ + ///////////////////////////////////////////////////////////////// + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + static COORD saveposition = {0,0}; + */ + + ///////////////////////////////////////////////////////////////// + DWORD written; + char *p, *q; + NEWBUF(tempbuf); // temporary buffer + + if(!fmt || !*fmt) + return 0; + + // Print everything to the buffer + BUFVPRINTF(tempbuf,fmt,argptr); + + if( !is_console(handle) && stdout_with_ansisequence ) + { + WriteFile(handle, BUFVAL(tempbuf), BUFLEN(tempbuf), &written, 0); + return 0; + } + + // start with processing + p = BUFVAL(tempbuf); + while ((q = strchr(p, 0x1b)) != NULL) + { // find the escape character + if( 0==WriteConsole(handle, p, (DWORD)(q-p), &written, 0) ) // write up to the escape + WriteFile(handle, p, (DWORD)(q-p), &written, 0); + + if( q[1]!='[' ) + { // write the escape char (whatever purpose it has) + if(0==WriteConsole(handle, q, 1, &written, 0) ) + WriteFile(handle,q, 1, &written, 0); + p=q+1; //and start searching again + } + else + { // from here, we will skip the '\033[' + // we break at the first unprocessible position + // assuming regular text is starting there + uint8 numbers[16], numpoint=0; + CONSOLE_SCREEN_BUFFER_INFO info; + + // initialize + GetConsoleScreenBufferInfo(handle, &info); + memset(numbers,0,sizeof(numbers)); + + // skip escape and bracket + q=q+2; + for(;;) + { + if( ISDIGIT(*q) ) + { // add number to number array, only accept 2digits, shift out the rest + // so // \033[123456789m will become \033[89m + numbers[numpoint] = (numbers[numpoint]<<4) | (*q-'0'); + ++q; + // and next character + continue; + } + else if( *q == ';' ) + { // delimiter + if(numpoint<sizeof(numbers)/sizeof(*numbers)) + { // go to next array position + numpoint++; + } + else + { // array is full, so we 'forget' the first value + memmove(numbers,numbers+1,sizeof(numbers)/sizeof(*numbers)-1); + numbers[sizeof(numbers)/sizeof(*numbers)-1]=0; + } + ++q; + // and next number + continue; + } + else if( *q == 'm' ) + { // \033[#;...;#m - Set Graphics Rendition (SGR) + uint8 i; + for(i=0; i<= numpoint; ++i) + { + if( 0x00 == (0xF0 & numbers[i]) ) + { // upper nibble 0 + if( 0 == numbers[i] ) + { // reset + info.wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } + else if( 1==numbers[i] ) + { // set foreground intensity + info.wAttributes |= FOREGROUND_INTENSITY; + } + else if( 5==numbers[i] ) + { // set background intensity + info.wAttributes |= BACKGROUND_INTENSITY; + } + else if( 7==numbers[i] ) + { // reverse colors (just xor them) + info.wAttributes ^= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + } + //case '2': // not existing + //case '3': // blinking (not implemented) + //case '4': // unterline (not implemented) + //case '6': // not existing + //case '8': // concealed (not implemented) + //case '9': // not existing + } + else if( 0x20 == (0xF0 & numbers[i]) ) + { // off + + if( 1==numbers[i] ) + { // set foreground intensity off + info.wAttributes &= ~FOREGROUND_INTENSITY; + } + else if( 5==numbers[i] ) + { // set background intensity off + info.wAttributes &= ~BACKGROUND_INTENSITY; + } + else if( 7==numbers[i] ) + { // reverse colors (just xor them) + info.wAttributes ^= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + } + } + else if( 0x30 == (0xF0 & numbers[i]) ) + { // foreground + uint8 num = numbers[i]&0x0F; + if(num==9) info.wAttributes |= FOREGROUND_INTENSITY; + if(num>7) num=7; // set white for 37, 38 and 39 + info.wAttributes &= ~(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); + if( (num & 0x01)>0 ) // lowest bit set = red + info.wAttributes |= FOREGROUND_RED; + if( (num & 0x02)>0 ) // second bit set = green + info.wAttributes |= FOREGROUND_GREEN; + if( (num & 0x04)>0 ) // third bit set = blue + info.wAttributes |= FOREGROUND_BLUE; + } + else if( 0x40 == (0xF0 & numbers[i]) ) + { // background + uint8 num = numbers[i]&0x0F; + if(num==9) info.wAttributes |= BACKGROUND_INTENSITY; + if(num>7) num=7; // set white for 47, 48 and 49 + info.wAttributes &= ~(BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE); + if( (num & 0x01)>0 ) // lowest bit set = red + info.wAttributes |= BACKGROUND_RED; + if( (num & 0x02)>0 ) // second bit set = green + info.wAttributes |= BACKGROUND_GREEN; + if( (num & 0x04)>0 ) // third bit set = blue + info.wAttributes |= BACKGROUND_BLUE; + } + } + // set the attributes + SetConsoleTextAttribute(handle, info.wAttributes); + } + else if( *q=='J' ) + { // \033[#J - Erase Display (ED) + // \033[0J - Clears the screen from cursor to end of display. The cursor position is unchanged. + // \033[1J - Clears the screen from start to cursor. The cursor position is unchanged. + // \033[2J - Clears the screen and moves the cursor to the home position (line 1, column 1). + uint8 num = (numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F); + int cnt; + DWORD tmp; + COORD origin = {0,0}; + if(num==1) + { // chars from start up to and including cursor + cnt = info.dwSize.X * info.dwCursorPosition.Y + info.dwCursorPosition.X + 1; + } + else if(num==2) + { // Number of chars on screen. + cnt = info.dwSize.X * info.dwSize.Y; + SetConsoleCursorPosition(handle, origin); + } + else// 0 and default + { // number of chars from cursor to end + origin = info.dwCursorPosition; + cnt = info.dwSize.X * (info.dwSize.Y - info.dwCursorPosition.Y) - info.dwCursorPosition.X; + } + FillConsoleOutputAttribute(handle, info.wAttributes, cnt, origin, &tmp); + FillConsoleOutputCharacter(handle, ' ', cnt, origin, &tmp); + } + else if( *q=='K' ) + { // \033[K : clear line from actual position to end of the line + // \033[0K - Clears all characters from the cursor position to the end of the line. + // \033[1K - Clears all characters from start of line to the cursor position. + // \033[2K - Clears all characters of the whole line. + + uint8 num = (numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F); + COORD origin = {0,info.dwCursorPosition.Y}; //warning C4204 + SHORT cnt; + DWORD tmp; + if(num==1) + { + cnt = info.dwCursorPosition.X + 1; + } + else if(num==2) + { + cnt = info.dwSize.X; + } + else// 0 and default + { + origin = info.dwCursorPosition; + cnt = info.dwSize.X - info.dwCursorPosition.X; // how many spaces until line is full + } + FillConsoleOutputAttribute(handle, info.wAttributes, cnt, origin, &tmp); + FillConsoleOutputCharacter(handle, ' ', cnt, origin, &tmp); + } + else if( *q == 'H' || *q == 'f' ) + { // \033[#;#H - Cursor Position (CUP) + // \033[#;#f - Horizontal & Vertical Position + // The first # specifies the line number, the second # specifies the column. + // The default for both is 1 + info.dwCursorPosition.X = (numbers[numpoint])?(numbers[numpoint]>>4)*10+((numbers[numpoint]&0x0F)-1):0; + info.dwCursorPosition.Y = (numpoint && numbers[numpoint-1])?(numbers[numpoint-1]>>4)*10+((numbers[numpoint-1]&0x0F)-1):0; + + if( info.dwCursorPosition.X >= info.dwSize.X ) info.dwCursorPosition.Y = info.dwSize.X-1; + if( info.dwCursorPosition.Y >= info.dwSize.Y ) info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q=='s' ) + { // \033[s - Save Cursor Position (SCP) + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(handle, &info); + saveposition = info.dwCursorPosition; + */ + } + else if( *q=='u' ) + { // \033[u - Restore cursor position (RCP) + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + SetConsoleCursorPosition(handle, saveposition); + */ + } + else if( *q == 'A' ) + { // \033[#A - Cursor Up (CUU) + // Moves the cursor UP # number of lines + info.dwCursorPosition.Y -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.Y < 0 ) + info.dwCursorPosition.Y = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'B' ) + { // \033[#B - Cursor Down (CUD) + // Moves the cursor DOWN # number of lines + info.dwCursorPosition.Y += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.Y >= info.dwSize.Y ) + info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'C' ) + { // \033[#C - Cursor Forward (CUF) + // Moves the cursor RIGHT # number of columns + info.dwCursorPosition.X += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.X >= info.dwSize.X ) + info.dwCursorPosition.X = info.dwSize.X-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'D' ) + { // \033[#D - Cursor Backward (CUB) + // Moves the cursor LEFT # number of columns + info.dwCursorPosition.X -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.X < 0 ) + info.dwCursorPosition.X = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'E' ) + { // \033[#E - Cursor Next Line (CNL) + // Moves the cursor down the indicated # of rows, to column 1 + info.dwCursorPosition.Y += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + info.dwCursorPosition.X = 0; + + if( info.dwCursorPosition.Y >= info.dwSize.Y ) + info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'F' ) + { // \033[#F - Cursor Preceding Line (CPL) + // Moves the cursor up the indicated # of rows, to column 1. + info.dwCursorPosition.Y -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + info.dwCursorPosition.X = 0; + + if( info.dwCursorPosition.Y < 0 ) + info.dwCursorPosition.Y = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'G' ) + { // \033[#G - Cursor Horizontal Absolute (CHA) + // Moves the cursor to indicated column in current row. + info.dwCursorPosition.X = (numbers[numpoint])?(numbers[numpoint]>>4)*10+((numbers[numpoint]&0x0F)-1):0; + + if( info.dwCursorPosition.X >= info.dwSize.X ) + info.dwCursorPosition.X = info.dwSize.X-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'L' || *q == 'M' || *q == '@' || *q == 'P') + { // not implemented, just skip + } + else + { // no number nor valid sequencer + // something is fishy, we break and give the current char free + --q; + } + // skip the sequencer and search again + p = q+1; + break; + }// end while + } + } + if (*p) // write the rest of the buffer + if( 0==WriteConsole(handle, p, (DWORD)strlen(p), &written, 0) ) + WriteFile(handle, p, (DWORD)strlen(p), &written, 0); + FREEBUF(tempbuf); + return 0; +} + +int FPRINTF(HANDLE handle, const char *fmt, ...) +{ + int ret; + va_list argptr; + va_start(argptr, fmt); + ret = VFPRINTF(handle,fmt,argptr); + va_end(argptr); + return ret; +} + +#define FFLUSH(handle) + +#define STDOUT GetStdHandle(STD_OUTPUT_HANDLE) +#define STDERR GetStdHandle(STD_ERROR_HANDLE) + +#else // not _WIN32 + + +#define is_console(file) (0!=isatty(fileno(file))) + +//vprintf_without_ansiformats +int VFPRINTF(FILE *file, const char *fmt, va_list argptr) +{ + char *p, *q; + NEWBUF(tempbuf); // temporary buffer + + if(!fmt || !*fmt) + return 0; + + if( is_console(file) || stdout_with_ansisequence ) + { + vfprintf(file, fmt, argptr); + return 0; + } + + // Print everything to the buffer + BUFVPRINTF(tempbuf,fmt,argptr); + + // start with processing + p = BUFVAL(tempbuf); + while ((q = strchr(p, 0x1b)) != NULL) + { // find the escape character + fprintf(file, "%.*s", (int)(q-p), p); // write up to the escape + if( q[1]!='[' ) + { // write the escape char (whatever purpose it has) + fprintf(file, "%.*s", 1, q); + p=q+1; //and start searching again + } + else + { // from here, we will skip the '\033[' + // we break at the first unprocessible position + // assuming regular text is starting there + + // skip escape and bracket + q=q+2; + while(1) + { + if( ISDIGIT(*q) ) + { + ++q; + // and next character + continue; + } + else if( *q == ';' ) + { // delimiter + ++q; + // and next number + continue; + } + else if( *q == 'm' ) + { // \033[#;...;#m - Set Graphics Rendition (SGR) + // set the attributes + } + else if( *q=='J' ) + { // \033[#J - Erase Display (ED) + } + else if( *q=='K' ) + { // \033[K : clear line from actual position to end of the line + } + else if( *q == 'H' || *q == 'f' ) + { // \033[#;#H - Cursor Position (CUP) + // \033[#;#f - Horizontal & Vertical Position + } + else if( *q=='s' ) + { // \033[s - Save Cursor Position (SCP) + } + else if( *q=='u' ) + { // \033[u - Restore cursor position (RCP) + } + else if( *q == 'A' ) + { // \033[#A - Cursor Up (CUU) + // Moves the cursor UP # number of lines + } + else if( *q == 'B' ) + { // \033[#B - Cursor Down (CUD) + // Moves the cursor DOWN # number of lines + } + else if( *q == 'C' ) + { // \033[#C - Cursor Forward (CUF) + // Moves the cursor RIGHT # number of columns + } + else if( *q == 'D' ) + { // \033[#D - Cursor Backward (CUB) + // Moves the cursor LEFT # number of columns + } + else if( *q == 'E' ) + { // \033[#E - Cursor Next Line (CNL) + // Moves the cursor down the indicated # of rows, to column 1 + } + else if( *q == 'F' ) + { // \033[#F - Cursor Preceding Line (CPL) + // Moves the cursor up the indicated # of rows, to column 1. + } + else if( *q == 'G' ) + { // \033[#G - Cursor Horizontal Absolute (CHA) + // Moves the cursor to indicated column in current row. + } + else if( *q == 'L' || *q == 'M' || *q == '@' || *q == 'P') + { // not implemented, just skip + } + else + { // no number nor valid sequencer + // something is fishy, we break and give the current char free + --q; + } + // skip the sequencer and search again + p = q+1; + break; + }// end while + } + } + if (*p) // write the rest of the buffer + fprintf(file, "%s", p); + FREEBUF(tempbuf); + return 0; +} +int FPRINTF(FILE *file, const char *fmt, ...) +{ + int ret; + va_list argptr; + va_start(argptr, fmt); + ret = VFPRINTF(file,fmt,argptr); + va_end(argptr); + return ret; +} + +#define FFLUSH fflush + +#define STDOUT stdout +#define STDERR stderr + +#endif// not _WIN32 + + + + + + + + + + +char timestamp_format[20] = ""; //For displaying Timestamps + +int _vShowMessage(enum msg_type flag, const char *string, va_list ap) +{ + va_list apcopy; + char prefix[100]; +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + FILE *fp; +#endif + + if (!string || *string == '\0') { + ShowError("Empty string passed to _vShowMessage().\n"); + return 1; + } + /** + * For the buildbot, these result in a EXIT_FAILURE from core.c when done reading the params. + **/ +#if defined(BUILDBOT) + if( flag == MSG_WARNING || + flag == MSG_ERROR || + flag == MSG_SQL ) { + buildbotflag = 1; + } +#endif + if( + ( flag == MSG_WARNING && console_msg_log&1 ) || + ( ( flag == MSG_ERROR || flag == MSG_SQL ) && console_msg_log&2 ) || + ( flag == MSG_DEBUG && console_msg_log&4 ) ) {//[Ind] + FILE *log = NULL; + if( (log = fopen(SERVER_TYPE == ATHENA_SERVER_MAP ? "./log/map-msg_log.log" : "./log/unknown.log","a+")) ) { + char timestring[255]; + time_t curtime; + time(&curtime); + strftime(timestring, 254, "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(log,"(%s) [ %s ] : ", + timestring, + flag == MSG_WARNING ? "Warning" : + flag == MSG_ERROR ? "Error" : + flag == MSG_SQL ? "SQL Error" : + flag == MSG_DEBUG ? "Debug" : + "Unknown"); + va_copy(apcopy, ap); + vfprintf(log,string,apcopy); + va_end(apcopy); + fclose(log); + } + } + if( + (flag == MSG_INFORMATION && msg_silent&1) || + (flag == MSG_STATUS && msg_silent&2) || + (flag == MSG_NOTICE && msg_silent&4) || + (flag == MSG_WARNING && msg_silent&8) || + (flag == MSG_ERROR && msg_silent&16) || + (flag == MSG_SQL && msg_silent&16) || + (flag == MSG_DEBUG && msg_silent&32) + ) + return 0; //Do not print it. + + if (timestamp_format[0] && flag != MSG_NONE) + { //Display time format. [Skotlex] + time_t t = time(NULL); + strftime(prefix, 80, timestamp_format, localtime(&t)); + } else prefix[0]='\0'; + + switch (flag) { + case MSG_NONE: // direct printf replacement + break; + case MSG_STATUS: //Bright Green (To inform about good things) + strcat(prefix,CL_GREEN"[Status]"CL_RESET":"); + break; + case MSG_SQL: //Bright Violet (For dumping out anything related with SQL) <- Actually, this is mostly used for SQL errors with the database, as successes can as well just be anything else... [Skotlex] + strcat(prefix,CL_MAGENTA"[SQL]"CL_RESET":"); + break; + case MSG_INFORMATION: //Bright White (Variable information) + strcat(prefix,CL_WHITE"[Info]"CL_RESET":"); + break; + case MSG_NOTICE: //Bright White (Less than a warning) + strcat(prefix,CL_WHITE"[Notice]"CL_RESET":"); + break; + case MSG_WARNING: //Bright Yellow + strcat(prefix,CL_YELLOW"[Warning]"CL_RESET":"); + break; + case MSG_DEBUG: //Bright Cyan, important stuff! + strcat(prefix,CL_CYAN"[Debug]"CL_RESET":"); + break; + case MSG_ERROR: //Bright Red (Regular errors) + strcat(prefix,CL_RED"[Error]"CL_RESET":"); + break; + case MSG_FATALERROR: //Bright Red (Fatal errors, abort(); if possible) + strcat(prefix,CL_RED"[Fatal Error]"CL_RESET":"); + break; + default: + ShowError("In function _vShowMessage() -> Invalid flag passed.\n"); + return 1; + } + + if (flag == MSG_ERROR || flag == MSG_FATALERROR || flag == MSG_SQL) + { //Send Errors to StdErr [Skotlex] + FPRINTF(STDERR, "%s ", prefix); + va_copy(apcopy, ap); + VFPRINTF(STDERR, string, apcopy); + va_end(apcopy); + FFLUSH(STDERR); + } else { + if (flag != MSG_NONE) + FPRINTF(STDOUT, "%s ", prefix); + va_copy(apcopy, ap); + VFPRINTF(STDOUT, string, apcopy); + va_end(apcopy); + FFLUSH(STDOUT); + } + +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + if(strlen(DEBUGLOGPATH) > 0) { + fp=fopen(DEBUGLOGPATH,"a"); + if (fp == NULL) { + FPRINTF(STDERR, CL_RED"[ERROR]"CL_RESET": Could not open '"CL_WHITE"%s"CL_RESET"', access denied.\n", DEBUGLOGPATH); + FFLUSH(STDERR); + } else { + fprintf(fp,"%s ", prefix); + va_copy(apcopy, ap); + vfprintf(fp,string,apcopy); + va_end(apcopy); + fclose(fp); + } + } else { + FPRINTF(STDERR, CL_RED"[ERROR]"CL_RESET": DEBUGLOGPATH not defined!\n"); + FFLUSH(STDERR); + } +#endif + + return 0; +} + +void ClearScreen(void) +{ +#ifndef _WIN32 + ShowMessage(CL_CLS); // to prevent empty string passed messages +#endif +} +int _ShowMessage(enum msg_type flag, const char *string, ...) +{ + int ret; + va_list ap; + va_start(ap, string); + ret = _vShowMessage(flag, string, ap); + va_end(ap); + return ret; +} + +// direct printf replacement +void ShowMessage(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_NONE, string, ap); + va_end(ap); +} +void ShowStatus(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_STATUS, string, ap); + va_end(ap); +} +void ShowSQL(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_SQL, string, ap); + va_end(ap); +} +void ShowInfo(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_INFORMATION, string, ap); + va_end(ap); +} +void ShowNotice(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_NOTICE, string, ap); + va_end(ap); +} +void ShowWarning(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_WARNING, string, ap); + va_end(ap); +} +void ShowConfigWarning(config_setting_t *config, const char *string, ...) +{ + StringBuf buf; + va_list ap; + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, string); + StringBuf_Printf(&buf, " (%s:%d)\n", config_setting_source_file(config), config_setting_source_line(config)); + va_start(ap, string); + _vShowMessage(MSG_WARNING, StringBuf_Value(&buf), ap); + va_end(ap); + StringBuf_Destroy(&buf); +} +void ShowDebug(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_DEBUG, string, ap); + va_end(ap); +} +void ShowError(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_ERROR, string, ap); + va_end(ap); +} +void ShowFatalError(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_FATALERROR, string, ap); + va_end(ap); +} diff --git a/src/common/showmsg.h b/src/common/showmsg.h new file mode 100644 index 000000000..0d6cb5c9f --- /dev/null +++ b/src/common/showmsg.h @@ -0,0 +1,99 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SHOWMSG_H_ +#define _SHOWMSG_H_ + +#include "libconfig.h" + +// for help with the console colors look here: +// http://www.edoceo.com/liberum/?doc=printf-with-color +// some code explanation (used here): +// \033[2J : clear screen and go up/left (0, 0 position) +// \033[K : clear line from actual position to end of the line +// \033[0m : reset color parameter +// \033[1m : use bold for font + +#define CL_RESET "\033[0m" +#define CL_CLS "\033[2J" +#define CL_CLL "\033[K" + +// font settings +#define CL_BOLD "\033[1m" +#define CL_NORM CL_RESET +#define CL_NORMAL CL_RESET +#define CL_NONE CL_RESET +// foreground color and bold font (bright color on windows) +#define CL_WHITE "\033[1;37m" +#define CL_GRAY "\033[1;30m" +#define CL_RED "\033[1;31m" +#define CL_GREEN "\033[1;32m" +#define CL_YELLOW "\033[1;33m" +#define CL_BLUE "\033[1;34m" +#define CL_MAGENTA "\033[1;35m" +#define CL_CYAN "\033[1;36m" + +// background color +#define CL_BG_BLACK "\033[40m" +#define CL_BG_RED "\033[41m" +#define CL_BG_GREEN "\033[42m" +#define CL_BG_YELLOW "\033[43m" +#define CL_BG_BLUE "\033[44m" +#define CL_BG_MAGENTA "\033[45m" +#define CL_BG_CYAN "\033[46m" +#define CL_BG_WHITE "\033[47m" +// foreground color and normal font (normal color on windows) +#define CL_LT_BLACK "\033[0;30m" +#define CL_LT_RED "\033[0;31m" +#define CL_LT_GREEN "\033[0;32m" +#define CL_LT_YELLOW "\033[0;33m" +#define CL_LT_BLUE "\033[0;34m" +#define CL_LT_MAGENTA "\033[0;35m" +#define CL_LT_CYAN "\033[0;36m" +#define CL_LT_WHITE "\033[0;37m" +// foreground color and bold font (bright color on windows) +#define CL_BT_BLACK "\033[1;30m" +#define CL_BT_RED "\033[1;31m" +#define CL_BT_GREEN "\033[1;32m" +#define CL_BT_YELLOW "\033[1;33m" +#define CL_BT_BLUE "\033[1;34m" +#define CL_BT_MAGENTA "\033[1;35m" +#define CL_BT_CYAN "\033[1;36m" +#define CL_BT_WHITE "\033[1;37m" + +#define CL_WTBL "\033[37;44m" // white on blue +#define CL_XXBL "\033[0;44m" // default on blue +#define CL_PASS "\033[0;32;42m" // green on green + +#define CL_SPACE " " // space aquivalent of the print messages + +extern int stdout_with_ansisequence; //If the color ansi sequences are to be used. [flaviojs] +extern int msg_silent; //Specifies how silent the console is. [Skotlex] +extern int console_msg_log; //Specifies what error messages to log. [Ind] +extern char timestamp_format[20]; //For displaying Timestamps [Skotlex] + +enum msg_type { + MSG_NONE, + MSG_STATUS, + MSG_SQL, + MSG_INFORMATION, + MSG_NOTICE, + MSG_WARNING, + MSG_DEBUG, + MSG_ERROR, + MSG_FATALERROR +}; + +extern void ClearScreen(void); +extern void ShowMessage(const char *, ...); +extern void ShowStatus(const char *, ...); +extern void ShowSQL(const char *, ...); +extern void ShowInfo(const char *, ...); +extern void ShowNotice(const char *, ...); +extern void ShowWarning(const char *, ...); +extern void ShowDebug(const char *, ...); +extern void ShowError(const char *, ...); +extern void ShowFatalError(const char *, ...); +extern void ShowConfigWarning(config_setting_t *config, const char *string, ...); + +#endif /* _SHOWMSG_H_ */ diff --git a/src/common/socket.c b/src/common/socket.c new file mode 100644 index 000000000..d24a9c1d8 --- /dev/null +++ b/src/common/socket.c @@ -0,0 +1,1456 @@ +// 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/timer.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "socket.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#ifdef WIN32 + #include "../common/winapi.h" +#else + #include <errno.h> + #include <sys/socket.h> + #include <netinet/in.h> + #include <netinet/tcp.h> + #include <net/if.h> + #include <unistd.h> + #include <sys/time.h> + #include <sys/ioctl.h> + #include <netdb.h> + #include <arpa/inet.h> + + #ifndef SIOCGIFCONF + #include <sys/sockio.h> // SIOCGIFCONF on Solaris, maybe others? [Shinomori] + #endif + #ifndef FIONBIO + #include <sys/filio.h> // FIONBIO on Solaris [FlavioJS] + #endif + + #ifdef HAVE_SETRLIMIT + #include <sys/resource.h> + #endif +#endif + +///////////////////////////////////////////////////////////////////// +#if defined(WIN32) +///////////////////////////////////////////////////////////////////// +// windows portability layer + +typedef int socklen_t; + +#define sErrno WSAGetLastError() +#define S_ENOTSOCK WSAENOTSOCK +#define S_EWOULDBLOCK WSAEWOULDBLOCK +#define S_EINTR WSAEINTR +#define S_ECONNABORTED WSAECONNABORTED + +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND +#define SHUT_RDWR SD_BOTH + +// global array of sockets (emulating linux) +// fd is the position in the array +static SOCKET sock_arr[FD_SETSIZE]; +static int sock_arr_len = 0; + +/// Returns the socket associated with the target fd. +/// +/// @param fd Target fd. +/// @return Socket +#define fd2sock(fd) sock_arr[fd] + +/// Returns the first fd associated with the socket. +/// Returns -1 if the socket is not found. +/// +/// @param s Socket +/// @return Fd or -1 +int sock2fd(SOCKET s) +{ + int fd; + + // search for the socket + for( fd = 1; fd < sock_arr_len; ++fd ) + if( sock_arr[fd] == s ) + break;// found the socket + if( fd == sock_arr_len ) + return -1;// not found + return fd; +} + + +/// Inserts the socket into the global array of sockets. +/// Returns a new fd associated with the socket. +/// If there are too many sockets it closes the socket, sets an error and +// returns -1 instead. +/// Since fd 0 is reserved, it returns values in the range [1,FD_SETSIZE[. +/// +/// @param s Socket +/// @return New fd or -1 +int sock2newfd(SOCKET s) +{ + int fd; + + // find an empty position + for( fd = 1; fd < sock_arr_len; ++fd ) + if( sock_arr[fd] == INVALID_SOCKET ) + break;// empty position + if( fd == ARRAYLENGTH(sock_arr) ) + {// too many sockets + closesocket(s); + WSASetLastError(WSAEMFILE); + return -1; + } + sock_arr[fd] = s; + if( sock_arr_len <= fd ) + sock_arr_len = fd+1; + return fd; +} + +int sAccept(int fd, struct sockaddr* addr, int* addrlen) +{ + SOCKET s; + + // accept connection + s = accept(fd2sock(fd), addr, addrlen); + if( s == INVALID_SOCKET ) + return -1;// error + return sock2newfd(s); +} + +int sClose(int fd) +{ + int ret = closesocket(fd2sock(fd)); + fd2sock(fd) = INVALID_SOCKET; + return ret; +} + +int sSocket(int af, int type, int protocol) +{ + SOCKET s; + + // create socket + s = socket(af,type,protocol); + if( s == INVALID_SOCKET ) + return -1;// error + return sock2newfd(s); +} + +char* sErr(int code) +{ + static char sbuf[512]; + // strerror does not handle socket codes + if( FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + code, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPTSTR)&sbuf, sizeof(sbuf), NULL) == 0 ) + snprintf(sbuf, sizeof(sbuf), "unknown error"); + return sbuf; +} + +#define sBind(fd,name,namelen) bind(fd2sock(fd),name,namelen) +#define sConnect(fd,name,namelen) connect(fd2sock(fd),name,namelen) +#define sIoctl(fd,cmd,argp) ioctlsocket(fd2sock(fd),cmd,argp) +#define sListen(fd,backlog) listen(fd2sock(fd),backlog) +#define sRecv(fd,buf,len,flags) recv(fd2sock(fd),buf,len,flags) +#define sSelect select +#define sSend(fd,buf,len,flags) send(fd2sock(fd),buf,len,flags) +#define sSetsockopt(fd,level,optname,optval,optlen) setsockopt(fd2sock(fd),level,optname,optval,optlen) +#define sShutdown(fd,how) shutdown(fd2sock(fd),how) +#define sFD_SET(fd,set) FD_SET(fd2sock(fd),set) +#define sFD_CLR(fd,set) FD_CLR(fd2sock(fd),set) +#define sFD_ISSET(fd,set) FD_ISSET(fd2sock(fd),set) +#define sFD_ZERO FD_ZERO + +///////////////////////////////////////////////////////////////////// +#else +///////////////////////////////////////////////////////////////////// +// nix portability layer + +#define SOCKET_ERROR (-1) + +#define sErrno errno +#define S_ENOTSOCK EBADF +#define S_EWOULDBLOCK EAGAIN +#define S_EINTR EINTR +#define S_ECONNABORTED ECONNABORTED + +#define sAccept accept +#define sClose close +#define sSocket socket +#define sErr strerror + +#define sBind bind +#define sConnect connect +#define sIoctl ioctl +#define sListen listen +#define sRecv recv +#define sSelect select +#define sSend send +#define sSetsockopt setsockopt +#define sShutdown shutdown +#define sFD_SET FD_SET +#define sFD_CLR FD_CLR +#define sFD_ISSET FD_ISSET +#define sFD_ZERO FD_ZERO + +///////////////////////////////////////////////////////////////////// +#endif +///////////////////////////////////////////////////////////////////// + +#ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 +#endif + +fd_set readfds; +int fd_max; +time_t last_tick; +time_t stall_time = 60; + +uint32 addr_[16]; // ip addresses of local host (host byte order) +int naddr_ = 0; // # of ip addresses + +// Maximum packet size in bytes, which the client is able to handle. +// Larger packets cause a buffer overflow and stack corruption. +static size_t socket_max_client_packet = 24576; + +// initial recv buffer size (this will also be the max. size) +// biggest known packet: S 0153 <len>.w <emblem data>.?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes) +#define RFIFO_SIZE (2*1024) +// initial send buffer size (will be resized as needed) +#define WFIFO_SIZE (16*1024) + +// Maximum size of pending data in the write fifo. (for non-server connections) +// The connection is closed if it goes over the limit. +#define WFIFO_MAX (1*1024*1024) + +struct socket_data* session[FD_SETSIZE]; + +#ifdef SEND_SHORTLIST +int send_shortlist_array[FD_SETSIZE];// we only support FD_SETSIZE sockets, limit the array to that +int send_shortlist_count = 0;// how many fd's are in the shortlist +uint32 send_shortlist_set[(FD_SETSIZE+31)/32];// to know if specific fd's are already in the shortlist +#endif + +static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse); + +#ifndef MINICORE + int ip_rules = 1; + static int connect_check(uint32 ip); +#endif + +const char* error_msg(void) +{ + static char buf[512]; + int code = sErrno; + snprintf(buf, sizeof(buf), "error %d: %s", code, sErr(code)); + return buf; +} + +/*====================================== + * CORE : Default processing functions + *--------------------------------------*/ +int null_recv(int fd) { return 0; } +int null_send(int fd) { return 0; } +int null_parse(int fd) { return 0; } + +ParseFunc default_func_parse = null_parse; + +void set_defaultparse(ParseFunc defaultparse) +{ + default_func_parse = defaultparse; +} + + +/*====================================== + * CORE : Socket options + *--------------------------------------*/ +void set_nonblocking(int fd, unsigned long yes) +{ + // FIONBIO Use with a nonzero argp parameter to enable the nonblocking mode of socket s. + // The argp parameter is zero if nonblocking is to be disabled. + if( sIoctl(fd, FIONBIO, &yes) != 0 ) + ShowError("set_nonblocking: Failed to set socket #%d to non-blocking mode (%s) - Please report this!!!\n", fd, error_msg()); +} + +void setsocketopts(int fd) +{ + int yes = 1; // reuse fix +#if !defined(WIN32) + // set SO_REAUSEADDR to true, unix only. on windows this option causes + // the previous owner of the socket to give up, which is not desirable + // in most cases, neither compatible with unix. + sSetsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof(yes)); +#ifdef SO_REUSEPORT + sSetsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof(yes)); +#endif +#endif + + // Set the socket into no-delay mode; otherwise packets get delayed for up to 200ms, likely creating server-side lag. + // The RO protocol is mainly single-packet request/response, plus the FIFO model already does packet grouping anyway. + sSetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&yes, sizeof(yes)); + + // force the socket into no-wait, graceful-close mode (should be the default, but better make sure) + //(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/closesocket_2.asp) + { + struct linger opt; + opt.l_onoff = 0; // SO_DONTLINGER + opt.l_linger = 0; // Do not care + if( sSetsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&opt, sizeof(opt)) ) + ShowWarning("setsocketopts: Unable to set SO_LINGER mode for connection #%d!\n", fd); + } +} + +/*====================================== + * CORE : Socket Sub Function + *--------------------------------------*/ +void set_eof(int fd) +{ + if( session_isActive(fd) ) + { +#ifdef SEND_SHORTLIST + // Add this socket to the shortlist for eof handling. + send_shortlist_add_fd(fd); +#endif + session[fd]->flag.eof = 1; + } +} + +int recv_to_fifo(int fd) +{ + int len; + + if( !session_isActive(fd) ) + return -1; + + len = sRecv(fd, (char *) session[fd]->rdata + session[fd]->rdata_size, (int)RFIFOSPACE(fd), 0); + + if( len == SOCKET_ERROR ) + {//An exception has occured + if( sErrno != S_EWOULDBLOCK ) { + //ShowDebug("recv_to_fifo: %s, closing connection #%d\n", error_msg(), fd); + set_eof(fd); + } + return 0; + } + + if( len == 0 ) + {//Normal connection end. + set_eof(fd); + return 0; + } + + session[fd]->rdata_size += len; + session[fd]->rdata_tick = last_tick; + return 0; +} + +int send_from_fifo(int fd) +{ + int len; + + if( !session_isValid(fd) ) + return -1; + + if( session[fd]->wdata_size == 0 ) + return 0; // nothing to send + + len = sSend(fd, (const char *) session[fd]->wdata, (int)session[fd]->wdata_size, MSG_NOSIGNAL); + + if( len == SOCKET_ERROR ) + {//An exception has occured + if( sErrno != S_EWOULDBLOCK ) { + //ShowDebug("send_from_fifo: %s, ending connection #%d\n", error_msg(), fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } + + if( len > 0 ) + { + // some data could not be transferred? + // shift unsent data to the beginning of the queue + if( (size_t)len < session[fd]->wdata_size ) + memmove(session[fd]->wdata, session[fd]->wdata + len, session[fd]->wdata_size - len); + + session[fd]->wdata_size -= len; + } + + return 0; +} + +/// Best effort - there's no warranty that the data will be sent. +void flush_fifo(int fd) +{ + if(session[fd] != NULL) + session[fd]->func_send(fd); +} + +void flush_fifos(void) +{ + int i; + for(i = 1; i < fd_max; i++) + flush_fifo(i); +} + +/*====================================== + * CORE : Connection functions + *--------------------------------------*/ +int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; + socklen_t len; + + len = sizeof(client_address); + + fd = sAccept(listen_fd, (struct sockaddr*)&client_address, &len); + if ( fd == -1 ) { + ShowError("connect_client: accept failed (%s)!\n", error_msg()); + return -1; + } + if( fd == 0 ) + {// reserved + ShowError("connect_client: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("connect_client: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + set_nonblocking(fd, 1); + +#ifndef MINICORE + if( ip_rules && !connect_check(ntohl(client_address.sin_addr.s_addr)) ) { + do_close(fd); + return -1; + } +#endif + + if( fd_max <= fd ) fd_max = fd + 1; + sFD_SET(fd,&readfds); + + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); + session[fd]->client_addr = ntohl(client_address.sin_addr.s_addr); + + return fd; +} + +int make_listen_bind(uint32 ip, uint16 port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + if( fd == -1 ) + { + ShowError("make_listen_bind: socket creation failed (%s)!\n", error_msg()); + exit(EXIT_FAILURE); + } + if( fd == 0 ) + {// reserved + ShowError("make_listen_bind: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("make_listen_bind: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + set_nonblocking(fd, 1); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl(ip); + server_address.sin_port = htons(port); + + result = sBind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); + if( result == SOCKET_ERROR ) { + ShowError("make_listen_bind: bind failed (socket #%d, %s)!\n", fd, error_msg()); + exit(EXIT_FAILURE); + } + result = sListen(fd,5); + if( result == SOCKET_ERROR ) { + ShowError("make_listen_bind: listen failed (socket #%d, %s)!\n", fd, error_msg()); + exit(EXIT_FAILURE); + } + + if(fd_max <= fd) fd_max = fd + 1; + sFD_SET(fd, &readfds); + + create_session(fd, connect_client, null_send, null_parse); + session[fd]->client_addr = 0; // just listens + session[fd]->rdata_tick = 0; // disable timeouts on this socket + + return fd; +} + +int make_connection(uint32 ip, uint16 port, bool silent) { + struct sockaddr_in remote_address; + int fd; + int result; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + if (fd == -1) { + ShowError("make_connection: socket creation failed (%s)!\n", error_msg()); + return -1; + } + if( fd == 0 ) + {// reserved + ShowError("make_connection: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("make_connection: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + + remote_address.sin_family = AF_INET; + remote_address.sin_addr.s_addr = htonl(ip); + remote_address.sin_port = htons(port); + + if( !silent ) + ShowStatus("Connecting to %d.%d.%d.%d:%i\n", CONVIP(ip), port); + + result = sConnect(fd, (struct sockaddr *)(&remote_address), sizeof(struct sockaddr_in)); + if( result == SOCKET_ERROR ) { + if( !silent ) + ShowError("make_connection: connect failed (socket #%d, %s)!\n", fd, error_msg()); + do_close(fd); + return -1; + } + //Now the socket can be made non-blocking. [Skotlex] + set_nonblocking(fd, 1); + + if (fd_max <= fd) fd_max = fd + 1; + sFD_SET(fd,&readfds); + + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); + session[fd]->client_addr = ntohl(remote_address.sin_addr.s_addr); + + return fd; +} + +static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse) +{ + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, unsigned char, RFIFO_SIZE); + CREATE(session[fd]->wdata, unsigned char, WFIFO_SIZE); + session[fd]->max_rdata = RFIFO_SIZE; + session[fd]->max_wdata = WFIFO_SIZE; + session[fd]->func_recv = func_recv; + session[fd]->func_send = func_send; + session[fd]->func_parse = func_parse; + session[fd]->rdata_tick = last_tick; + return 0; +} + +static void delete_session(int fd) +{ + if( session_isValid(fd) ) + { + aFree(session[fd]->rdata); + aFree(session[fd]->wdata); + aFree(session[fd]->session_data); + aFree(session[fd]); + session[fd] = NULL; + } +} + +int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size) +{ + if( !session_isValid(fd) ) + return 0; + + if( session[fd]->max_rdata != rfifo_size && session[fd]->rdata_size < rfifo_size) { + RECREATE(session[fd]->rdata, unsigned char, rfifo_size); + session[fd]->max_rdata = rfifo_size; + } + + if( session[fd]->max_wdata != wfifo_size && session[fd]->wdata_size < wfifo_size) { + RECREATE(session[fd]->wdata, unsigned char, wfifo_size); + session[fd]->max_wdata = wfifo_size; + } + return 0; +} + +int realloc_writefifo(int fd, size_t addition) +{ + size_t newsize; + + if( !session_isValid(fd) ) // might not happen + return 0; + + if( session[fd]->wdata_size + addition > session[fd]->max_wdata ) + { // grow rule; grow in multiples of WFIFO_SIZE + newsize = WFIFO_SIZE; + while( session[fd]->wdata_size + addition > newsize ) newsize += WFIFO_SIZE; + } + else + if( session[fd]->max_wdata >= (size_t)2*(session[fd]->flag.server?FIFOSIZE_SERVERLINK:WFIFO_SIZE) + && (session[fd]->wdata_size+addition)*4 < session[fd]->max_wdata ) + { // shrink rule, shrink by 2 when only a quarter of the fifo is used, don't shrink below nominal size. + newsize = session[fd]->max_wdata / 2; + } + else // no change + return 0; + + RECREATE(session[fd]->wdata, unsigned char, newsize); + session[fd]->max_wdata = newsize; + + return 0; +} + +/// advance the RFIFO cursor (marking 'len' bytes as processed) +int RFIFOSKIP(int fd, size_t len) +{ + struct socket_data *s; + + if ( !session_isActive(fd) ) + return 0; + + s = session[fd]; + + if ( s->rdata_size < s->rdata_pos + len ) { + ShowError("RFIFOSKIP: skipped past end of read buffer! Adjusting from %d to %d (session #%d)\n", len, RFIFOREST(fd), fd); + len = RFIFOREST(fd); + } + + s->rdata_pos = s->rdata_pos + len; + return 0; +} + +/// advance the WFIFO cursor (marking 'len' bytes for sending) +int WFIFOSET(int fd, size_t len) +{ + size_t newreserve; + struct socket_data* s = session[fd]; + + if( !session_isValid(fd) || s->wdata == NULL ) + return 0; + + // we have written len bytes to the buffer already before calling WFIFOSET + if(s->wdata_size+len > s->max_wdata) + { // actually there was a buffer overflow already + uint32 ip = s->client_addr; + ShowFatalError("WFIFOSET: Write Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %u bytes on a %u/%u bytes buffer.\n", fd, CONVIP(ip), (unsigned int)len, (unsigned int)s->wdata_size, (unsigned int)s->max_wdata); + ShowDebug("Likely command that caused it: 0x%x\n", (*(uint16*)(s->wdata + s->wdata_size))); + // no other chance, make a better fifo model + exit(EXIT_FAILURE); + } + + if( len > 0xFFFF ) + { + // dynamic packets allow up to UINT16_MAX bytes (<packet_id>.W <packet_len>.W ...) + // all known fixed-size packets are within this limit, so use the same limit + ShowFatalError("WFIFOSET: Packet 0x%x is too big. (len=%u, max=%u)\n", (*(uint16*)(s->wdata + s->wdata_size)), (unsigned int)len, 0xFFFF); + exit(EXIT_FAILURE); + } + else if( len == 0 ) + { + // abuses the fact, that the code that did WFIFOHEAD(fd,0), already wrote + // the packet type into memory, even if it could have overwritten vital data + // this can happen when a new packet was added on map-server, but packet len table was not updated + ShowWarning("WFIFOSET: Attempted to send zero-length packet, most likely 0x%04x (please report this).\n", WFIFOW(fd,0)); + return 0; + } + + if( !s->flag.server ) { + + if( len > socket_max_client_packet ) {// see declaration of socket_max_client_packet for details + ShowError("WFIFOSET: Dropped too large client packet 0x%04x (length=%u, max=%u).\n", WFIFOW(fd,0), len, socket_max_client_packet); + return 0; + } + + if( s->wdata_size+len > WFIFO_MAX ) {// reached maximum write fifo size + ShowError("WFIFOSET: Maximum write buffer size for client connection %d exceeded, most likely caused by packet 0x%04x (len=%u, ip=%lu.%lu.%lu.%lu).\n", fd, WFIFOW(fd,0), len, CONVIP(s->client_addr)); + set_eof(fd); + return 0; + } + + } + s->wdata_size += len; + //If the interserver has 200% of its normal size full, flush the data. + if( s->flag.server && s->wdata_size >= 2*FIFOSIZE_SERVERLINK ) + flush_fifo(fd); + + // always keep a WFIFO_SIZE reserve in the buffer + // For inter-server connections, let the reserve be 1/4th of the link size. + newreserve = s->flag.server ? FIFOSIZE_SERVERLINK / 4 : WFIFO_SIZE; + + // readjust the buffer to include the chosen reserve + realloc_writefifo(fd, newreserve); + +#ifdef SEND_SHORTLIST + send_shortlist_add_fd(fd); +#endif + + return 0; +} + +int do_sockets(int next) +{ + fd_set rfd; + struct timeval timeout; + int ret,i; + + // PRESEND Timers are executed before do_sendrecv and can send packets and/or set sessions to eof. + // Send remaining data and process client-side disconnects here. +#ifdef SEND_SHORTLIST + send_shortlist_do_sends(); +#else + for (i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if(session[i]->wdata_size) + session[i]->func_send(i); + } +#endif + + // can timeout until the next tick + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + + memcpy(&rfd, &readfds, sizeof(rfd)); + ret = sSelect(fd_max, &rfd, NULL, NULL, &timeout); + + if( ret == SOCKET_ERROR ) + { + if( sErrno != S_EINTR ) + { + ShowFatalError("do_sockets: select() failed, %s!\n", error_msg()); + exit(EXIT_FAILURE); + } + return 0; // interrupted by a signal, just loop and try again + } + + last_tick = time(NULL); + +#if defined(WIN32) + // on windows, enumerating all members of the fd_set is way faster if we access the internals + for( i = 0; i < (int)rfd.fd_count; ++i ) + { + int fd = sock2fd(rfd.fd_array[i]); + if( session[fd] ) + session[fd]->func_recv(fd); + } +#else + // otherwise assume that the fd_set is a bit-array and enumerate it in a standard way + for( i = 1; ret && i < fd_max; ++i ) + { + if(sFD_ISSET(i,&rfd) && session[i]) + { + session[i]->func_recv(i); + --ret; + } + } +#endif + + // POSTSEND Send remaining data and handle eof sessions. +#ifdef SEND_SHORTLIST + send_shortlist_do_sends(); +#else + for (i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if(session[i]->wdata_size) + session[i]->func_send(i); + + if(session[i]->flag.eof) //func_send can't free a session, this is safe. + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + session[i]->func_parse(i); //This should close the session immediately. + } + } +#endif + + // parse input data on each socket + for(i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if (session[i]->rdata_tick && DIFF_TICK(last_tick, session[i]->rdata_tick) > stall_time) { + if( session[i]->flag.server ) {/* server is special */ + if( session[i]->flag.ping != 2 )/* only update if necessary otherwise it'd resend the ping unnecessarily */ + session[i]->flag.ping = 1; + } else { + ShowInfo("Session #%d timed out\n", i); + set_eof(i); + } + } + + session[i]->func_parse(i); + + if(!session[i]) + continue; + + // after parse, check client's RFIFO size to know if there is an invalid packet (too big and not parsed) + if (session[i]->rdata_size == RFIFO_SIZE && session[i]->max_rdata == RFIFO_SIZE) { + set_eof(i); + continue; + } + RFIFOFLUSH(i); + } + + return 0; +} + +////////////////////////////// +#ifndef MINICORE +////////////////////////////// +// IP rules and DDoS protection + +typedef struct _connect_history { + struct _connect_history* next; + uint32 ip; + uint32 tick; + int count; + unsigned ddos : 1; +} ConnectHistory; + +typedef struct _access_control { + uint32 ip; + uint32 mask; +} AccessControl; + +enum _aco { + ACO_DENY_ALLOW, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILURE +}; + +static AccessControl* access_allow = NULL; +static AccessControl* access_deny = NULL; +static int access_order = ACO_DENY_ALLOW; +static int access_allownum = 0; +static int access_denynum = 0; +static int access_debug = 0; +static int ddos_count = 10; +static int ddos_interval = 3*1000; +static int ddos_autoreset = 10*60*1000; +/// Connection history, an array of linked lists. +/// The array's index for any ip is ip&0xFFFF +static ConnectHistory* connect_history[0x10000]; + +static int connect_check_(uint32 ip); + +/// Verifies if the IP can connect. (with debug info) +/// @see connect_check_() +static int connect_check(uint32 ip) +{ + int result = connect_check_(ip); + if( access_debug ) { + ShowInfo("connect_check: Connection from %d.%d.%d.%d %s\n", CONVIP(ip),result ? "allowed." : "denied!"); + } + return result; +} + +/// Verifies if the IP can connect. +/// 0 : Connection Rejected +/// 1 or 2 : Connection Accepted +static int connect_check_(uint32 ip) +{ + ConnectHistory* hist = connect_history[ip&0xFFFF]; + int i; + int is_allowip = 0; + int is_denyip = 0; + int connect_ok = 0; + + // Search the allow list + for( i=0; i < access_allownum; ++i ){ + if( (ip & access_allow[i].mask) == (access_allow[i].ip & access_allow[i].mask) ){ + if( access_debug ){ + ShowInfo("connect_check: Found match from allow list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_allow[i].ip), + CONVIP(access_allow[i].mask)); + } + is_allowip = 1; + break; + } + } + // Search the deny list + for( i=0; i < access_denynum; ++i ){ + if( (ip & access_deny[i].mask) == (access_deny[i].ip & access_deny[i].mask) ){ + if( access_debug ){ + ShowInfo("connect_check: Found match from deny list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_deny[i].ip), + CONVIP(access_deny[i].mask)); + } + is_denyip = 1; + break; + } + } + // Decide connection status + // 0 : Reject + // 1 : Accept + // 2 : Unconditional Accept (accepts even if flagged as DDoS) + switch(access_order) { + case ACO_DENY_ALLOW: + default: + if( is_denyip ) + connect_ok = 0; // Reject + else if( is_allowip ) + connect_ok = 2; // Unconditional Accept + else + connect_ok = 1; // Accept + break; + case ACO_ALLOW_DENY: + if( is_allowip ) + connect_ok = 2; // Unconditional Accept + else if( is_denyip ) + connect_ok = 0; // Reject + else + connect_ok = 1; // Accept + break; + case ACO_MUTUAL_FAILURE: + if( is_allowip && !is_denyip ) + connect_ok = 2; // Unconditional Accept + else + connect_ok = 0; // Reject + break; + } + + // Inspect connection history + while( hist ) { + if( ip == hist->ip ) + {// IP found + if( hist->ddos ) + {// flagged as DDoS + return (connect_ok == 2 ? 1 : 0); + } else if( DIFF_TICK(gettick(),hist->tick) < ddos_interval ) + {// connection within ddos_interval + hist->tick = gettick(); + if( hist->count++ >= ddos_count ) + {// DDoS attack detected + hist->ddos = 1; + ShowWarning("connect_check: DDoS Attack detected from %d.%d.%d.%d!\n", CONVIP(ip)); + return (connect_ok == 2 ? 1 : 0); + } + return connect_ok; + } else + {// not within ddos_interval, clear data + hist->tick = gettick(); + hist->count = 0; + return connect_ok; + } + } + hist = hist->next; + } + // IP not found, add to history + CREATE(hist, ConnectHistory, 1); + memset(hist, 0, sizeof(ConnectHistory)); + hist->ip = ip; + hist->tick = gettick(); + hist->next = connect_history[ip&0xFFFF]; + connect_history[ip&0xFFFF] = hist; + return connect_ok; +} + +/// Timer function. +/// Deletes old connection history records. +static int connect_check_clear(int tid, unsigned int tick, int id, intptr_t data) +{ + int i; + int clear = 0; + int list = 0; + ConnectHistory root; + ConnectHistory* prev_hist; + ConnectHistory* hist; + + for( i=0; i < 0x10000 ; ++i ){ + prev_hist = &root; + root.next = hist = connect_history[i]; + while( hist ){ + if( (!hist->ddos && DIFF_TICK(tick,hist->tick) > ddos_interval*3) || + (hist->ddos && DIFF_TICK(tick,hist->tick) > ddos_autoreset) ) + {// Remove connection history + prev_hist->next = hist->next; + aFree(hist); + hist = prev_hist->next; + clear++; + } else { + prev_hist = hist; + hist = hist->next; + } + list++; + } + connect_history[i] = root.next; + } + if( access_debug ){ + ShowInfo("connect_check_clear: Cleared %d of %d from IP list.\n", clear, list); + } + return list; +} + +/// Parses the ip address and mask and puts it into acc. +/// Returns 1 is successful, 0 otherwise. +int access_ipmask(const char* str, AccessControl* acc) +{ + uint32 ip; + uint32 mask; + unsigned int a[4]; + unsigned int m[4]; + int n; + + if( strcmp(str,"all") == 0 ) { + ip = 0; + mask = 0; + } else { + if( ((n=sscanf(str,"%u.%u.%u.%u/%u.%u.%u.%u",a,a+1,a+2,a+3,m,m+1,m+2,m+3)) != 8 && // not an ip + standard mask + (n=sscanf(str,"%u.%u.%u.%u/%u",a,a+1,a+2,a+3,m)) != 5 && // not an ip + bit mask + (n=sscanf(str,"%u.%u.%u.%u",a,a+1,a+2,a+3)) != 4 ) || // not an ip + a[0] > 255 || a[1] > 255 || a[2] > 255 || a[3] > 255 || // invalid ip + (n == 8 && (m[0] > 255 || m[1] > 255 || m[2] > 255 || m[3] > 255)) || // invalid standard mask + (n == 5 && m[0] > 32) ){ // invalid bit mask + return 0; + } + ip = MAKEIP(a[0],a[1],a[2],a[3]); + if( n == 8 ) + {// standard mask + mask = MAKEIP(m[0],m[1],m[2],m[3]); + } else if( n == 5 ) + {// bit mask + mask = 0; + while( m[0] ){ + mask = (mask >> 1) | 0x80000000; + --m[0]; + } + } else + {// just this ip + mask = 0xFFFFFFFF; + } + } + if( access_debug ){ + ShowInfo("access_ipmask: Loaded IP:%d.%d.%d.%d mask:%d.%d.%d.%d\n", CONVIP(ip), CONVIP(mask)); + } + acc->ip = ip; + acc->mask = mask; + return 1; +} +////////////////////////////// +#endif +////////////////////////////// + +int socket_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, "stall_time")) { + stall_time = atoi(w2); + if( stall_time < 3 ) + stall_time = 3;/* a minimum is required to refrain it from killing itself */ + } +#ifndef MINICORE + else if (!strcmpi(w1, "enable_ip_rules")) { + ip_rules = config_switch(w2); + } else if (!strcmpi(w1, "order")) { + if (!strcmpi(w2, "deny,allow")) + access_order = ACO_DENY_ALLOW; + else if (!strcmpi(w2, "allow,deny")) + access_order = ACO_ALLOW_DENY; + else if (!strcmpi(w2, "mutual-failure")) + access_order = ACO_MUTUAL_FAILURE; + } else if (!strcmpi(w1, "allow")) { + RECREATE(access_allow, AccessControl, access_allownum+1); + if (access_ipmask(w2, &access_allow[access_allownum])) + ++access_allownum; + else + ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); + } else if (!strcmpi(w1, "deny")) { + RECREATE(access_deny, AccessControl, access_denynum+1); + if (access_ipmask(w2, &access_deny[access_denynum])) + ++access_denynum; + else + ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); + } + else if (!strcmpi(w1,"ddos_interval")) + ddos_interval = atoi(w2); + else if (!strcmpi(w1,"ddos_count")) + ddos_count = atoi(w2); + else if (!strcmpi(w1,"ddos_autoreset")) + ddos_autoreset = atoi(w2); + else if (!strcmpi(w1,"debug")) + access_debug = config_switch(w2); + else if (!strcmpi(w1,"socket_max_client_packet")) + socket_max_client_packet = strtoul(w2, NULL, 0); +#endif + else if (!strcmpi(w1, "import")) + socket_config_read(w2); + else + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + + fclose(fp); + return 0; +} + + +void socket_final(void) +{ + int i; +#ifndef MINICORE + ConnectHistory* hist; + ConnectHistory* next_hist; + + for( i=0; i < 0x10000; ++i ){ + hist = connect_history[i]; + while( hist ){ + next_hist = hist->next; + aFree(hist); + hist = next_hist; + } + } + if( access_allow ) + aFree(access_allow); + if( access_deny ) + aFree(access_deny); +#endif + + for( i = 1; i < fd_max; i++ ) + if(session[i]) + do_close(i); + + // session[0] のダミーデータを削除 + aFree(session[0]->rdata); + aFree(session[0]->wdata); + aFree(session[0]); +} + +/// Closes a socket. +void do_close(int fd) +{ + if( fd <= 0 ||fd >= FD_SETSIZE ) + return;// invalid + + flush_fifo(fd); // Try to send what's left (although it might not succeed since it's a nonblocking socket) + sFD_CLR(fd, &readfds);// this needs to be done before closing the socket + sShutdown(fd, SHUT_RDWR); // Disallow further reads/writes + sClose(fd); // We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. + if (session[fd]) delete_session(fd); +} + +/// Retrieve local ips in host byte order. +/// Uses loopback is no address is found. +int socket_getips(uint32* ips, int max) +{ + int num = 0; + + if( ips == NULL || max <= 0 ) + return 0; + +#ifdef WIN32 + { + char fullhost[255]; + u_long** a; + struct hostent* hent; + + // XXX This should look up the local IP addresses in the registry + // instead of calling gethostbyname. However, the way IP addresses + // are stored in the registry is annoyingly complex, so I'll leave + // this as T.B.D. [Meruru] + if( gethostname(fullhost, sizeof(fullhost)) == SOCKET_ERROR ) + { + ShowError("socket_getips: No hostname defined!\n"); + return 0; + } + else + { + hent = gethostbyname(fullhost); + if( hent == NULL ){ + ShowError("socket_getips: Cannot resolve our own hostname to an IP address\n"); + return 0; + } + a = (u_long**)hent->h_addr_list; + for( ; a[num] != NULL && num < max; ++num) + ips[num] = (uint32)ntohl(*a[num]); + } + } +#else // not WIN32 + { + int pos; + int fd; + char buf[2*16*sizeof(struct ifreq)]; + struct ifconf ic; + struct ifreq* ir; + struct sockaddr_in* a; + u_long ad; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + memset(buf, 0x00, sizeof(buf)); + + // The ioctl call will fail with Invalid Argument if there are more + // interfaces than will fit in the buffer + ic.ifc_len = sizeof(buf); + ic.ifc_buf = buf; + if( sIoctl(fd, SIOCGIFCONF, &ic) == -1 ) + { + ShowError("socket_getips: SIOCGIFCONF failed!\n"); + return 0; + } + else + { + for( pos=0; pos < ic.ifc_len && num < max; ) + { + ir = (struct ifreq*)(buf+pos); + a = (struct sockaddr_in*) &(ir->ifr_addr); + if( a->sin_family == AF_INET ){ + ad = ntohl(a->sin_addr.s_addr); + if( ad != INADDR_LOOPBACK && ad != INADDR_ANY ) + ips[num++] = (uint32)ad; + } + #if (defined(BSD) && BSD >= 199103) || defined(_AIX) || defined(__APPLE__) + pos += ir->ifr_addr.sa_len + sizeof(ir->ifr_name); + #else// not AIX or APPLE + pos += sizeof(struct ifreq); + #endif//not AIX or APPLE + } + } + sClose(fd); + } +#endif // not W32 + + // Use loopback if no ips are found + if( num == 0 ) + ips[num++] = (uint32)INADDR_LOOPBACK; + + return num; +} + +void socket_init(void) +{ + char *SOCKET_CONF_FILENAME = "conf/packet_athena.conf"; + unsigned int rlim_cur = FD_SETSIZE; + +#ifdef WIN32 + {// Start up windows networking + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 0); + if( WSAStartup(wVersionRequested, &wsaData) != 0 ) + { + ShowError("socket_init: WinSock not available!\n"); + return; + } + if( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0 ) + { + ShowError("socket_init: WinSock version mismatch (2.0 or compatible required)!\n"); + return; + } + } +#elif defined(HAVE_SETRLIMIT) && !defined(CYGWIN) + // NOTE: getrlimit and setrlimit have bogus behaviour in cygwin. + // "Number of fds is virtually unlimited in cygwin" (sys/param.h) + {// set socket limit to FD_SETSIZE + struct rlimit rlp; + if( 0 == getrlimit(RLIMIT_NOFILE, &rlp) ) + { + rlp.rlim_cur = FD_SETSIZE; + if( 0 != setrlimit(RLIMIT_NOFILE, &rlp) ) + {// failed, try setting the maximum too (permission to change system limits is required) + rlp.rlim_max = FD_SETSIZE; + if( 0 != setrlimit(RLIMIT_NOFILE, &rlp) ) + {// failed + const char *errmsg = error_msg(); + int rlim_ori; + // set to maximum allowed + getrlimit(RLIMIT_NOFILE, &rlp); + rlim_ori = (int)rlp.rlim_cur; + rlp.rlim_cur = rlp.rlim_max; + setrlimit(RLIMIT_NOFILE, &rlp); + // report limit + getrlimit(RLIMIT_NOFILE, &rlp); + rlim_cur = rlp.rlim_cur; + ShowWarning("socket_init: failed to set socket limit to %d, setting to maximum allowed (original limit=%d, current limit=%d, maximum allowed=%d, %s).\n", FD_SETSIZE, rlim_ori, (int)rlp.rlim_cur, (int)rlp.rlim_max, errmsg); + } + } + } + } +#endif + + // Get initial local ips + naddr_ = socket_getips(addr_,16); + + sFD_ZERO(&readfds); +#if defined(SEND_SHORTLIST) + memset(send_shortlist_set, 0, sizeof(send_shortlist_set)); +#endif + + socket_config_read(SOCKET_CONF_FILENAME); + + // initialise last send-receive tick + last_tick = time(NULL); + + // session[0] is now currently used for disconnected sessions of the map server, and as such, + // should hold enough buffer (it is a vacuum so to speak) as it is never flushed. [Skotlex] + create_session(0, null_recv, null_send, null_parse); + +#ifndef MINICORE + // Delete old connection history every 5 minutes + memset(connect_history, 0, sizeof(connect_history)); + add_timer_func_list(connect_check_clear, "connect_check_clear"); + add_timer_interval(gettick()+1000, connect_check_clear, 0, 0, 5*60*1000); +#endif + + ShowInfo("Server supports up to '"CL_WHITE"%u"CL_RESET"' concurrent connections.\n", rlim_cur); +} + + +bool session_isValid(int fd) +{ + return ( fd > 0 && fd < FD_SETSIZE && session[fd] != NULL ); +} + +bool session_isActive(int fd) +{ + return ( session_isValid(fd) && !session[fd]->flag.eof ); +} + +// Resolves hostname into a numeric ip. +uint32 host2ip(const char* hostname) +{ + struct hostent* h = gethostbyname(hostname); + return (h != NULL) ? ntohl(*(uint32*)h->h_addr) : 0; +} + +// Converts a numeric ip into a dot-formatted string. +// Result is placed either into a user-provided buffer or a static system buffer. +const char* ip2str(uint32 ip, char ip_str[16]) +{ + struct in_addr addr; + addr.s_addr = htonl(ip); + return (ip_str == NULL) ? inet_ntoa(addr) : strncpy(ip_str, inet_ntoa(addr), 16); +} + +// Converts a dot-formatted ip string into a numeric ip. +uint32 str2ip(const char* ip_str) +{ + return ntohl(inet_addr(ip_str)); +} + +// Reorders bytes from network to little endian (Windows). +// Neccessary for sending port numbers to the RO client until Gravity notices that they forgot ntohs() calls. +uint16 ntows(uint16 netshort) +{ + return ((netshort & 0xFF) << 8) | ((netshort & 0xFF00) >> 8); +} + +#ifdef SEND_SHORTLIST +// Add a fd to the shortlist so that it'll be recognized as a fd that needs +// sending or eof handling. +void send_shortlist_add_fd(int fd) +{ + int i; + int bit; + + if( !session_isValid(fd) ) + return;// out of range + + i = fd/32; + bit = fd%32; + + if( (send_shortlist_set[i]>>bit)&1 ) + return;// already in the list + + if( send_shortlist_count >= ARRAYLENGTH(send_shortlist_array) ) + { + ShowDebug("send_shortlist_add_fd: shortlist is full, ignoring... (fd=%d shortlist.count=%d shortlist.length=%d)\n", fd, send_shortlist_count, ARRAYLENGTH(send_shortlist_array)); + return; + } + + // set the bit + send_shortlist_set[i] |= 1<<bit; + // Add to the end of the shortlist array. + send_shortlist_array[send_shortlist_count++] = fd; +} + +// Do pending network sends and eof handling from the shortlist. +void send_shortlist_do_sends() +{ + int i; + + for( i = send_shortlist_count-1; i >= 0; --i ) + { + int fd = send_shortlist_array[i]; + int idx = fd/32; + int bit = fd%32; + + // Remove fd from shortlist, move the last fd to the current position + --send_shortlist_count; + send_shortlist_array[i] = send_shortlist_array[send_shortlist_count]; + send_shortlist_array[send_shortlist_count] = 0; + + if( fd <= 0 || fd >= FD_SETSIZE ) + { + ShowDebug("send_shortlist_do_sends: fd is out of range, corrupted memory? (fd=%d)\n", fd); + continue; + } + if( ((send_shortlist_set[idx]>>bit)&1) == 0 ) + { + ShowDebug("send_shortlist_do_sends: fd is not set, why is it in the shortlist? (fd=%d)\n", fd); + continue; + } + send_shortlist_set[idx]&=~(1<<bit);// unset fd + // If this session still exists, perform send operations on it and + // check for the eof state. + if( session[fd] ) + { + // Send data + if( session[fd]->wdata_size ) + session[fd]->func_send(fd); + + // If it's been marked as eof, call the parse func on it so that + // the socket will be immediately closed. + if( session[fd]->flag.eof ) + session[fd]->func_parse(fd); + + // If the session still exists, is not eof and has things left to + // be sent from it we'll re-add it to the shortlist. + if( session[fd] && !session[fd]->flag.eof && session[fd]->wdata_size ) + send_shortlist_add_fd(fd); + } + } +} +#endif diff --git a/src/common/socket.h b/src/common/socket.h new file mode 100644 index 000000000..7c0e02f5d --- /dev/null +++ b/src/common/socket.h @@ -0,0 +1,163 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include "../common/cbasetypes.h" + +#ifdef WIN32 + #include "../common/winapi.h" + typedef long in_addr_t; +#else + #include <sys/types.h> + #include <sys/socket.h> + #include <netinet/in.h> +#endif + +#include <time.h> + +#define FIFOSIZE_SERVERLINK 256*1024 + +// socket I/O macros +#define RFIFOHEAD(fd) +#define WFIFOHEAD(fd, size) do{ if((fd) && session[fd]->wdata_size + (size) > session[fd]->max_wdata ) realloc_writefifo(fd, size); }while(0) +#define RFIFOP(fd,pos) (session[fd]->rdata + session[fd]->rdata_pos + (pos)) +#define WFIFOP(fd,pos) (session[fd]->wdata + session[fd]->wdata_size + (pos)) + +#define RFIFOB(fd,pos) (*(uint8*)RFIFOP(fd,pos)) +#define WFIFOB(fd,pos) (*(uint8*)WFIFOP(fd,pos)) +#define RFIFOW(fd,pos) (*(uint16*)RFIFOP(fd,pos)) +#define WFIFOW(fd,pos) (*(uint16*)WFIFOP(fd,pos)) +#define RFIFOL(fd,pos) (*(uint32*)RFIFOP(fd,pos)) +#define WFIFOL(fd,pos) (*(uint32*)WFIFOP(fd,pos)) +#define RFIFOQ(fd,pos) (*(uint64*)RFIFOP(fd,pos)) +#define WFIFOQ(fd,pos) (*(uint64*)WFIFOP(fd,pos)) +#define RFIFOSPACE(fd) (session[fd]->max_rdata - session[fd]->rdata_size) +#define WFIFOSPACE(fd) (session[fd]->max_wdata - session[fd]->wdata_size) + +#define RFIFOREST(fd) (session[fd]->flag.eof ? 0 : session[fd]->rdata_size - session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) \ + do { \ + if(session[fd]->rdata_size == session[fd]->rdata_pos){ \ + session[fd]->rdata_size = session[fd]->rdata_pos = 0; \ + } else { \ + session[fd]->rdata_size -= session[fd]->rdata_pos; \ + memmove(session[fd]->rdata, session[fd]->rdata+session[fd]->rdata_pos, session[fd]->rdata_size); \ + session[fd]->rdata_pos = 0; \ + } \ + } while(0) + +// buffer I/O macros +#define RBUFP(p,pos) (((uint8*)(p)) + (pos)) +#define RBUFB(p,pos) (*(uint8*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(uint16*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(uint32*)RBUFP((p),(pos))) +#define RBUFQ(p,pos) (*(uint64*)RBUFP((p),(pos))) + +#define WBUFP(p,pos) (((uint8*)(p)) + (pos)) +#define WBUFB(p,pos) (*(uint8*)WBUFP((p),(pos))) +#define WBUFW(p,pos) (*(uint16*)WBUFP((p),(pos))) +#define WBUFL(p,pos) (*(uint32*)WBUFP((p),(pos))) +#define WBUFQ(p,pos) (*(uint64*)WBUFP((p),(pos))) + +#define TOB(n) ((uint8)((n)&UINT8_MAX)) +#define TOW(n) ((uint16)((n)&UINT16_MAX)) +#define TOL(n) ((uint32)((n)&UINT32_MAX)) + + +// Struct declaration +typedef int (*RecvFunc)(int fd); +typedef int (*SendFunc)(int fd); +typedef int (*ParseFunc)(int fd); + +struct socket_data +{ + struct { + unsigned char eof : 1; + unsigned char server : 1; + unsigned char ping : 2; + } flag; + + uint32 client_addr; // remote client address + + uint8 *rdata, *wdata; + size_t max_rdata, max_wdata; + size_t rdata_size, wdata_size; + size_t rdata_pos; + time_t rdata_tick; // time of last recv (for detecting timeouts); zero when timeout is disabled + + RecvFunc func_recv; + SendFunc func_send; + ParseFunc func_parse; + + void* session_data; // stores application-specific data related to the session +}; + + +// Data prototype declaration + +extern struct socket_data* session[FD_SETSIZE]; + +extern int fd_max; + +extern time_t last_tick; +extern time_t stall_time; + +////////////////////////////////// +// some checking on sockets +extern bool session_isValid(int fd); +extern bool session_isActive(int fd); +////////////////////////////////// + +// Function prototype declaration + +int make_listen_bind(uint32 ip, uint16 port); +int make_connection(uint32 ip, uint16 port, bool silent); +int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size); +int realloc_writefifo(int fd, size_t addition); +int WFIFOSET(int fd, size_t len); +int RFIFOSKIP(int fd, size_t len); + +int do_sockets(int next); +void do_close(int fd); +void socket_init(void); +void socket_final(void); + +extern void flush_fifo(int fd); +extern void flush_fifos(void); +extern void set_nonblocking(int fd, unsigned long yes); + +void set_defaultparse(ParseFunc defaultparse); + +// hostname/ip conversion functions +uint32 host2ip(const char* hostname); +const char* ip2str(uint32 ip, char ip_str[16]); +uint32 str2ip(const char* ip_str); +#define CONVIP(ip) ((ip)>>24)&0xFF,((ip)>>16)&0xFF,((ip)>>8)&0xFF,((ip)>>0)&0xFF +#define MAKEIP(a,b,c,d) (uint32)( ( ( (a)&0xFF ) << 24 ) | ( ( (b)&0xFF ) << 16 ) | ( ( (c)&0xFF ) << 8 ) | ( ( (d)&0xFF ) << 0 ) ) +uint16 ntows(uint16 netshort); + +int socket_getips(uint32* ips, int max); + +extern uint32 addr_[16]; // ip addresses of local host (host byte order) +extern int naddr_; // # of ip addresses + +void set_eof(int fd); + +/// Use a shortlist of sockets instead of iterating all sessions for sockets +/// that have data to send or need eof handling. +/// Adapted to use a static array instead of a linked list. +/// +/// @author Buuyo-tama +#define SEND_SHORTLIST + +#ifdef SEND_SHORTLIST +// Add a fd to the shortlist so that it'll be recognized as a fd that needs +// sending done on it. +void send_shortlist_add_fd(int fd); +// Do pending network sends (and eof handling) from the shortlist. +void send_shortlist_do_sends(); +#endif + +#endif /* _SOCKET_H_ */ diff --git a/src/common/spinlock.h b/src/common/spinlock.h new file mode 100644 index 000000000..3419bfdd5 --- /dev/null +++ b/src/common/spinlock.h @@ -0,0 +1,104 @@ +#pragma once +#ifndef _rA_SPINLOCK_H_ +#define _rA_SPINLOCK_H_ + +// +// CAS based Spinlock Implementation +// +// CamelCase names are choosen to be consistent with microsofts winapi +// which implements CriticalSection by this naming... +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#ifdef WIN32 +#include "../common/winapi.h" +#endif + +#include "../common/cbasetypes.h" +#include "../common/atomic.h" +#include "../common/thread.h" + +#ifdef WIN32 + +typedef struct __declspec( align(64) ) SPIN_LOCK{ + volatile LONG lock; + volatile LONG nest; + volatile LONG sync_lock; +} SPIN_LOCK, *PSPIN_LOCK; +#else +typedef struct SPIN_LOCK{ + volatile int32 lock; + volatile int32 nest; // nesting level. + + volatile int32 sync_lock; +} __attribute__((aligned(64))) SPIN_LOCK, *PSPIN_LOCK; +#endif + + + +static forceinline void InitializeSpinLock(PSPIN_LOCK lck){ + lck->lock = 0; + lck->nest = 0; + lck->sync_lock = 0; +} + +static forceinline void FinalizeSpinLock(PSPIN_LOCK lck){ + return; +} + + +#define getsynclock(l) { while(1){ if(InterlockedCompareExchange(l, 1, 0) == 0) break; rathread_yield(); } } +#define dropsynclock(l) { InterlockedExchange(l, 0); } + +static forceinline void EnterSpinLock(PSPIN_LOCK lck){ + int tid = rathread_get_tid(); + + // Get Sync Lock && Check if the requester thread already owns the lock. + // if it owns, increase nesting level + getsynclock(&lck->sync_lock); + if(InterlockedCompareExchange(&lck->lock, tid, tid) == tid){ + InterlockedIncrement(&lck->nest); + dropsynclock(&lck->sync_lock); + return; // Got Lock + } + // drop sync lock + dropsynclock(&lck->sync_lock); + + + // Spin until we've got it ! + while(1){ + + if(InterlockedCompareExchange(&lck->lock, tid, 0) == 0){ + + InterlockedIncrement(&lck->nest); + return; // Got Lock + } + + rathread_yield(); // Force ctxswitch to another thread. + } + +} + + +static forceinline void LeaveSpinLock(PSPIN_LOCK lck){ + int tid = rathread_get_tid(); + + getsynclock(&lck->sync_lock); + + if(InterlockedCompareExchange(&lck->lock, tid, tid) == tid){ // this thread owns the lock. + if(InterlockedDecrement(&lck->nest) == 0) + InterlockedExchange(&lck->lock, 0); // Unlock! + } + + dropsynclock(&lck->sync_lock); +} + + + + +#endif diff --git a/src/common/sql.c b/src/common/sql.c new file mode 100644 index 000000000..800aa89b0 --- /dev/null +++ b/src/common/sql.c @@ -0,0 +1,948 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "sql.h" + +#ifdef WIN32 +#include "../common/winapi.h" +#endif +#include <mysql.h> +#include <string.h>// strlen/strnlen/memcpy/memset +#include <stdlib.h>// strtoul + + + +/// Sql handle +struct Sql +{ + StringBuf buf; + MYSQL handle; + MYSQL_RES* result; + MYSQL_ROW row; + unsigned long* lengths; + int keepalive; +}; + + + +// Column length receiver. +// Takes care of the possible size missmatch between uint32 and unsigned long. +struct s_column_length +{ + uint32* out_length; + unsigned long length; +}; +typedef struct s_column_length s_column_length; + + + +/// Sql statement +struct SqlStmt +{ + StringBuf buf; + MYSQL_STMT* stmt; + MYSQL_BIND* params; + MYSQL_BIND* columns; + s_column_length* column_lengths; + size_t max_params; + size_t max_columns; + bool bind_params; + bool bind_columns; +}; + + + +/////////////////////////////////////////////////////////////////////////////// +// Sql Handle +/////////////////////////////////////////////////////////////////////////////// + + + +/// Allocates and initializes a new Sql handle. +Sql* Sql_Malloc(void) +{ + Sql* self; + + CREATE(self, Sql, 1); + mysql_init(&self->handle); + StringBuf_Init(&self->buf); + self->lengths = NULL; + self->result = NULL; + self->keepalive = INVALID_TIMER; + + return self; +} + + + +static int Sql_P_Keepalive(Sql* self); + +/// Establishes a connection. +int Sql_Connect(Sql* self, const char* user, const char* passwd, const char* host, uint16 port, const char* db) +{ + if( self == NULL ) + return SQL_ERROR; + + StringBuf_Clear(&self->buf); + if( !mysql_real_connect(&self->handle, host, user, passwd, db, (unsigned int)port, NULL/*unix_socket*/, 0/*clientflag*/) ) + { + ShowSQL("%s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + + self->keepalive = Sql_P_Keepalive(self); + if( self->keepalive == INVALID_TIMER ) + { + ShowSQL("Failed to establish keepalive for DB connection!\n"); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + + +/// Retrieves the timeout of the connection. +int Sql_GetTimeout(Sql* self, uint32* out_timeout) +{ + if( self && out_timeout && SQL_SUCCESS == Sql_Query(self, "SHOW VARIABLES LIKE 'wait_timeout'") ) + { + char* data; + size_t len; + if( SQL_SUCCESS == Sql_NextRow(self) && + SQL_SUCCESS == Sql_GetData(self, 1, &data, &len) ) + { + *out_timeout = (uint32)strtoul(data, NULL, 10); + Sql_FreeResult(self); + return SQL_SUCCESS; + } + Sql_FreeResult(self); + } + return SQL_ERROR; +} + + + +/// Retrieves the name of the columns of a table into out_buf, with the separator after each name. +int Sql_GetColumnNames(Sql* self, const char* table, char* out_buf, size_t buf_len, char sep) +{ + char* data; + size_t len; + size_t off = 0; + + if( self == NULL || SQL_ERROR == Sql_Query(self, "EXPLAIN `%s`", table) ) + return SQL_ERROR; + + out_buf[off] = '\0'; + while( SQL_SUCCESS == Sql_NextRow(self) && SQL_SUCCESS == Sql_GetData(self, 0, &data, &len) ) + { + len = strnlen(data, len); + if( off + len + 2 > buf_len ) + { + ShowDebug("Sql_GetColumns: output buffer is too small\n"); + *out_buf = '\0'; + return SQL_ERROR; + } + memcpy(out_buf+off, data, len); + off += len; + out_buf[off++] = sep; + } + out_buf[off] = '\0'; + Sql_FreeResult(self); + return SQL_SUCCESS; +} + + + +/// Changes the encoding of the connection. +int Sql_SetEncoding(Sql* self, const char* encoding) +{ + if( self && mysql_set_character_set(&self->handle, encoding) == 0 ) + return SQL_SUCCESS; + return SQL_ERROR; +} + + + +/// Pings the connection. +int Sql_Ping(Sql* self) +{ + if( self && mysql_ping(&self->handle) == 0 ) + return SQL_SUCCESS; + return SQL_ERROR; +} + + + +/// Wrapper function for Sql_Ping. +/// +/// @private +static int Sql_P_KeepaliveTimer(int tid, unsigned int tick, int id, intptr_t data) +{ + Sql* self = (Sql*)data; + ShowInfo("Pinging SQL server to keep connection alive...\n"); + Sql_Ping(self); + return 0; +} + + + +/// Establishes keepalive (periodic ping) on the connection. +/// +/// @return the keepalive timer id, or INVALID_TIMER +/// @private +static int Sql_P_Keepalive(Sql* self) +{ + uint32 timeout, ping_interval; + + // set a default value first + timeout = 28800; // 8 hours + + // request the timeout value from the mysql server + Sql_GetTimeout(self, &timeout); + + if( timeout < 60 ) + timeout = 60; + + // establish keepalive + ping_interval = timeout - 30; // 30-second reserve + //add_timer_func_list(Sql_P_KeepaliveTimer, "Sql_P_KeepaliveTimer"); + return add_timer_interval(gettick() + ping_interval*1000, Sql_P_KeepaliveTimer, 0, (intptr_t)self, ping_interval*1000); +} + + + +/// Escapes a string. +size_t Sql_EscapeString(Sql* self, char *out_to, const char *from) +{ + if( self ) + return (size_t)mysql_real_escape_string(&self->handle, out_to, from, (unsigned long)strlen(from)); + else + return (size_t)mysql_escape_string(out_to, from, (unsigned long)strlen(from)); +} + + + +/// Escapes a string. +size_t Sql_EscapeStringLen(Sql* self, char *out_to, const char *from, size_t from_len) +{ + if( self ) + return (size_t)mysql_real_escape_string(&self->handle, out_to, from, (unsigned long)from_len); + else + return (size_t)mysql_escape_string(out_to, from, (unsigned long)from_len); +} + + + +/// Executes a query. +int Sql_Query(Sql* self, const char* query, ...) +{ + int res; + va_list args; + + va_start(args, query); + res = Sql_QueryV(self, query, args); + va_end(args); + + return res; +} + + + +/// Executes a query. +int Sql_QueryV(Sql* self, const char* query, va_list args) +{ + if( self == NULL ) + return SQL_ERROR; + + Sql_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_Vprintf(&self->buf, query, args); + if( mysql_real_query(&self->handle, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + self->result = mysql_store_result(&self->handle); + if( mysql_errno(&self->handle) != 0 ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + return SQL_SUCCESS; +} + + + +/// Executes a query. +int Sql_QueryStr(Sql* self, const char* query) +{ + if( self == NULL ) + return SQL_ERROR; + + Sql_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_AppendStr(&self->buf, query); + if( mysql_real_query(&self->handle, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + self->result = mysql_store_result(&self->handle); + if( mysql_errno(&self->handle) != 0 ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + return SQL_SUCCESS; +} + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE query. +uint64 Sql_LastInsertId(Sql* self) +{ + if( self ) + return (uint64)mysql_insert_id(&self->handle); + else + return 0; +} + + + +/// Returns the number of columns in each row of the result. +uint32 Sql_NumColumns(Sql* self) +{ + if( self && self->result ) + return (uint32)mysql_num_fields(self->result); + return 0; +} + + + +/// Returns the number of rows in the result. +uint64 Sql_NumRows(Sql* self) +{ + if( self && self->result ) + return (uint64)mysql_num_rows(self->result); + return 0; +} + + + +/// Fetches the next row. +int Sql_NextRow(Sql* self) +{ + if( self && self->result ) + { + self->row = mysql_fetch_row(self->result); + if( self->row ) + { + self->lengths = mysql_fetch_lengths(self->result); + return SQL_SUCCESS; + } + self->lengths = NULL; + if( mysql_errno(&self->handle) == 0 ) + return SQL_NO_DATA; + } + return SQL_ERROR; +} + + + +/// Gets the data of a column. +int Sql_GetData(Sql* self, size_t col, char** out_buf, size_t* out_len) +{ + if( self && self->row ) + { + if( col < Sql_NumColumns(self) ) + { + if( out_buf ) *out_buf = self->row[col]; + if( out_len ) *out_len = (size_t)self->lengths[col]; + } + else + {// out of range - ignore + if( out_buf ) *out_buf = NULL; + if( out_len ) *out_len = 0; + } + return SQL_SUCCESS; + } + return SQL_ERROR; +} + + + +/// Frees the result of the query. +void Sql_FreeResult(Sql* self) +{ + if( self && self->result ) + { + mysql_free_result(self->result); + self->result = NULL; + self->row = NULL; + self->lengths = NULL; + } +} + + + +/// Shows debug information (last query). +void Sql_ShowDebug_(Sql* self, const char* debug_file, const unsigned long debug_line) +{ + if( self == NULL ) + ShowDebug("at %s:%lu - self is NULL\n", debug_file, debug_line); + else if( StringBuf_Length(&self->buf) > 0 ) + ShowDebug("at %s:%lu - %s\n", debug_file, debug_line, StringBuf_Value(&self->buf)); + else + ShowDebug("at %s:%lu\n", debug_file, debug_line); +} + + + +/// Frees a Sql handle returned by Sql_Malloc. +void Sql_Free(Sql* self) +{ + if( self ) + { + Sql_FreeResult(self); + StringBuf_Destroy(&self->buf); + if( self->keepalive != INVALID_TIMER ) delete_timer(self->keepalive, Sql_P_KeepaliveTimer); + aFree(self); + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// Prepared Statements +/////////////////////////////////////////////////////////////////////////////// + + + +/// Returns the mysql integer type for the target size. +/// +/// @private +static enum enum_field_types Sql_P_SizeToMysqlIntType(int sz) +{ + switch( sz ) + { + case 1: return MYSQL_TYPE_TINY; + case 2: return MYSQL_TYPE_SHORT; + case 4: return MYSQL_TYPE_LONG; + case 8: return MYSQL_TYPE_LONGLONG; + default: + ShowDebug("SizeToMysqlIntType: unsupported size (%d)\n", sz); + return MYSQL_TYPE_NULL; + } +} + + + +/// Binds a parameter/result. +/// +/// @private +static int Sql_P_BindSqlDataType(MYSQL_BIND* bind, enum SqlDataType buffer_type, void* buffer, size_t buffer_len, unsigned long* out_length, int8* out_is_null) +{ + memset(bind, 0, sizeof(MYSQL_BIND)); + switch( buffer_type ) + { + case SQLDT_NULL: bind->buffer_type = MYSQL_TYPE_NULL; + buffer_len = 0;// FIXME length = ? [FlavioJS] + break; + // fixed size + case SQLDT_UINT8: bind->is_unsigned = 1; + case SQLDT_INT8: bind->buffer_type = MYSQL_TYPE_TINY; + buffer_len = 1; + break; + case SQLDT_UINT16: bind->is_unsigned = 1; + case SQLDT_INT16: bind->buffer_type = MYSQL_TYPE_SHORT; + buffer_len = 2; + break; + case SQLDT_UINT32: bind->is_unsigned = 1; + case SQLDT_INT32: bind->buffer_type = MYSQL_TYPE_LONG; + buffer_len = 4; + break; + case SQLDT_UINT64: bind->is_unsigned = 1; + case SQLDT_INT64: bind->buffer_type = MYSQL_TYPE_LONGLONG; + buffer_len = 8; + break; + // platform dependent size + case SQLDT_UCHAR: bind->is_unsigned = 1; + case SQLDT_CHAR: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(char)); + buffer_len = sizeof(char); + break; + case SQLDT_USHORT: bind->is_unsigned = 1; + case SQLDT_SHORT: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(short)); + buffer_len = sizeof(short); + break; + case SQLDT_UINT: bind->is_unsigned = 1; + case SQLDT_INT: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(int)); + buffer_len = sizeof(int); + break; + case SQLDT_ULONG: bind->is_unsigned = 1; + case SQLDT_LONG: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(long)); + buffer_len = sizeof(long); + break; + case SQLDT_ULONGLONG: bind->is_unsigned = 1; + case SQLDT_LONGLONG: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(int64)); + buffer_len = sizeof(int64); + break; + // floating point + case SQLDT_FLOAT: bind->buffer_type = MYSQL_TYPE_FLOAT; + buffer_len = 4; + break; + case SQLDT_DOUBLE: bind->buffer_type = MYSQL_TYPE_DOUBLE; + buffer_len = 8; + break; + // other + case SQLDT_STRING: + case SQLDT_ENUM: bind->buffer_type = MYSQL_TYPE_STRING; + break; + case SQLDT_BLOB: bind->buffer_type = MYSQL_TYPE_BLOB; + break; + default: + ShowDebug("Sql_P_BindSqlDataType: unsupported buffer type (%d)\n", buffer_type); + return SQL_ERROR; + } + bind->buffer = buffer; + bind->buffer_length = (unsigned long)buffer_len; + bind->length = out_length; + bind->is_null = (my_bool*)out_is_null; + return SQL_SUCCESS; +} + + + +/// Prints debug information about a field (type and length). +/// +/// @private +static void Sql_P_ShowDebugMysqlFieldInfo(const char* prefix, enum enum_field_types type, int is_unsigned, unsigned long length, const char* length_postfix) +{ + const char* sign = (is_unsigned ? "UNSIGNED " : ""); + const char* type_string; + switch( type ) + { + default: + ShowDebug("%stype=%s%u, length=%d\n", prefix, sign, type, length); + return; +#define SHOW_DEBUG_OF(x) case x: type_string = #x; break + SHOW_DEBUG_OF(MYSQL_TYPE_TINY); + SHOW_DEBUG_OF(MYSQL_TYPE_SHORT); + SHOW_DEBUG_OF(MYSQL_TYPE_LONG); + SHOW_DEBUG_OF(MYSQL_TYPE_INT24); + SHOW_DEBUG_OF(MYSQL_TYPE_LONGLONG); + SHOW_DEBUG_OF(MYSQL_TYPE_DECIMAL); + SHOW_DEBUG_OF(MYSQL_TYPE_FLOAT); + SHOW_DEBUG_OF(MYSQL_TYPE_DOUBLE); + SHOW_DEBUG_OF(MYSQL_TYPE_TIMESTAMP); + SHOW_DEBUG_OF(MYSQL_TYPE_DATE); + SHOW_DEBUG_OF(MYSQL_TYPE_TIME); + SHOW_DEBUG_OF(MYSQL_TYPE_DATETIME); + SHOW_DEBUG_OF(MYSQL_TYPE_YEAR); + SHOW_DEBUG_OF(MYSQL_TYPE_STRING); + SHOW_DEBUG_OF(MYSQL_TYPE_VAR_STRING); + SHOW_DEBUG_OF(MYSQL_TYPE_BLOB); + SHOW_DEBUG_OF(MYSQL_TYPE_SET); + SHOW_DEBUG_OF(MYSQL_TYPE_ENUM); + SHOW_DEBUG_OF(MYSQL_TYPE_NULL); +#undef SHOW_DEBUG_TYPE_OF + } + ShowDebug("%stype=%s%s, length=%d%s\n", prefix, sign, type_string, length, length_postfix); +} + + + +/// Reports debug information about a truncated column. +/// +/// @private +static void SqlStmt_P_ShowDebugTruncatedColumn(SqlStmt* self, size_t i) +{ + MYSQL_RES* meta; + MYSQL_FIELD* field; + MYSQL_BIND* column; + + meta = mysql_stmt_result_metadata(self->stmt); + field = mysql_fetch_field_direct(meta, (unsigned int)i); + ShowSQL("DB error - data of field '%s' was truncated.\n", field->name); + ShowDebug("column - %lu\n", (unsigned long)i); + Sql_P_ShowDebugMysqlFieldInfo("data - ", field->type, field->flags&UNSIGNED_FLAG, self->column_lengths[i].length, ""); + column = &self->columns[i]; + if( column->buffer_type == MYSQL_TYPE_STRING ) + Sql_P_ShowDebugMysqlFieldInfo("buffer - ", column->buffer_type, column->is_unsigned, column->buffer_length, "+1(nul-terminator)"); + else + Sql_P_ShowDebugMysqlFieldInfo("buffer - ", column->buffer_type, column->is_unsigned, column->buffer_length, ""); + mysql_free_result(meta); +} + + + +/// Allocates and initializes a new SqlStmt handle. +SqlStmt* SqlStmt_Malloc(Sql* sql) +{ + SqlStmt* self; + MYSQL_STMT* stmt; + + if( sql == NULL ) + return NULL; + + stmt = mysql_stmt_init(&sql->handle); + if( stmt == NULL ) + { + ShowSQL("DB error - %s\n", mysql_error(&sql->handle)); + return NULL; + } + CREATE(self, SqlStmt, 1); + StringBuf_Init(&self->buf); + self->stmt = stmt; + self->params = NULL; + self->columns = NULL; + self->column_lengths = NULL; + self->max_params = 0; + self->max_columns = 0; + self->bind_params = false; + self->bind_columns = false; + + return self; +} + + + +/// Prepares the statement. +int SqlStmt_Prepare(SqlStmt* self, const char* query, ...) +{ + int res; + va_list args; + + va_start(args, query); + res = SqlStmt_PrepareV(self, query, args); + va_end(args); + + return res; +} + + + +/// Prepares the statement. +int SqlStmt_PrepareV(SqlStmt* self, const char* query, va_list args) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_Vprintf(&self->buf, query, args); + if( mysql_stmt_prepare(self->stmt, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_params = false; + + return SQL_SUCCESS; +} + + + +/// Prepares the statement. +int SqlStmt_PrepareStr(SqlStmt* self, const char* query) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_AppendStr(&self->buf, query); + if( mysql_stmt_prepare(self->stmt, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_params = false; + + return SQL_SUCCESS; +} + + + +/// Returns the number of parameters in the prepared statement. +size_t SqlStmt_NumParams(SqlStmt* self) +{ + if( self ) + return (size_t)mysql_stmt_param_count(self->stmt); + else + return 0; +} + + + +/// Binds a parameter to a buffer. +int SqlStmt_BindParam(SqlStmt* self, size_t idx, enum SqlDataType buffer_type, void* buffer, size_t buffer_len) +{ + if( self == NULL ) + return SQL_ERROR; + + if( !self->bind_params ) + {// initialize the bindings + size_t i; + size_t count; + + count = SqlStmt_NumParams(self); + if( self->max_params < count ) + { + self->max_params = count; + RECREATE(self->params, MYSQL_BIND, count); + } + memset(self->params, 0, count*sizeof(MYSQL_BIND)); + for( i = 0; i < count; ++i ) + self->params[i].buffer_type = MYSQL_TYPE_NULL; + self->bind_params = true; + } + if( idx < self->max_params ) + return Sql_P_BindSqlDataType(self->params+idx, buffer_type, buffer, buffer_len, NULL, NULL); + else + return SQL_SUCCESS;// out of range - ignore +} + + + +/// Executes the prepared statement. +int SqlStmt_Execute(SqlStmt* self) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + if( (self->bind_params && mysql_stmt_bind_param(self->stmt, self->params)) || + mysql_stmt_execute(self->stmt) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_columns = false; + if( mysql_stmt_store_result(self->stmt) )// store all the data + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE statement. +uint64 SqlStmt_LastInsertId(SqlStmt* self) +{ + if( self ) + return (uint64)mysql_stmt_insert_id(self->stmt); + else + return 0; +} + + + +/// Returns the number of columns in each row of the result. +size_t SqlStmt_NumColumns(SqlStmt* self) +{ + if( self ) + return (size_t)mysql_stmt_field_count(self->stmt); + else + return 0; +} + + + +/// Binds the result of a column to a buffer. +int SqlStmt_BindColumn(SqlStmt* self, size_t idx, enum SqlDataType buffer_type, void* buffer, size_t buffer_len, uint32* out_length, int8* out_is_null) +{ + if( self == NULL ) + return SQL_ERROR; + + if( buffer_type == SQLDT_STRING || buffer_type == SQLDT_ENUM ) + { + if( buffer_len < 1 ) + { + ShowDebug("SqlStmt_BindColumn: buffer_len(%d) is too small, no room for the nul-terminator\n", buffer_len); + return SQL_ERROR; + } + --buffer_len;// nul-terminator + } + if( !self->bind_columns ) + {// initialize the bindings + size_t i; + size_t cols; + + cols = SqlStmt_NumColumns(self); + if( self->max_columns < cols ) + { + self->max_columns = cols; + RECREATE(self->columns, MYSQL_BIND, cols); + RECREATE(self->column_lengths, s_column_length, cols); + } + memset(self->columns, 0, cols*sizeof(MYSQL_BIND)); + memset(self->column_lengths, 0, cols*sizeof(s_column_length)); + for( i = 0; i < cols; ++i ) + self->columns[i].buffer_type = MYSQL_TYPE_NULL; + self->bind_columns = true; + } + if( idx < self->max_columns ) + { + self->column_lengths[idx].out_length = out_length; + return Sql_P_BindSqlDataType(self->columns+idx, buffer_type, buffer, buffer_len, &self->column_lengths[idx].length, out_is_null); + } + else + { + return SQL_SUCCESS;// out of range - ignore + } +} + + + +/// Returns the number of rows in the result. +uint64 SqlStmt_NumRows(SqlStmt* self) +{ + if( self ) + return (uint64)mysql_stmt_num_rows(self->stmt); + else + return 0; +} + + + +/// Fetches the next row. +int SqlStmt_NextRow(SqlStmt* self) +{ + int err; + size_t i; + size_t cols; + MYSQL_BIND* column; + unsigned long length; + + if( self == NULL ) + return SQL_ERROR; + + // bind columns + if( self->bind_columns && mysql_stmt_bind_result(self->stmt, self->columns) ) + err = 1;// error binding columns + else + err = mysql_stmt_fetch(self->stmt);// fetch row + + // check for errors + if( err == MYSQL_NO_DATA ) + return SQL_NO_DATA; +#if defined(MYSQL_DATA_TRUNCATED) + // MySQL 5.0/5.1 defines and returns MYSQL_DATA_TRUNCATED [FlavioJS] + if( err == MYSQL_DATA_TRUNCATED ) + { + my_bool truncated; + + if( !self->bind_columns ) + { + ShowSQL("DB error - data truncated (unknown source, columns are not bound)\n"); + return SQL_ERROR; + } + + // find truncated column + cols = SqlStmt_NumColumns(self); + for( i = 0; i < cols; ++i ) + { + column = &self->columns[i]; + column->error = &truncated; + mysql_stmt_fetch_column(self->stmt, column, (unsigned int)i, 0); + column->error = NULL; + if( truncated ) + {// report truncated column + SqlStmt_P_ShowDebugTruncatedColumn(self, i); + return SQL_ERROR; + } + } + ShowSQL("DB error - data truncated (unknown source)\n"); + return SQL_ERROR; + } +#endif + if( err ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + + // propagate column lengths and clear unused parts of string/enum/blob buffers + cols = SqlStmt_NumColumns(self); + for( i = 0; i < cols; ++i ) + { + length = self->column_lengths[i].length; + column = &self->columns[i]; +#if !defined(MYSQL_DATA_TRUNCATED) + // MySQL 4.1/(below?) returns success even if data is truncated, so we test truncation manually [FlavioJS] + if( column->buffer_length < length ) + {// report truncated column + if( column->buffer_type == MYSQL_TYPE_STRING || column->buffer_type == MYSQL_TYPE_BLOB ) + {// string/enum/blob column + SqlStmt_P_ShowDebugTruncatedColumn(self, i); + return SQL_ERROR; + } + // FIXME numeric types and null [FlavioJS] + } +#endif + if( self->column_lengths[i].out_length ) + *self->column_lengths[i].out_length = (uint32)length; + if( column->buffer_type == MYSQL_TYPE_STRING ) + {// clear unused part of the string/enum buffer (and nul-terminate) + memset((char*)column->buffer + length, 0, column->buffer_length - length + 1); + } + else if( column->buffer_type == MYSQL_TYPE_BLOB && length < column->buffer_length ) + {// clear unused part of the blob buffer + memset((char*)column->buffer + length, 0, column->buffer_length - length); + } + } + + return SQL_SUCCESS; +} + + + +/// Frees the result of the statement execution. +void SqlStmt_FreeResult(SqlStmt* self) +{ + if( self ) + mysql_stmt_free_result(self->stmt); +} + + + +/// Shows debug information (with statement). +void SqlStmt_ShowDebug_(SqlStmt* self, const char* debug_file, const unsigned long debug_line) +{ + if( self == NULL ) + ShowDebug("at %s:%lu - self is NULL\n", debug_file, debug_line); + else if( StringBuf_Length(&self->buf) > 0 ) + ShowDebug("at %s:%lu - %s\n", debug_file, debug_line, StringBuf_Value(&self->buf)); + else + ShowDebug("at %s:%lu\n", debug_file, debug_line); +} + + + +/// Frees a SqlStmt returned by SqlStmt_Malloc. +void SqlStmt_Free(SqlStmt* self) +{ + if( self ) + { + SqlStmt_FreeResult(self); + StringBuf_Destroy(&self->buf); + mysql_stmt_close(self->stmt); + if( self->params ) + aFree(self->params); + if( self->columns ) + { + aFree(self->columns); + aFree(self->column_lengths); + } + aFree(self); + } +} diff --git a/src/common/sql.h b/src/common/sql.h new file mode 100644 index 000000000..898e2c778 --- /dev/null +++ b/src/common/sql.h @@ -0,0 +1,344 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _COMMON_SQL_H_ +#define _COMMON_SQL_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h>// va_list + + + +// Return codes +#define SQL_ERROR -1 +#define SQL_SUCCESS 0 +#define SQL_NO_DATA 100 + +// macro definition to determine whether the mySQL engine is running on InnoDB (rather than MyISAM) +// uncomment this line if the your mySQL tables have been changed to run on InnoDB +// this macro will adjust how logs are recorded in the database to accommodate the change +//#define SQL_INNODB + +/// Data type identifier. +/// String, enum and blob data types need the buffer length specified. +enum SqlDataType +{ + SQLDT_NULL, + // fixed size + SQLDT_INT8, + SQLDT_INT16, + SQLDT_INT32, + SQLDT_INT64, + SQLDT_UINT8, + SQLDT_UINT16, + SQLDT_UINT32, + SQLDT_UINT64, + // platform dependent size + SQLDT_CHAR, + SQLDT_SHORT, + SQLDT_INT, + SQLDT_LONG, + SQLDT_LONGLONG, + SQLDT_UCHAR, + SQLDT_USHORT, + SQLDT_UINT, + SQLDT_ULONG, + SQLDT_ULONGLONG, + // floating point + SQLDT_FLOAT, + SQLDT_DOUBLE, + // other + SQLDT_STRING, + SQLDT_ENUM, + // Note: An ENUM is a string with restricted values. When an invalid value + // is inserted, it is saved as an empty string (numerical value 0). + SQLDT_BLOB, + SQLDT_LASTID +}; + +struct Sql;// Sql handle (private access) +struct SqlStmt;// Sql statement (private access) + +typedef enum SqlDataType SqlDataType; +typedef struct Sql Sql; +typedef struct SqlStmt SqlStmt; + + +/// Allocates and initializes a new Sql handle. +struct Sql* Sql_Malloc(void); + + + +/// Establishes a connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Connect(Sql* self, const char* user, const char* passwd, const char* host, uint16 port, const char* db); + + + + +/// Retrieves the timeout of the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetTimeout(Sql* self, uint32* out_timeout); + + + + +/// Retrieves the name of the columns of a table into out_buf, with the separator after each name. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetColumnNames(Sql* self, const char* table, char* out_buf, size_t buf_len, char sep); + + + + +/// Changes the encoding of the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_SetEncoding(Sql* self, const char* encoding); + + + +/// Pings the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Ping(Sql* self); + + + +/// Escapes a string. +/// The output buffer must be at least strlen(from)*2+1 in size. +/// +/// @return The size of the escaped string +size_t Sql_EscapeString(Sql* self, char* out_to, const char* from); + + + +/// Escapes a string. +/// The output buffer must be at least from_len*2+1 in size. +/// +/// @return The size of the escaped string +size_t Sql_EscapeStringLen(Sql* self, char* out_to, const char* from, size_t from_len); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is constructed as if it was sprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Query(Sql* self, const char* query, ...); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is constructed as if it was svprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_QueryV(Sql* self, const char* query, va_list args); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is used directly. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_QueryStr(Sql* self, const char* query); + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE query. +/// +/// @return Value of the auto-increment column +uint64 Sql_LastInsertId(Sql* self); + + + +/// Returns the number of columns in each row of the result. +/// +/// @return Number of columns +uint32 Sql_NumColumns(Sql* self); + + + +/// Returns the number of rows in the result. +/// +/// @return Number of rows +uint64 Sql_NumRows(Sql* self); + + + +/// Fetches the next row. +/// The data of the previous row is no longer valid. +/// +/// @return SQL_SUCCESS, SQL_ERROR or SQL_NO_DATA +int Sql_NextRow(Sql* self); + + + +/// Gets the data of a column. +/// The data remains valid until the next row is fetched or the result is freed. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetData(Sql* self, size_t col, char** out_buf, size_t* out_len); + + + +/// Frees the result of the query. +void Sql_FreeResult(Sql* self); + + + +#if defined(SQL_REMOVE_SHOWDEBUG) +#define Sql_ShowDebug(self) (void)0 +#else +#define Sql_ShowDebug(self) Sql_ShowDebug_(self, __FILE__, __LINE__) +#endif +/// Shows debug information (last query). +void Sql_ShowDebug_(Sql* self, const char* debug_file, const unsigned long debug_line); + + + +/// Frees a Sql handle returned by Sql_Malloc. +void Sql_Free(Sql* self); + + + +/////////////////////////////////////////////////////////////////////////////// +// Prepared Statements +/////////////////////////////////////////////////////////////////////////////// +// Parameters are placed in the statement by embedding question mark ('?') +// characters into the query at the appropriate positions. +// The markers are legal only in places where they represent data. +// The markers cannot be inside quotes. Quotes will be added automatically +// when they are required. +// +// example queries with parameters: +// 1) SELECT col FROM table WHERE id=? +// 2) INSERT INTO table(col1,col2) VALUES(?,?) + + + +/// Allocates and initializes a new SqlStmt handle. +/// It uses the connection of the parent Sql handle. +/// Queries in Sql and SqlStmt are independent and don't affect each other. +/// +/// @return SqlStmt handle or NULL if an error occured +struct SqlStmt* SqlStmt_Malloc(Sql* sql); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is constructed as if it was sprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_Prepare(SqlStmt* self, const char* query, ...); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is constructed as if it was svprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_PrepareV(SqlStmt* self, const char* query, va_list args); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is used directly. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_PrepareStr(SqlStmt* self, const char* query); + + + +/// Returns the number of parameters in the prepared statement. +/// +/// @return Number or paramenters +size_t SqlStmt_NumParams(SqlStmt* self); + + + +/// Binds a parameter to a buffer. +/// The buffer data will be used when the statement is executed. +/// All parameters should have bindings. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_BindParam(SqlStmt* self, size_t idx, SqlDataType buffer_type, void* buffer, size_t buffer_len); + + + +/// Executes the prepared statement. +/// Any previous result is freed and all column bindings are removed. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_Execute(SqlStmt* self); + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE statement. +/// +/// @return Value of the auto-increment column +uint64 SqlStmt_LastInsertId(SqlStmt* self); + + + +/// Returns the number of columns in each row of the result. +/// +/// @return Number of columns +size_t SqlStmt_NumColumns(SqlStmt* self); + + + +/// Binds the result of a column to a buffer. +/// The buffer will be filled with data when the next row is fetched. +/// For string/enum buffer types there has to be enough space for the data +/// and the nul-terminator (an extra byte). +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_BindColumn(SqlStmt* self, size_t idx, SqlDataType buffer_type, void* buffer, size_t buffer_len, uint32* out_length, int8* out_is_null); + + + +/// Returns the number of rows in the result. +/// +/// @return Number of rows +uint64 SqlStmt_NumRows(SqlStmt* self); + + + +/// Fetches the next row. +/// All column bindings will be filled with data. +/// +/// @return SQL_SUCCESS, SQL_ERROR or SQL_NO_DATA +int SqlStmt_NextRow(SqlStmt* self); + + + +/// Frees the result of the statement execution. +void SqlStmt_FreeResult(SqlStmt* self); + + + +#if defined(SQL_REMOVE_SHOWDEBUG) +#define SqlStmt_ShowDebug(self) (void)0 +#else +#define SqlStmt_ShowDebug(self) SqlStmt_ShowDebug_(self, __FILE__, __LINE__) +#endif +/// Shows debug information (with statement). +void SqlStmt_ShowDebug_(SqlStmt* self, const char* debug_file, const unsigned long debug_line); + + + +/// Frees a SqlStmt returned by SqlStmt_Malloc. +void SqlStmt_Free(SqlStmt* self); + + + +#endif /* _COMMON_SQL_H_ */ diff --git a/src/common/strlib.c b/src/common/strlib.c new file mode 100644 index 000000000..dfacbf136 --- /dev/null +++ b/src/common/strlib.c @@ -0,0 +1,1167 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "strlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + + +#define J_MAX_MALLOC_SIZE 65535 + +// escapes a string in-place (' -> \' , \ -> \\ , % -> _) +char* jstrescape (char* pt) +{ + //copy from here + char *ptr; + int i = 0, j = 0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy(ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + aFree(ptr); + return pt; +} + +// escapes a string into a provided buffer +char* jstrescapecpy (char* pt, const char* spt) +{ + //copy from here + //WARNING: Target string pt should be able to hold strlen(spt)*2, as each time + //a escape character is found, the target's final length increases! [Skotlex] + int i =0, j=0; + + if (!spt) { //Return an empty string [Skotlex] + pt[0] = '\0'; + return &pt[0]; + } + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return &pt[0]; +} + +// escapes exactly 'size' bytes of a string into a provided buffer +int jmemescapecpy (char* pt, const char* spt, int size) +{ + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} + +// Function to suppress control characters in a string. +int remove_control_chars(char* str) +{ + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (ISCNTRL(str[i])) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +// Removes characters identified by ISSPACE from the start and end of the string +// NOTE: make sure the string is not const!! +char* trim(char* str) +{ + size_t start; + size_t end; + + if( str == NULL ) + return str; + + // get start position + for( start = 0; str[start] && ISSPACE(str[start]); ++start ) + ; + // get end position + for( end = strlen(str); start < end && str[end-1] && ISSPACE(str[end-1]); --end ) + ; + // trim + if( start == end ) + *str = '\0';// empty string + else + {// move string with nul terminator + str[end] = '\0'; + memmove(str,str+start,end-start+1); + } + return str; +} + +// Converts one or more consecutive occurences of the delimiters into a single space +// and removes such occurences from the beginning and end of string +// NOTE: make sure the string is not const!! +char* normalize_name(char* str,const char* delims) +{ + char* in = str; + char* out = str; + int put_space = 0; + + if( str == NULL || delims == NULL ) + return str; + + // trim start of string + while( *in && strchr(delims,*in) ) + ++in; + while( *in ) + { + if( put_space ) + {// replace trim characters with a single space + *out = ' '; + ++out; + } + // copy non trim characters + while( *in && !strchr(delims,*in) ) + { + *out = *in; + ++out; + ++in; + } + // skip trim characters + while( *in && strchr(delims,*in) ) + ++in; + put_space = 1; + } + *out = '\0'; + return str; +} + +//stristr: Case insensitive version of strstr, code taken from +//http://www.daniweb.com/code/snippet313.html, Dave Sinkula +// +const char* stristr(const char* haystack, const char* needle) +{ + if ( !*needle ) + { + return haystack; + } + for ( ; *haystack; ++haystack ) + { + if ( TOUPPER(*haystack) == TOUPPER(*needle) ) + { + // matched starting char -- loop through remaining chars + const char *h, *n; + for ( h = haystack, n = needle; *h && *n; ++h, ++n ) + { + if ( TOUPPER(*h) != TOUPPER(*n) ) + { + break; + } + } + if ( !*n ) // matched all of 'needle' to null termination + { + return haystack; // return the start of the match + } + } + } + return 0; +} + +#ifdef __WIN32 +char* _strtok_r(char *s1, const char *s2, char **lasts) +{ + char *ret; + + if (s1 == NULL) + s1 = *lasts; + while(*s1 && strchr(s2, *s1)) + ++s1; + if(*s1 == '\0') + return NULL; + ret = s1; + while(*s1 && !strchr(s2, *s1)) + ++s1; + if(*s1) + *s1++ = '\0'; + *lasts = s1; + return ret; +} +#endif + +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) +/* Find the length of STRING, but scan at most MAXLEN characters. + If no '\0' terminator is found in that many characters, return MAXLEN. */ +size_t strnlen (const char* string, size_t maxlen) +{ + const char* end = (const char*)memchr(string, '\0', maxlen); + return end ? (size_t) (end - string) : maxlen; +} +#endif + +#if defined(WIN32) && defined(_MSC_VER) && _MSC_VER <= 1200 +uint64 strtoull(const char* str, char** endptr, int base) +{ + uint64 result; + int count; + int n; + + if( base == 0 ) + { + if( str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + base = 16; + else + if( str[0] == '0' ) + base = 8; + else + base = 10; + } + + if( base == 8 ) + count = sscanf(str, "%I64o%n", &result, &n); + else + if( base == 10 ) + count = sscanf(str, "%I64u%n", &result, &n); + else + if( base == 16 ) + count = sscanf(str, "%I64x%n", &result, &n); + else + count = 0; // fail + + if( count < 1 ) + { + errno = EINVAL; + result = 0; + n = 0; + } + + if( endptr ) + *endptr = (char*)str + n; + + return result; +} +#endif + +//---------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//---------------------------------------------------- +int e_mail_check(char* email) +{ + char ch; + char* last_arobas; + size_t len = strlen(email); + + // athena limits + if (len < 3 || len > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[len-1] == '@') + return 0; + + if (email[len-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) + if (strchr(last_arobas, ch) != NULL) + return 0; + + if (strchr(last_arobas, ' ') != NULL || strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//-------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//-------------------------------------------------- +int config_switch(const char* str) +{ + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return (int)strtol(str, NULL, 0); +} + +/// strncpy that always nul-terminates the string +char* safestrncpy(char* dst, const char* src, size_t n) +{ + if( n > 0 ) + { + char* d = dst; + const char* s = src; + d[--n] = '\0';/* nul-terminate string */ + for( ; n > 0; --n ) + { + if( (*d++ = *s++) == '\0' ) + {/* nul-pad remaining bytes */ + while( --n > 0 ) + *d++ = '\0'; + break; + } + } + } + return dst; +} + +/// doesn't crash on null pointer +size_t safestrnlen(const char* string, size_t maxlen) +{ + return ( string != NULL ) ? strnlen(string, maxlen) : 0; +} + +/// Works like snprintf, but always nul-terminates the buffer. +/// Returns the size of the string (without nul-terminator) +/// or -1 if the buffer is too small. +/// +/// @param buf Target buffer +/// @param sz Size of the buffer (including nul-terminator) +/// @param fmt Format string +/// @param ... Format arguments +/// @return The size of the string or -1 if the buffer is too small +int safesnprintf(char* buf, size_t sz, const char* fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap,fmt); + ret = vsnprintf(buf, sz, fmt, ap); + va_end(ap); + if( ret < 0 || (size_t)ret >= sz ) + {// overflow + buf[sz-1] = '\0';// always nul-terminate + return -1; + } + return ret; +} + +/// Returns the line of the target position in the string. +/// Lines start at 1. +int strline(const char* str, size_t pos) +{ + const char* target; + int line; + + if( str == NULL || pos == 0 ) + return 1; + + target = str+pos; + for( line = 1; ; ++line ) + { + str = strchr(str, '\n'); + if( str == NULL || target <= str ) + break;// found target line + ++str;// skip newline + } + return line; +} + +/// Produces the hexadecimal representation of the given input. +/// The output buffer must be at least count*2+1 in size. +/// Returns true on success, false on failure. +/// +/// @param output Output string +/// @param input Binary input buffer +/// @param count Number of bytes to convert +bool bin2hex(char* output, unsigned char* input, size_t count) +{ + char toHex[] = "0123456789abcdef"; + size_t i; + + for( i = 0; i < count; ++i ) + { + *output++ = toHex[(*input & 0xF0) >> 4]; + *output++ = toHex[(*input & 0x0F) >> 0]; + ++input; + } + *output = '\0'; + return true; +} + + + +///////////////////////////////////////////////////////////////////// +/// Parses a single field in a delim-separated string. +/// The delimiter after the field is skipped. +/// +/// @param sv Parse state +/// @return 1 if a field was parsed, 0 if already done, -1 on error. +int sv_parse_next(struct s_svstate* sv) +{ + enum { + START_OF_FIELD, + PARSING_FIELD, + PARSING_C_ESCAPE, + END_OF_FIELD, + TERMINATE, + END + } state; + const char* str; + int len; + enum e_svopt opt; + char delim; + int i; + + if( sv == NULL ) + return -1;// error + + str = sv->str; + len = sv->len; + opt = sv->opt; + delim = sv->delim; + + // check opt + if( delim == '\n' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_LF)) ) + { + ShowError("sv_parse_next: delimiter '\\n' is not compatible with options SV_TERMINATE_LF or SV_TERMINATE_CRLF.\n"); + return -1;// error + } + if( delim == '\r' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_CR)) ) + { + ShowError("sv_parse_next: delimiter '\\r' is not compatible with options SV_TERMINATE_CR or SV_TERMINATE_CRLF.\n"); + return -1;// error + } + + if( sv->done || str == NULL ) + { + sv->done = true; + return 0;// nothing to parse + } + +#define IS_END() ( i >= len ) +#define IS_DELIM() ( str[i] == delim ) +#define IS_TERMINATOR() ( \ + ((opt&SV_TERMINATE_LF) && str[i] == '\n') || \ + ((opt&SV_TERMINATE_CR) && str[i] == '\r') || \ + ((opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n') ) +#define IS_C_ESCAPE() ( (opt&SV_ESCAPE_C) && str[i] == '\\' ) +#define SET_FIELD_START() sv->start = i +#define SET_FIELD_END() sv->end = i + + i = sv->off; + state = START_OF_FIELD; + while( state != END ) + { + switch( state ) + { + case START_OF_FIELD:// record start of field and start parsing it + SET_FIELD_START(); + state = PARSING_FIELD; + break; + + case PARSING_FIELD:// skip field character + if( IS_END() || IS_DELIM() || IS_TERMINATOR() ) + state = END_OF_FIELD; + else if( IS_C_ESCAPE() ) + state = PARSING_C_ESCAPE; + else + ++i;// normal character + break; + + case PARSING_C_ESCAPE:// skip escape sequence (validates it too) + { + ++i;// '\\' + if( IS_END() ) + { + ShowError("sv_parse_next: empty escape sequence\n"); + return -1; + } + if( str[i] == 'x' ) + {// hex escape + ++i;// 'x' + if( IS_END() || !ISXDIGIT(str[i]) ) + { + ShowError("sv_parse_next: \\x with no following hex digits\n"); + return -1; + } + do{ + ++i;// hex digit + }while( !IS_END() && ISXDIGIT(str[i])); + } + else if( str[i] == '0' || str[i] == '1' || str[i] == '2' ) + {// octal escape + ++i;// octal digit + if( !IS_END() && str[i] >= '0' && str[i] <= '7' ) + ++i;// octal digit + if( !IS_END() && str[i] >= '0' && str[i] <= '7' ) + ++i;// octal digit + } + else if( strchr(SV_ESCAPE_C_SUPPORTED, str[i]) ) + {// supported escape character + ++i; + } + else + { + ShowError("sv_parse_next: unknown escape sequence \\%c\n", str[i]); + return -1; + } + state = PARSING_FIELD; + break; + } + + case END_OF_FIELD:// record end of field and stop + SET_FIELD_END(); + state = END; + if( IS_END() ) + ;// nothing else + else if( IS_DELIM() ) + ++i;// delim + else if( IS_TERMINATOR() ) + state = TERMINATE; + break; + + case TERMINATE: +#if 0 + // skip line terminator + if( (opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n' ) + i += 2;// CRLF + else + ++i;// CR or LF +#endif + sv->done = true; + state = END; + break; + } + } + if( IS_END() ) + sv->done = true; + sv->off = i; + +#undef IS_END +#undef IS_DELIM +#undef IS_TERMINATOR +#undef IS_C_ESCAPE +#undef SET_FIELD_START +#undef SET_FIELD_END + + return 1; +} + + +/// Parses a delim-separated string. +/// Starts parsing at startoff and fills the pos array with position pairs. +/// out_pos[0] and out_pos[1] are the start and end of line. +/// Other position pairs are the start and end of fields. +/// Returns the number of fields found or -1 if an error occurs. +/// +/// out_pos can be NULL. +/// If a line terminator is found, the end position is placed there. +/// out_pos[2] and out_pos[3] for the first field, out_pos[4] and out_pos[5] +/// for the seconds field and so on. +/// Unfilled positions are set to -1. +/// +/// @param str String to parse +/// @param len Length of the string +/// @param startoff Where to start parsing +/// @param delim Field delimiter +/// @param out_pos Array of resulting positions +/// @param npos Size of the pos array +/// @param opt Options that determine the parsing behaviour +/// @return Number of fields found in the string or -1 if an error occured +int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt) +{ + struct s_svstate sv; + int count; + + // initialize + if( out_pos == NULL ) npos = 0; + for( count = 0; count < npos; ++count ) + out_pos[count] = -1; + sv.str = str; + sv.len = len; + sv.off = startoff; + sv.opt = opt; + sv.delim = delim; + sv.done = false; + + // parse + count = 0; + if( npos > 0 ) out_pos[0] = startoff; + while( !sv.done ) + { + ++count; + if( sv_parse_next(&sv) <= 0 ) + return -1;// error + if( npos > count*2 ) out_pos[count*2] = sv.start; + if( npos > count*2+1 ) out_pos[count*2+1] = sv.end; + } + if( npos > 1 ) out_pos[1] = sv.off; + return count; +} + +/// Splits a delim-separated string. +/// WARNING: this function modifies the input string +/// Starts splitting at startoff and fills the out_fields array. +/// out_fields[0] is the start of the next line. +/// Other entries are the start of fields (nul-teminated). +/// Returns the number of fields found or -1 if an error occurs. +/// +/// out_fields can be NULL. +/// Fields that don't fit in out_fields are not nul-terminated. +/// Extra entries in out_fields are filled with the end of the last field (empty string). +/// +/// @param str String to parse +/// @param len Length of the string +/// @param startoff Where to start parsing +/// @param delim Field delimiter +/// @param out_fields Array of resulting fields +/// @param nfields Size of the field array +/// @param opt Options that determine the parsing behaviour +/// @return Number of fields found in the string or -1 if an error occured +int sv_split(char* str, int len, int startoff, char delim, char** out_fields, int nfields, enum e_svopt opt) +{ + int pos[1024]; + int i; + int done; + char* end; + int ret = sv_parse(str, len, startoff, delim, pos, ARRAYLENGTH(pos), opt); + + if( ret == -1 || out_fields == NULL || nfields <= 0 ) + return ret; // nothing to do + + // next line + end = str + pos[1]; + if( end[0] == '\0' ) + { + *out_fields = end; + } + else if( (opt&SV_TERMINATE_LF) && end[0] == '\n' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = '\0'; + *out_fields = end + 1; + } + else if( (opt&SV_TERMINATE_CRLF) && end[0] == '\r' && end[1] == '\n' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = end[1] = '\0'; + *out_fields = end + 2; + } + else if( (opt&SV_TERMINATE_CR) && end[0] == '\r' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = '\0'; + *out_fields = end + 1; + } + else + { + ShowError("sv_split: unknown line delimiter 0x02%x.\n", (unsigned char)end[0]); + return -1;// error + } + ++out_fields; + --nfields; + + // fields + i = 2; + done = 0; + while( done < ret && nfields > 0 ) + { + if( i < ARRAYLENGTH(pos) ) + {// split field + *out_fields = str + pos[i]; + end = str + pos[i+1]; + *end = '\0'; + // next field + i += 2; + ++done; + ++out_fields; + --nfields; + } + else + {// get more fields + sv_parse(str, len, pos[i-1] + 1, delim, pos, ARRAYLENGTH(pos), opt); + i = 2; + } + } + // remaining fields + for( i = 0; i < nfields; ++i ) + out_fields[i] = end; + return ret; +} + +/// Escapes src to out_dest according to the format of the C compiler. +/// Returns the length of the escaped string. +/// out_dest should be len*4+1 in size. +/// +/// @param out_dest Destination buffer +/// @param src Source string +/// @param len Length of the source string +/// @param escapes Extra characters to be escaped +/// @return Length of the escaped string +size_t sv_escape_c(char* out_dest, const char* src, size_t len, const char* escapes) +{ + size_t i; + size_t j; + + if( out_dest == NULL ) + return 0;// nothing to do + if( src == NULL ) + {// nothing to escape + *out_dest = 0; + return 0; + } + if( escapes == NULL ) + escapes = ""; + + for( i = 0, j = 0; i < len; ++i ) + { + switch( src[i] ) + { + case '\0':// octal 0 + out_dest[j++] = '\\'; + out_dest[j++] = '0'; + out_dest[j++] = '0'; + out_dest[j++] = '0'; + break; + case '\r':// carriage return + out_dest[j++] = '\\'; + out_dest[j++] = 'r'; + break; + case '\n':// line feed + out_dest[j++] = '\\'; + out_dest[j++] = 'n'; + break; + case '\\':// escape character + out_dest[j++] = '\\'; + out_dest[j++] = '\\'; + break; + default: + if( strchr(escapes,src[i]) ) + {// escape + out_dest[j++] = '\\'; + switch( src[i] ) + { + case '\a': out_dest[j++] = 'a'; break; + case '\b': out_dest[j++] = 'b'; break; + case '\t': out_dest[j++] = 't'; break; + case '\v': out_dest[j++] = 'v'; break; + case '\f': out_dest[j++] = 'f'; break; + case '\?': out_dest[j++] = '?'; break; + default:// to octal + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0700)>>6)); + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0070)>>3)); + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0007) )); + break; + } + } + else + out_dest[j++] = src[i]; + break; + } + } + out_dest[j] = 0; + return j; +} + +/// Unescapes src to out_dest according to the format of the C compiler. +/// Returns the length of the unescaped string. +/// out_dest should be len+1 in size and can be the same buffer as src. +/// +/// @param out_dest Destination buffer +/// @param src Source string +/// @param len Length of the source string +/// @return Length of the escaped string +size_t sv_unescape_c(char* out_dest, const char* src, size_t len) +{ + static unsigned char low2hex[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x0? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x1? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x2? + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,// 0x3? + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x4? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x5? + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x6? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xA? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xB? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xC? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xD? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xE? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF? + }; + size_t i; + size_t j; + + for( i = 0, j = 0; i < len; ) + { + if( src[i] == '\\' ) + { + ++i;// '\\' + if( i >= len ) + ShowWarning("sv_unescape_c: empty escape sequence\n"); + else if( src[i] == 'x' ) + {// hex escape sequence + unsigned char c = 0; + unsigned char inrange = 1; + + ++i;// 'x' + if( i >= len || !ISXDIGIT(src[i]) ) + { + ShowWarning("sv_unescape_c: \\x with no following hex digits\n"); + continue; + } + do{ + if( c > 0x0F && inrange ) + { + ShowWarning("sv_unescape_c: hex escape sequence out of range\n"); + inrange = 0; + } + c = (c<<4)|low2hex[(unsigned char)src[i]];// hex digit + ++i; + }while( i < len && ISXDIGIT(src[i]) ); + out_dest[j++] = (char)c; + } + else if( src[i] == '0' || src[i] == '1' || src[i] == '2' || src[i] == '3' ) + {// octal escape sequence (255=0377) + unsigned char c = src[i]-'0'; + ++i;// '0', '1', '2' or '3' + if( i < len && src[i] >= '0' && src[i] <= '7' ) + { + c = (c<<3)|(src[i]-'0'); + ++i;// octal digit + } + if( i < len && src[i] >= '0' && src[i] <= '7' ) + { + c = (c<<3)|(src[i]-'0'); + ++i;// octal digit + } + out_dest[j++] = (char)c; + } + else + {// other escape sequence + if( strchr(SV_ESCAPE_C_SUPPORTED, src[i]) == NULL ) + ShowWarning("sv_unescape_c: unknown escape sequence \\%c\n", src[i]); + switch( src[i] ) + { + case 'a': out_dest[j++] = '\a'; break; + case 'b': out_dest[j++] = '\b'; break; + case 't': out_dest[j++] = '\t'; break; + case 'n': out_dest[j++] = '\n'; break; + case 'v': out_dest[j++] = '\v'; break; + case 'f': out_dest[j++] = '\f'; break; + case 'r': out_dest[j++] = '\r'; break; + case '?': out_dest[j++] = '\?'; break; + default: out_dest[j++] = src[i]; break; + } + ++i;// escaped character + } + } + else + out_dest[j++] = src[i++];// normal character + } + out_dest[j] = 0; + return j; +} + +/// Skips a C escape sequence (starting with '\\'). +const char* skip_escaped_c(const char* p) +{ + if( p && *p == '\\' ) + { + ++p; + switch( *p ) + { + case 'x':// hexadecimal + ++p; + while( ISXDIGIT(*p) ) + ++p; + break; + case '0': + case '1': + case '2': + case '3':// octal + ++p; + if( *p >= '0' && *p <= '7' ) + ++p; + if( *p >= '0' && *p <= '7' ) + ++p; + break; + default: + if( *p && strchr(SV_ESCAPE_C_SUPPORTED, *p) ) + ++p; + } + } + return p; +} + + +/// Opens and parses a file containing delim-separated columns, feeding them to the specified callback function row by row. +/// Tracks the progress of the operation (current line number, number of successfully processed rows). +/// Returns 'true' if it was able to process the specified file, or 'false' if it could not be read. +/// +/// @param directory Directory +/// @param filename File to process +/// @param delim Field delimiter +/// @param mincols Minimum number of columns of a valid row +/// @param maxcols Maximum number of columns of a valid row +/// @param parseproc User-supplied row processing function +/// @return true on success, false if file could not be opened +bool sv_readdb(const char* directory, const char* filename, char delim, int mincols, int maxcols, int maxrows, bool (*parseproc)(char* fields[], int columns, int current)) +{ + FILE* fp; + int lines = 0; + int entries = 0; + char** fields; // buffer for fields ([0] is reserved) + int columns, fields_length; + char path[1024], line[1024]; + char* match; + + snprintf(path, sizeof(path), "%s/%s", directory, filename); + + // open file + fp = fopen(path, "r"); + if( fp == NULL ) + { + ShowError("sv_readdb: can't read %s\n", path); + return false; + } + + // allocate enough memory for the maximum requested amount of columns plus the reserved one + fields_length = maxcols+1; + fields = (char**)aMalloc(fields_length*sizeof(char*)); + + // process rows one by one + while( fgets(line, sizeof(line), fp) ) + { + lines++; + + if( ( match = strstr(line, "//") ) != NULL ) + {// strip comments + match[0] = 0; + } + + //TODO: strip trailing whitespace + if( line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + + columns = sv_split(line, strlen(line), 0, delim, fields, fields_length, (e_svopt)(SV_TERMINATE_LF|SV_TERMINATE_CRLF)); + + if( columns < mincols ) + { + ShowError("sv_readdb: Insufficient columns in line %d of \"%s\" (found %d, need at least %d).\n", lines, path, columns, mincols); + continue; // not enough columns + } + if( columns > maxcols ) + { + ShowError("sv_readdb: Too many columns in line %d of \"%s\" (found %d, maximum is %d).\n", lines, path, columns, maxcols ); + continue; // too many columns + } + if( entries == maxrows ) + { + ShowError("sv_readdb: Reached the maximum allowed number of entries (%d) when parsing file \"%s\".\n", maxrows, path); + break; + } + + // parse this row + if( !parseproc(fields+1, columns, entries) ) + { + ShowError("sv_readdb: Could not process contents of line %d of \"%s\".\n", lines, path); + continue; // invalid row contents + } + + // success! + entries++; + } + + aFree(fields); + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, path); + + return true; +} + + +///////////////////////////////////////////////////////////////////// +// StringBuf - dynamic string +// +// @author MouseJstr (original) + +/// Allocates a StringBuf +StringBuf* StringBuf_Malloc() +{ + StringBuf* self; + CREATE(self, StringBuf, 1); + StringBuf_Init(self); + return self; +} + +/// Initializes a previously allocated StringBuf +void StringBuf_Init(StringBuf* self) +{ + self->max_ = 1024; + self->ptr_ = self->buf_ = (char*)aMalloc(self->max_ + 1); +} + +/// Appends the result of printf to the StringBuf +int StringBuf_Printf(StringBuf* self, const char* fmt, ...) +{ + int len; + va_list ap; + + va_start(ap, fmt); + len = StringBuf_Vprintf(self, fmt, ap); + va_end(ap); + + return len; +} + +/// Appends the result of vprintf to the StringBuf +int StringBuf_Vprintf(StringBuf* self, const char* fmt, va_list ap) +{ + int n, size, off; + + for(;;) + { + va_list apcopy; + /* Try to print in the allocated space. */ + size = self->max_ - (self->ptr_ - self->buf_); + va_copy(apcopy, ap); + n = vsnprintf(self->ptr_, size, fmt, apcopy); + va_end(apcopy); + /* If that worked, return the length. */ + if( n > -1 && n < size ) + { + self->ptr_ += n; + return (int)(self->ptr_ - self->buf_); + } + /* Else try again with more space. */ + self->max_ *= 2; // twice the old size + off = (int)(self->ptr_ - self->buf_); + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } +} + +/// Appends the contents of another StringBuf to the StringBuf +int StringBuf_Append(StringBuf* self, const StringBuf* sbuf) +{ + int available = self->max_ - (self->ptr_ - self->buf_); + int needed = (int)(sbuf->ptr_ - sbuf->buf_); + + if( needed >= available ) + { + int off = (int)(self->ptr_ - self->buf_); + self->max_ += needed; + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } + + memcpy(self->ptr_, sbuf->buf_, needed); + self->ptr_ += needed; + return (int)(self->ptr_ - self->buf_); +} + +// Appends str to the StringBuf +int StringBuf_AppendStr(StringBuf* self, const char* str) +{ + int available = self->max_ - (self->ptr_ - self->buf_); + int needed = (int)strlen(str); + + if( needed >= available ) + {// not enough space, expand the buffer (minimum expansion = 1024) + int off = (int)(self->ptr_ - self->buf_); + self->max_ += max(needed, 1024); + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } + + memcpy(self->ptr_, str, needed); + self->ptr_ += needed; + return (int)(self->ptr_ - self->buf_); +} + +// Returns the length of the data in the Stringbuf +int StringBuf_Length(StringBuf* self) +{ + return (int)(self->ptr_ - self->buf_); +} + +/// Returns the data in the StringBuf +char* StringBuf_Value(StringBuf* self) +{ + *self->ptr_ = '\0'; + return self->buf_; +} + +/// Clears the contents of the StringBuf +void StringBuf_Clear(StringBuf* self) +{ + self->ptr_ = self->buf_; +} + +/// Destroys the StringBuf +void StringBuf_Destroy(StringBuf* self) +{ + aFree(self->buf_); + self->ptr_ = self->buf_ = 0; + self->max_ = 0; +} + +// Frees a StringBuf returned by StringBuf_Malloc +void StringBuf_Free(StringBuf* self) +{ + StringBuf_Destroy(self); + aFree(self); +} diff --git a/src/common/strlib.h b/src/common/strlib.h new file mode 100644 index 000000000..bbc2c6105 --- /dev/null +++ b/src/common/strlib.h @@ -0,0 +1,155 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _STRLIB_H_ +#define _STRLIB_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h> + +#define __USE_GNU // required to enable strnlen on some platforms +#include <string.h> +#undef __USE_GNU + +char* jstrescape (char* pt); +char* jstrescapecpy (char* pt, const char* spt); +int jmemescapecpy (char* pt, const char* spt, int size); + +int remove_control_chars(char* str); +char* trim(char* str); +char* normalize_name(char* str,const char* delims); +const char *stristr(const char *haystack, const char *needle); + +#ifdef WIN32 +#define HAVE_STRTOK_R +#define strtok_r(s,delim,save_ptr) _strtok_r((s),(delim),(save_ptr)) +char* _strtok_r(char* s1, const char* s2, char** lasts); +#endif + +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) +size_t strnlen (const char* string, size_t maxlen); +#endif + +#if defined(WIN32) && defined(_MSC_VER) && _MSC_VER <= 1200 +uint64 strtoull(const char* str, char** endptr, int base); +#endif + +int e_mail_check(char* email); +int config_switch(const char* str); + +/// strncpy that always nul-terminates the string +char* safestrncpy(char* dst, const char* src, size_t n); + +/// doesn't crash on null pointer +size_t safestrnlen(const char* string, size_t maxlen); + +/// Works like snprintf, but always nul-terminates the buffer. +/// Returns the size of the string (without nul-terminator) +/// or -1 if the buffer is too small. +int safesnprintf(char* buf, size_t sz, const char* fmt, ...); + +/// Returns the line of the target position in the string. +/// Lines start at 1. +int strline(const char* str, size_t pos); + +/// Produces the hexadecimal representation of the given input. +/// The output buffer must be at least count*2+1 in size. +/// Returns true on success, false on failure. +bool bin2hex(char* output, unsigned char* input, size_t count); + + +/// Bitfield determining the behaviour of sv_parse and sv_split. +typedef enum e_svopt +{ + // default: no escapes and no line terminator + SV_NOESCAPE_NOTERMINATE = 0, + // Escapes according to the C compiler. + SV_ESCAPE_C = 1, + // Line terminators + SV_TERMINATE_LF = 2, + SV_TERMINATE_CRLF = 4, + SV_TERMINATE_CR = 8, + // If sv_split keeps the end of line terminator, instead of replacing with '\0' + SV_KEEP_TERMINATOR = 16 +} e_svopt; + +/// Other escape sequences supported by the C compiler. +#define SV_ESCAPE_C_SUPPORTED "abtnvfr\?\"'\\" + +/// Parse state. +/// The field is [start,end[ +struct s_svstate +{ + const char* str; //< string to parse + int len; //< string length + int off; //< current offset in the string + int start; //< where the field starts + int end; //< where the field ends + enum e_svopt opt; //< parse options + char delim; //< field delimiter + bool done; //< if all the text has been parsed +}; + +/// Parses a single field in a delim-separated string. +/// The delimiter after the field is skipped. +/// +/// @param sv Parse state +/// @return 1 if a field was parsed, 0 if done, -1 on error. +int sv_parse_next(struct s_svstate* sv); + +/// Parses a delim-separated string. +/// Starts parsing at startoff and fills the pos array with position pairs. +/// out_pos[0] and out_pos[1] are the start and end of line. +/// Other position pairs are the start and end of fields. +/// Returns the number of fields found or -1 if an error occurs. +int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt); + +/// Splits a delim-separated string. +/// WARNING: this function modifies the input string +/// Starts splitting at startoff and fills the out_fields array. +/// out_fields[0] is the start of the next line. +/// Other entries are the start of fields (nul-teminated). +/// Returns the number of fields found or -1 if an error occurs. +int sv_split(char* str, int len, int startoff, char delim, char** out_fields, int nfields, enum e_svopt opt); + +/// Escapes src to out_dest according to the format of the C compiler. +/// Returns the length of the escaped string. +/// out_dest should be len*4+1 in size. +size_t sv_escape_c(char* out_dest, const char* src, size_t len, const char* escapes); + +/// Unescapes src to out_dest according to the format of the C compiler. +/// Returns the length of the unescaped string. +/// out_dest should be len+1 in size and can be the same buffer as src. +size_t sv_unescape_c(char* out_dest, const char* src, size_t len); + +/// Skips a C escape sequence (starting with '\\'). +const char* skip_escaped_c(const char* p); + +/// Opens and parses a file containing delim-separated columns, feeding them to the specified callback function row by row. +/// Tracks the progress of the operation (current line number, number of successfully processed rows). +/// Returns 'true' if it was able to process the specified file, or 'false' if it could not be read. +bool sv_readdb(const char* directory, const char* filename, char delim, int mincols, int maxcols, int maxrows, bool (*parseproc)(char* fields[], int columns, int current)); + + +/// StringBuf - dynamic string +struct StringBuf +{ + char *buf_; + char *ptr_; + unsigned int max_; +}; +typedef struct StringBuf StringBuf; + +StringBuf* StringBuf_Malloc(void); +void StringBuf_Init(StringBuf* self); +int StringBuf_Printf(StringBuf* self, const char* fmt, ...); +int StringBuf_Vprintf(StringBuf* self, const char* fmt, va_list args); +int StringBuf_Append(StringBuf* self, const StringBuf *sbuf); +int StringBuf_AppendStr(StringBuf* self, const char* str); +int StringBuf_Length(StringBuf* self); +char* StringBuf_Value(StringBuf* self); +void StringBuf_Clear(StringBuf* self); +void StringBuf_Destroy(StringBuf* self); +void StringBuf_Free(StringBuf* self); + +#endif /* _STRLIB_H_ */ diff --git a/src/common/thread.c b/src/common/thread.c new file mode 100644 index 000000000..315b310b2 --- /dev/null +++ b/src/common/thread.c @@ -0,0 +1,317 @@ +// +// Basic Threading abstraction (for pthread / win32 based systems) +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifdef WIN32 +#include "../common/winapi.h" +#define getpagesize() 4096 // @TODO: implement this properly (GetSystemInfo .. dwPageSize..). (Atm as on all supported win platforms its 4k its static.) +#define __thread __declspec( thread ) +#else +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> +#include <sched.h> +#endif + +#include "cbasetypes.h" +#include "malloc.h" +#include "showmsg.h" +#include "thread.h" + +// When Compiling using MSC (on win32..) we know we have support in any case! +#ifdef _MSC_VER +#define HAS_TLS +#endif + + +#define RA_THREADS_MAX 64 + +struct rAthread { + unsigned int myID; + + RATHREAD_PRIO prio; + rAthreadProc proc; + void *param; + + #ifdef WIN32 + HANDLE hThread; + #else + pthread_t hThread; + #endif +}; + + +#ifdef HAS_TLS +__thread int g_rathread_ID = -1; +#endif + + +/// +/// Subystem Code +/// +static struct rAthread l_threads[RA_THREADS_MAX]; + +void rathread_init(){ + register unsigned int i; + memset(&l_threads, 0x00, RA_THREADS_MAX * sizeof(struct rAthread) ); + + for(i = 0; i < RA_THREADS_MAX; i++){ + l_threads[i].myID = i; + } + + // now lets init thread id 0, which represnts the main thread +#ifdef HAS_TLS + g_rathread_ID = 0; +#endif + l_threads[0].prio = RAT_PRIO_NORMAL; + l_threads[0].proc = (rAthreadProc)0xDEADCAFE; + +}//end: rathread_init() + + + +void rathread_final(){ + register unsigned int i; + + // Unterminated Threads Left? + // Should'nt happen .. + // Kill 'em all! + // + for(i = 1; i < RA_THREADS_MAX; i++){ + if(l_threads[i].proc != NULL){ + ShowWarning("rAthread_final: unterminated Thread (tid %u entryPoint %p) - forcing to terminate (kill)\n", i, l_threads[i].proc); + rathread_destroy(&l_threads[i]); + } + } + + +}//end: rathread_final() + + + +// gets called whenever a thread terminated .. +static void rat_thread_terminated( rAthread handle ){ + + int id_backup = handle->myID; + + // Simply set all members to 0 (except the id) + memset(handle, 0x00, sizeof(struct rAthread)); + + handle->myID = id_backup; // done ;) + +}//end: rat_thread_terminated() + +#ifdef WIN32 +DWORD WINAPI _raThreadMainRedirector(LPVOID p){ +#else +static void *_raThreadMainRedirector( void *p ){ + sigset_t set; // on Posix Thread platforms +#endif + void *ret; + + // Update myID @ TLS to right id. +#ifdef HAS_TLS + g_rathread_ID = ((rAthread)p)->myID; +#endif + +#ifndef WIN32 + // When using posix threads + // the threads inherits the Signal mask from the thread which's spawned + // this thread + // so we've to block everything we dont care about. + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + sigaddset(&set, SIGPIPE); + + pthread_sigmask(SIG_BLOCK, &set, NULL); + +#endif + + + ret = ((rAthread)p)->proc( ((rAthread)p)->param ) ; + +#ifdef WIN32 + CloseHandle( ((rAthread)p)->hThread ); +#endif + + rat_thread_terminated( (rAthread)p ); +#ifdef WIN32 + return (DWORD)ret; +#else + return ret; +#endif +}//end: _raThreadMainRedirector() + + + + + +/// +/// API Level +/// +rAthread rathread_create( rAthreadProc entryPoint, void *param ){ + return rathread_createEx( entryPoint, param, (1<<23) /*8MB*/, RAT_PRIO_NORMAL ); +}//end: rathread_create() + + +rAthread rathread_createEx( rAthreadProc entryPoint, void *param, size_t szStack, RATHREAD_PRIO prio ){ +#ifndef WIN32 + pthread_attr_t attr; +#endif + size_t tmp; + unsigned int i; + rAthread handle = NULL; + + + // given stacksize aligned to systems pagesize? + tmp = szStack % getpagesize(); + if(tmp != 0) + szStack += tmp; + + + // Get a free Thread Slot. + for(i = 0; i < RA_THREADS_MAX; i++){ + if(l_threads[i].proc == NULL){ + handle = &l_threads[i]; + break; + } + } + + if(handle == NULL){ + ShowError("rAthread: cannot create new thread (entryPoint: %p) - no free thread slot found!", entryPoint); + return NULL; + } + + + + handle->proc = entryPoint; + handle->param = param; + +#ifdef WIN32 + handle->hThread = CreateThread(NULL, szStack, _raThreadMainRedirector, (void*)handle, 0, NULL); +#else + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, szStack); + + if(pthread_create(&handle->hThread, &attr, _raThreadMainRedirector, (void*)handle) != 0){ + handle->proc = NULL; + handle->param = NULL; + return NULL; + } + pthread_attr_destroy(&attr); +#endif + + rathread_prio_set( handle, prio ); + + return handle; +}//end: rathread_createEx + + +void rathread_destroy ( rAthread handle ){ +#ifdef WIN32 + if( TerminateThread(handle->hThread, 0) != FALSE){ + CloseHandle(handle->hThread); + rat_thread_terminated(handle); + } +#else + if( pthread_cancel( handle->hThread ) == 0){ + + // We have to join it, otherwise pthread wont re-cycle its internal ressources assoc. with this thread. + // + pthread_join( handle->hThread, NULL ); + + // Tell our manager to release ressources ;) + rat_thread_terminated(handle); + } +#endif +}//end: rathread_destroy() + +rAthread rathread_self( ){ +#ifdef HAS_TLS + rAthread handle = &l_threads[g_rathread_ID]; + + if(handle->proc != NULL) // entry point set, so its used! + return handle; +#else + // .. so no tls means we have to search the thread by its api-handle .. + int i; + + #ifdef WIN32 + HANDLE hSelf; + hSelf = GetCurrent = GetCurrentThread(); + #else + pthread_t hSelf; + hSelf = pthread_self(); + #endif + + for(i = 0; i < RA_THREADS_MAX; i++){ + if(l_threads[i].hThread == hSelf && l_threads[i].proc != NULL) + return &l_threads[i]; + } + +#endif + + return NULL; +}//end: rathread_self() + + +int rathread_get_tid(){ + +#ifdef HAS_TLS + return g_rathread_ID; +#else + // todo + #ifdef WIN32 + return (int)GetCurrentThreadId(); + #else + return (intptr_t)pthread_self(); + #endif + +#endif + +}//end: rathread_get_tid() + + +bool rathread_wait( rAthread handle, void* *out_exitCode ){ + + // Hint: + // no thread data cleanup routine call here! + // its managed by the callProxy itself.. + // +#ifdef WIN32 + WaitForSingleObject(handle->hThread, INFINITE); + return true; +#else + if(pthread_join(handle->hThread, out_exitCode) == 0) + return true; + return false; +#endif + +}//end: rathread_wait() + + +void rathread_prio_set( rAthread handle, RATHREAD_PRIO prio ){ + handle->prio = RAT_PRIO_NORMAL; + //@TODO +}//end: rathread_prio_set() + + +RATHREAD_PRIO rathread_prio_get( rAthread handle){ + return handle->prio; +}//end: rathread_prio_get() + + +void rathread_yield(){ +#ifdef WIN32 + SwitchToThread(); +#else + sched_yield(); +#endif +}//end: rathread_yield() diff --git a/src/common/thread.h b/src/common/thread.h new file mode 100644 index 000000000..a5a66e954 --- /dev/null +++ b/src/common/thread.h @@ -0,0 +1,119 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#pragma once +#ifndef _rA_THREAD_H_ +#define _rA_THREAD_H_ + +#include "../common/cbasetypes.h" + +typedef struct rAthread *rAthread; +typedef void* (*rAthreadProc)(void*); + +typedef enum RATHREAD_PRIO { + RAT_PRIO_LOW = 0, + RAT_PRIO_NORMAL, + RAT_PRIO_HIGH +} RATHREAD_PRIO; + + +/** + * Creates a new Thread + * + * @param entyPoint - entryProc, + * @param param - general purpose parameter, would be given as parameter to the thread's entrypoint. + * + * @return not NULL if success + */ +rAthread rathread_create( rAthreadProc entryPoint, void *param ); + + +/** + * Creates a new Thread (with more creation options) + * + * @param entyPoint - entryProc, + * @param param - general purpose parameter, would be given as parameter to the thread's entrypoint + * @param szStack - stack Size in bytes + * @param prio - Priority of the Thread @ OS Scheduler.. + * + * @return not NULL if success + */ +rAthread rathread_createEx( rAthreadProc entryPoint, void *param, size_t szStack, RATHREAD_PRIO prio ); + + +/** + * Destroys the given Thread immediatly + * + * @note The Handle gets invalid after call! dont use it afterwards. + * + * @param handle - thread to destroy. + */ +void rathread_destroy ( rAthread handle ); + + +/** + * Returns the thread handle of the thread calling this function + * + * @note this wont work @ programms main thread + * @note the underlying implementation might not perform very well, cache the value received! + * + * @return not NULL if success + */ +rAthread rathread_self( ); + + +/** + * Returns own thrad id (TID) + * + * @note this is an unique identifier for the calling thread, and + * depends on platfrom / compiler, and may not be the systems Thread ID! + * + * @return -1 when fails, otherwise >= 0 + */ +int rathread_get_tid(); + + +/** + * Waits for the given thread to terminate + * + * @param handle - thread to wait (join) for + * @param out_Exitcode - [OPTIONAL] - if given => Exitcode (value) of the given thread - if it's terminated + * + * @return true - if the given thread has been terminated. + */ +bool rathread_wait( rAthread handle, void* *out_exitCode ); + + +/** + * Sets the given PRIORITY @ OS Task Scheduler + * + * @param handle - thread to set prio for + * @param rio - the priority (RAT_PRIO_LOW ... ) + */ +void rathread_prio_set( rAthread handle, RATHREAD_PRIO prio ); + + +/** + * Gets the current Prio of the given trhead + * + * @param handle - the thread to get the prio for. + */ +RATHREAD_PRIO rathread_prio_get( rAthread handle); + + +/** + * Tells the OS scheduler to yield the execution of the calling thread + * + * @note: this will not "pause" the thread, + * it just allows the OS to spent the remaining time + * of the slice to another thread. + */ +void rathread_yield(); + + + +void rathread_init(); +void rathread_final(); + + +#endif diff --git a/src/common/timer.c b/src/common/timer.c new file mode 100644 index 000000000..c239a9d70 --- /dev/null +++ b/src/common/timer.c @@ -0,0 +1,432 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "timer.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef WIN32 +#include "../common/winapi.h" // GetTickCount() +#else +#include <unistd.h> +#include <sys/time.h> // struct timeval, gettimeofday() +#endif + +// If the server can't handle processing thousands of monsters +// or many connected clients, please increase TIMER_MIN_INTERVAL. +#define TIMER_MIN_INTERVAL 50 +#define TIMER_MAX_INTERVAL 1000 + +// timers (array) +static struct TimerData* timer_data = NULL; +static int timer_data_max = 0; +static int timer_data_num = 0; + +// free timers (array) +static int* free_timer_list = NULL; +static int free_timer_list_max = 0; +static int free_timer_list_pos = 0; + + +/// Comparator for the timer heap. (minimum tick at top) +/// Returns negative if tid1's tick is smaller, positive if tid2's tick is smaller, 0 if equal. +/// +/// @param tid1 First timer +/// @param tid2 Second timer +/// @return negative if tid1 is top, positive if tid2 is top, 0 if equal +#define DIFFTICK_MINTOPCMP(tid1,tid2) DIFF_TICK(timer_data[tid1].tick,timer_data[tid2].tick) + +// timer heap (binary heap of tid's) +static BHEAP_VAR(int, timer_heap); + + +// server startup time +time_t start_time; + + +/*---------------------------- + * Timer debugging + *----------------------------*/ +struct timer_func_list { + struct timer_func_list* next; + TimerFunc func; + char* name; +} *tfl_root = NULL; + +/// Sets the name of a timer function. +int add_timer_func_list(TimerFunc func, char* name) +{ + struct timer_func_list* tfl; + + if (name) { + for( tfl=tfl_root; tfl != NULL; tfl=tfl->next ) + {// check suspicious cases + if( func == tfl->func ) + ShowWarning("add_timer_func_list: duplicating function %p(%s) as %s.\n",tfl->func,tfl->name,name); + else if( strcmp(name,tfl->name) == 0 ) + ShowWarning("add_timer_func_list: function %p has the same name as %p(%s)\n",func,tfl->func,tfl->name); + } + CREATE(tfl,struct timer_func_list,1); + tfl->next = tfl_root; + tfl->func = func; + tfl->name = aStrdup(name); + tfl_root = tfl; + } + return 0; +} + +/// Returns the name of the timer function. +char* search_timer_func_list(TimerFunc func) +{ + struct timer_func_list* tfl; + + for( tfl=tfl_root; tfl != NULL; tfl=tfl->next ) + if (func == tfl->func) + return tfl->name; + + return "unknown timer function"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ + +#if defined(ENABLE_RDTSC) +static uint64 RDTSC_BEGINTICK = 0, RDTSC_CLOCK = 0; + +static __inline uint64 _rdtsc(){ + register union{ + uint64 qw; + uint32 dw[2]; + } t; + + asm volatile("rdtsc":"=a"(t.dw[0]), "=d"(t.dw[1]) ); + + return t.qw; +} + +static void rdtsc_calibrate(){ + uint64 t1, t2; + int32 i; + + ShowStatus("Calibrating Timer Source, please wait... "); + + RDTSC_CLOCK = 0; + + for(i = 0; i < 5; i++){ + t1 = _rdtsc(); + usleep(1000000); //1000 MS + t2 = _rdtsc(); + RDTSC_CLOCK += (t2 - t1) / 1000; + } + RDTSC_CLOCK /= 5; + + RDTSC_BEGINTICK = _rdtsc(); + + ShowMessage(" done. (Frequency: %u Mhz)\n", (uint32)(RDTSC_CLOCK/1000) ); +} + +#endif + +/// platform-abstracted tick retrieval +static unsigned int tick(void) +{ +#if defined(WIN32) + return GetTickCount(); +#elif defined(ENABLE_RDTSC) + // + return (unsigned int)((_rdtsc() - RDTSC_BEGINTICK) / RDTSC_CLOCK); + // +#elif defined(HAVE_MONOTONIC_CLOCK) + struct timespec tval; + clock_gettime(CLOCK_MONOTONIC, &tval); + return tval.tv_sec * 1000 + tval.tv_nsec / 1000000; +#else + struct timeval tval; + gettimeofday(&tval, NULL); + return tval.tv_sec * 1000 + tval.tv_usec / 1000; +#endif +} + +////////////////////////////////////////////////////////////////////////// +#if defined(TICK_CACHE) && TICK_CACHE > 1 +////////////////////////////////////////////////////////////////////////// +// tick is cached for TICK_CACHE calls +static unsigned int gettick_cache; +static int gettick_count = 1; + +unsigned int gettick_nocache(void) +{ + gettick_count = TICK_CACHE; + gettick_cache = tick(); + return gettick_cache; +} + +unsigned int gettick(void) +{ + return ( --gettick_count == 0 ) ? gettick_nocache() : gettick_cache; +} +////////////////////////////// +#else +////////////////////////////// +// tick doesn't get cached +unsigned int gettick_nocache(void) +{ + return tick(); +} + +unsigned int gettick(void) +{ + return tick(); +} +////////////////////////////////////////////////////////////////////////// +#endif +////////////////////////////////////////////////////////////////////////// + +/*====================================== + * CORE : Timer Heap + *--------------------------------------*/ + +/// Adds a timer to the timer_heap +static void push_timer_heap(int tid) +{ + BHEAP_ENSURE(timer_heap, 1, 256); + BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP); +} + +/*========================== + * Timer Management + *--------------------------*/ + +/// Returns a free timer id. +static int acquire_timer(void) +{ + int tid; + + // select a free timer + if (free_timer_list_pos) { + do { + tid = free_timer_list[--free_timer_list_pos]; + } while(tid >= timer_data_num && free_timer_list_pos > 0); + } else + tid = timer_data_num; + + // check available space + if( tid >= timer_data_num ) + for (tid = timer_data_num; tid < timer_data_max && timer_data[tid].type; tid++); + if (tid >= timer_data_num && tid >= timer_data_max) + {// expand timer array + timer_data_max += 256; + if( timer_data ) + RECREATE(timer_data, struct TimerData, timer_data_max); + else + CREATE(timer_data, struct TimerData, timer_data_max); + memset(timer_data + (timer_data_max - 256), 0, sizeof(struct TimerData)*256); + } + + if( tid >= timer_data_num ) + timer_data_num = tid + 1; + + return tid; +} + +/// Starts a new timer that is deleted once it expires (single-use). +/// Returns the timer's id. +int add_timer(unsigned int tick, TimerFunc func, int id, intptr_t data) +{ + int tid; + + tid = acquire_timer(); + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_ONCE_AUTODEL; + timer_data[tid].interval = 1000; + push_timer_heap(tid); + + return tid; +} + +/// Starts a new timer that automatically restarts itself (infinite loop until manually removed). +/// Returns the timer's id, or INVALID_TIMER if it fails. +int add_timer_interval(unsigned int tick, TimerFunc func, int id, intptr_t data, int interval) +{ + int tid; + + if( interval < 1 ) + { + ShowError("add_timer_interval: invalid interval (tick=%u %p[%s] id=%d data=%d diff_tick=%d)\n", tick, func, search_timer_func_list(func), id, data, DIFF_TICK(tick, gettick())); + return INVALID_TIMER; + } + + tid = acquire_timer(); + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_INTERVAL; + timer_data[tid].interval = interval; + push_timer_heap(tid); + + return tid; +} + +/// Retrieves internal timer data +const struct TimerData* get_timer(int tid) +{ + return ( tid >= 0 && tid < timer_data_num ) ? &timer_data[tid] : NULL; +} + +/// Marks a timer specified by 'id' for immediate deletion once it expires. +/// Param 'func' is used for debug/verification purposes. +/// Returns 0 on success, < 0 on failure. +int delete_timer(int tid, TimerFunc func) +{ + if( tid < 0 || tid >= timer_data_num ) + { + ShowError("delete_timer error : no such timer %d (%p(%s))\n", tid, func, search_timer_func_list(func)); + return -1; + } + if( timer_data[tid].func != func ) + { + ShowError("delete_timer error : function mismatch %p(%s) != %p(%s)\n", timer_data[tid].func, search_timer_func_list(timer_data[tid].func), func, search_timer_func_list(func)); + return -2; + } + + timer_data[tid].func = NULL; + timer_data[tid].type = TIMER_ONCE_AUTODEL; + + return 0; +} + +/// Adjusts a timer's expiration time. +/// Returns the new tick value, or -1 if it fails. +int addtick_timer(int tid, unsigned int tick) +{ + return settick_timer(tid, timer_data[tid].tick+tick); +} + +/// Modifies a timer's expiration time (an alternative to deleting a timer and starting a new one). +/// Returns the new tick value, or -1 if it fails. +int settick_timer(int tid, unsigned int tick) +{ + size_t i; + + // search timer position + ARR_FIND(0, BHEAP_LENGTH(timer_heap), i, BHEAP_DATA(timer_heap)[i] == tid); + if( i == BHEAP_LENGTH(timer_heap) ) + { + ShowError("settick_timer: no such timer %d (%p(%s))\n", tid, timer_data[tid].func, search_timer_func_list(timer_data[tid].func)); + return -1; + } + + if( (int)tick == -1 ) + tick = 0;// add 1ms to avoid the error value -1 + + if( timer_data[tid].tick == tick ) + return (int)tick;// nothing to do, already in propper position + + // pop and push adjusted timer + BHEAP_POPINDEX(timer_heap, i, DIFFTICK_MINTOPCMP); + timer_data[tid].tick = tick; + BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP); + return (int)tick; +} + +/// Executes all expired timers. +/// Returns the value of the smallest non-expired timer (or 1 second if there aren't any). +int do_timer(unsigned int tick) +{ + int diff = TIMER_MAX_INTERVAL; // return value + + // process all timers one by one + while( BHEAP_LENGTH(timer_heap) ) + { + int tid = BHEAP_PEEK(timer_heap);// top element in heap (smallest tick) + + diff = DIFF_TICK(timer_data[tid].tick, tick); + if( diff > 0 ) + break; // no more expired timers to process + + // remove timer + BHEAP_POP(timer_heap, DIFFTICK_MINTOPCMP); + timer_data[tid].type |= TIMER_REMOVE_HEAP; + + if( timer_data[tid].func ) + { + if( diff < -1000 ) + // timer was delayed for more than 1 second, use current tick instead + timer_data[tid].func(tid, tick, timer_data[tid].id, timer_data[tid].data); + else + timer_data[tid].func(tid, timer_data[tid].tick, timer_data[tid].id, timer_data[tid].data); + } + + // in the case the function didn't change anything... + if( timer_data[tid].type & TIMER_REMOVE_HEAP ) + { + timer_data[tid].type &= ~TIMER_REMOVE_HEAP; + + switch( timer_data[tid].type ) + { + default: + case TIMER_ONCE_AUTODEL: + timer_data[tid].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + RECREATE(free_timer_list,int,free_timer_list_max); + memset(free_timer_list + (free_timer_list_max - 256), 0, 256 * sizeof(int)); + } + free_timer_list[free_timer_list_pos++] = tid; + break; + case TIMER_INTERVAL: + if( DIFF_TICK(timer_data[tid].tick, tick) < -1000 ) + timer_data[tid].tick = tick + timer_data[tid].interval; + else + timer_data[tid].tick += timer_data[tid].interval; + push_timer_heap(tid); + break; + } + } + } + + return cap_value(diff, TIMER_MIN_INTERVAL, TIMER_MAX_INTERVAL); +} + +unsigned long get_uptime(void) +{ + return (unsigned long)difftime(time(NULL), start_time); +} + +void timer_init(void) +{ +#if defined(ENABLE_RDTSC) + rdtsc_calibrate(); +#endif + + time(&start_time); +} + +void timer_final(void) +{ + struct timer_func_list *tfl; + struct timer_func_list *next; + + for( tfl=tfl_root; tfl != NULL; tfl = next ) { + next = tfl->next; // copy next pointer + aFree(tfl->name); // free structures + aFree(tfl); + } + + if (timer_data) aFree(timer_data); + BHEAP_CLEAR(timer_heap); + if (free_timer_list) aFree(free_timer_list); +} diff --git a/src/common/timer.h b/src/common/timer.h new file mode 100644 index 000000000..d45c73d12 --- /dev/null +++ b/src/common/timer.h @@ -0,0 +1,57 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#include "../common/cbasetypes.h" + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +#define INVALID_TIMER -1 + +// timer flags +enum { + TIMER_ONCE_AUTODEL = 0x01, + TIMER_INTERVAL = 0x02, + TIMER_REMOVE_HEAP = 0x10, +}; + +// Struct declaration + +typedef int (*TimerFunc)(int tid, unsigned int tick, int id, intptr_t data); + +struct TimerData { + unsigned int tick; + TimerFunc func; + int type; + int interval; + int heap_pos; + + // general-purpose storage + int id; + intptr_t data; +}; + +// Function prototype declaration + +unsigned int gettick(void); +unsigned int gettick_nocache(void); + +int add_timer(unsigned int tick, TimerFunc func, int id, intptr_t data); +int add_timer_interval(unsigned int tick, TimerFunc func, int id, intptr_t data, int interval); +const struct TimerData* get_timer(int tid); +int delete_timer(int tid, TimerFunc func); + +int addtick_timer(int tid, unsigned int tick); +int settick_timer(int tid, unsigned int tick); + +int add_timer_func_list(TimerFunc func, char* name); + +unsigned long get_uptime(void); + +int do_timer(unsigned int tick); +void timer_init(void); +void timer_final(void); + +#endif /* _TIMER_H_ */ diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 000000000..296df7e70 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,283 @@ +// 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/showmsg.h" +#include "socket.h" +#include "utils.h" + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> // floor() + +#ifdef WIN32 + #include "../common/winapi.h" + #ifndef F_OK + #define F_OK 0x0 + #endif /* F_OK */ +#else + #include <unistd.h> + #include <dirent.h> + #include <sys/stat.h> +#endif + + +/// Dumps given buffer into file pointed to by a handle. +void WriteDump(FILE* fp, const void* buffer, size_t length) +{ + size_t i; + char hex[48+1], ascii[16+1]; + + fprintf(fp, "--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; + + for( i = 0; i < length; i++ ) + { + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); + + if( (i%16) == 15 ) + { + fprintf(fp, "%03X %s %s\n", (unsigned int)(i/16), hex, ascii); + } + } + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + fprintf(fp, "%03X %-48s %-16s\n", (unsigned int)(i/16), hex, ascii); + } +} + + +/// Dumps given buffer on the console. +void ShowDump(const void* buffer, size_t length) +{ + size_t i; + char hex[48+1], ascii[16+1]; + + ShowDebug("--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; + + for( i = 0; i < length; i++ ) + { + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); + + if( (i%16) == 15 ) + { + ShowDebug("%03X %s %s\n", i/16, hex, ascii); + } + } + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + ShowDebug("%03X %-48s %-16s\n", i/16, hex, ascii); + } +} + + +#ifdef WIN32 + +static char* checkpath(char *path, const char *srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='/') { + *p++ = '\\'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + WIN32_FIND_DATAA FindFileData; + HANDLE hFind; + char tmppath[MAX_PATH+1]; + + const char *path = (p ==NULL)? "." : p; + const char *pattern = (pat==NULL)? "" : pat; + + checkpath(tmppath,path); + if( PATHSEP != tmppath[strlen(tmppath)-1]) + strcat(tmppath, "\\*"); + else + strcat(tmppath, "*"); + + hFind = FindFirstFileA(tmppath, &FindFileData); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(FindFileData.cFileName, ".") == 0) + continue; + if (strcmp(FindFileData.cFileName, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path,PATHSEP,FindFileData.cFileName); + + if (FindFileData.cFileName && strstr(FindFileData.cFileName, pattern)) { + func( tmppath ); + } + + + if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + findfile(tmppath, pat, func); + } + }while (FindNextFileA(hFind, &FindFileData) != 0); + FindClose(hFind); + } + return; +} +#else + +#define MAX_DIR_PATH 2048 + +static char* checkpath(char *path, const char*srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='\\') { + *p++ = '/'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + DIR* dir; // pointer to the scanned directory. + struct dirent* entry; // pointer to one directory entry. + struct stat dir_stat; // used by stat(). + char tmppath[MAX_DIR_PATH+1]; + char path[MAX_DIR_PATH+1]= "."; + const char *pattern = (pat==NULL)? "" : pat; + if(p!=NULL) strcpy(path,p); + + // open the directory for reading + dir = opendir( checkpath(path, path) ); + if (!dir) { + ShowError("Cannot read directory '%s'\n", path); + return; + } + + // scan the directory, traversing each sub-directory + // matching the pattern for each file name. + while ((entry = readdir(dir))) { + // skip the "." and ".." entries. + if (strcmp(entry->d_name, ".") == 0) + continue; + if (strcmp(entry->d_name, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path, PATHSEP, entry->d_name); + + // check if the pattern matchs. + if (entry->d_name && strstr(entry->d_name, pattern)) { + func( tmppath ); + } + // check if it is a directory. + if (stat(tmppath, &dir_stat) == -1) { + ShowError("stat error %s\n': ", tmppath); + continue; + } + // is this a directory? + if (S_ISDIR(dir_stat.st_mode)) { + // decent recursivly + findfile(tmppath, pat, func); + } + }//end while + + closedir(dir); +} +#endif + +bool exists(const char* filename) +{ + return !access(filename, F_OK); +} + +uint8 GetByte(uint32 val, int idx) +{ + switch( idx ) + { + case 0: return (uint8)( (val & 0x000000FF) ); + case 1: return (uint8)( (val & 0x0000FF00) >> 0x08 ); + case 2: return (uint8)( (val & 0x00FF0000) >> 0x10 ); + case 3: return (uint8)( (val & 0xFF000000) >> 0x18 ); + default: +#if defined(DEBUG) + ShowDebug("GetByte: invalid index (idx=%d)\n", idx); +#endif + return 0; + } +} + +uint16 GetWord(uint32 val, int idx) +{ + switch( idx ) + { + case 0: return (uint16)( (val & 0x0000FFFF) ); + case 1: return (uint16)( (val & 0xFFFF0000) >> 0x10 ); + default: +#if defined(DEBUG) + ShowDebug("GetWord: invalid index (idx=%d)\n", idx); +#endif + return 0; + } +} +uint16 MakeWord(uint8 byte0, uint8 byte1) +{ + return byte0 | (byte1 << 0x08); +} + +uint32 MakeDWord(uint16 word0, uint16 word1) +{ + return + ( (uint32)(word0 ) )| + ( (uint32)(word1 << 0x10) ); +} + + +/// calculates the value of A / B, in percent (rounded down) +unsigned int get_percentage(const unsigned int A, const unsigned int B) +{ + double result; + + if( B == 0 ) + { + ShowError("get_percentage(): divison by zero! (A=%u,B=%u)\n", A, B); + return ~0U; + } + + result = 100 * ((double)A / (double)B); + + if( result > UINT_MAX ) + { + ShowError("get_percentage(): result percentage too high! (A=%u,B=%u,result=%g)\n", A, B, result); + return UINT_MAX; + } + + return (unsigned int)floor(result); +} diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 000000000..8e39f2655 --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,32 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include "../common/cbasetypes.h" +#include <stdio.h> // FILE* + +// generate a hex dump of the first 'length' bytes of 'buffer' +void WriteDump(FILE* fp, const void* buffer, size_t length); +void ShowDump(const void* buffer, size_t length); + +void findfile(const char *p, const char *pat, void (func)(const char*)); +bool exists(const char* filename); + +//Caps values to min/max +#define cap_value(a, min, max) ((a >= max) ? max : (a <= min) ? min : a) + +/// calculates the value of A / B, in percent (rounded down) +unsigned int get_percentage(const unsigned int A, const unsigned int B); + +////////////////////////////////////////////////////////////////////////// +// byte word dword access [Shinomori] +////////////////////////////////////////////////////////////////////////// + +extern uint8 GetByte(uint32 val, int idx); +extern uint16 GetWord(uint32 val, int idx); +extern uint16 MakeWord(uint8 byte0, uint8 byte1); +extern uint32 MakeDWord(uint16 word0, uint16 word1); + +#endif /* _UTILS_H_ */ diff --git a/src/common/winapi.h b/src/common/winapi.h new file mode 100644 index 000000000..7ce555049 --- /dev/null +++ b/src/common/winapi.h @@ -0,0 +1,36 @@ +#pragma once + + +#define STRICT +#define NTDDI_VERSION NTDDI_WIN2K +#define _WIN32_WINNT 0x0500 +#define WINVER 0x0500 +#define _WIN32_IE 0x0600 +#define WIN32_LEAN_AND_MEAN +#define NOCOMM +#define NOKANJI +#define NOHELP +#define NOMCX +#define NOCLIPBOARD +#define NOCOLOR +#define NONLS +#define NOMEMMGR +#define NOMETAFILE +#define NOOPENFILE +#define NOSERVICE +#define NOSOUND +#define NOTEXTMETRIC + + +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS + +#include <io.h> +#include <Windows.h> +#include <WinSock2.h> +#include <In6addr.h> +#include <Ws2tcpip.h> +#include <Mswsock.h> +#include <MMSystem.h> + + diff --git a/src/config/classes/general.h b/src/config/classes/general.h new file mode 100644 index 000000000..6e6cc1425 --- /dev/null +++ b/src/config/classes/general.h @@ -0,0 +1,23 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _CONFIG_GENERAL_H_ +#define _CONFIG_GENERAL_H_ + +/** + * rAthena configuration file (http://rathena.org) + * For detailed guidance on these check http://rathena.org/wiki/SRC/config/ + **/ + +/** + * Default Magical Reflection Behavior + * - When reflecting, reflected damage depends on gears caster is wearing, not target + * - When disabled damage depends on gears target is wearing, not caster. + * @values 1 (enabled) or 0 (disabled) + **/ +#define MAGIC_REFLECTION_TYPE 1 + +/** + * No settings past this point + **/ + +#endif // _CONFIG_GENERAL_H_ diff --git a/src/config/const.h b/src/config/const.h new file mode 100644 index 000000000..5fb74e22e --- /dev/null +++ b/src/config/const.h @@ -0,0 +1,99 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _RRCONFIGS_CONST_ +#define _RRCONFIGS_CONST_ + +/** + * rAthena configuration file (http://rathena.org) + * For detailed guidance on these check http://rathena.org/wiki/SRC/config/ + **/ + +/** + * @INFO: This file holds constants that aims at making code smoother and more efficient + */ + +/** + * "Sane Checks" to save you from compiling with cool bugs + **/ +#if SECURE_NPCTIMEOUT_INTERVAL <= 0 + #error SECURE_NPCTIMEOUT_INTERVAL should be at least 1 (1s) +#endif +#if SECURE_NPCTIMEOUT < 0 + #error SECURE_NPCTIMEOUT cannot be lower than 0 +#endif + +/** + * Path within the /db folder to (non-)renewal specific db files + **/ +#ifdef RENEWAL + #define DBPATH "re/" +#else + #define DBPATH "pre-re/" +#endif + +/** + * DefType + **/ +#ifdef RENEWAL + typedef short defType; + #define DEFTYPE_MIN SHRT_MIN + #define DEFTYPE_MAX SHRT_MAX +#else + typedef signed char defType; + #define DEFTYPE_MIN CHAR_MIN + #define DEFTYPE_MAX CHAR_MAX +#endif + +/* pointer size fix which fixes several gcc warnings */ +#ifdef __64BIT__ + #define __64BPRTSIZE(y) (intptr)y +#else + #define __64BPRTSIZE(y) y +#endif + +/* ATCMD_FUNC(mobinfo) HIT and FLEE calculations */ +#ifdef RENEWAL + #define MOB_FLEE(mob) ( mob->lv + mob->status.agi + mob->status.luk/5 + 100 ) + #define MOB_HIT(mob) ( mob->lv + mob->status.dex + mob->status.luk/3 + 175 ) +#else + #define MOB_FLEE(mob) ( mob->lv + mob->status.agi ) + #define MOB_HIT(mob) ( mob->lv + mob->status.dex ) +#endif + +/* Renewal's dmg level modifier, used as a macro for a easy way to turn off. */ +#ifdef RENEWAL_LVDMG + #define RE_LVL_DMOD(val) \ + if( status_get_lv(src) > 100 && val > 0 ) \ + skillratio = skillratio * status_get_lv(src) / val; + #define RE_LVL_MDMOD(val) \ + if( status_get_lv(src) > 100 && val > 0) \ + md.damage = md.damage * status_get_lv(src) / val; + /* ranger traps special */ + #define RE_LVL_TMDMOD() \ + if( status_get_lv(src) > 100 ) \ + md.damage = md.damage * 150 / 100 + md.damage * status_get_lv(src) / 100; +#else + #define RE_LVL_DMOD(val) + #define RE_LVL_MDMOD(val) + #define RE_LVL_TMDMOD() +#endif + +/* Feb 1st 2012 */ +#if PACKETVER >= 20120201 + #define NEW_CARTS + #define MAX_CARTS 9 +#else + #define MAX_CARTS 5 +#endif + +// Renewal variable cast time reduction +#ifdef RENEWAL_CAST + #define VARCAST_REDUCTION(val){ \ + if( (varcast_r += val) != 0 && varcast_r >= 0 ) \ + time = time * (1 - (float)min(val, 100) / 100); \ + } +#endif +/** + * End of File + **/ +#endif diff --git a/src/config/core.h b/src/config/core.h new file mode 100644 index 000000000..1e8ce9992 --- /dev/null +++ b/src/config/core.h @@ -0,0 +1,67 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _CONFIG_CORE_H_ +#define _CONFIG_CORE_H_ + +/** + * rAthena configuration file (http://rathena.org) + * For detailed guidance on these check http://rathena.org/wiki/SRC/config/ + **/ + +/// Max number of items on @autolootid list +#define AUTOLOOTITEM_SIZE 10 + +/// The maximum number of atcommand suggestions +#define MAX_SUGGESTIONS 10 + +/// Comment to disable the official walk path +/// The official walkpath disables users from taking non-clear walk paths, +/// e.g. if they want to get around an obstacle they have to walk around it, +/// while with OFFICIAL_WALKPATH disabled if they click to walk around a obstacle the server will do it automatically +#define OFFICIAL_WALKPATH + +/// leave this line uncommented to enable callfunc checks when processing scripts. +/// while allowed, the script engine will attempt to match user-defined functions +/// in scripts allowing direct function callback (without the use of callfunc.) +/// this CAN affect performance, so if you find scripts running slower or find +/// your map-server using more resources while this is active, comment the line +#define SCRIPT_CALLFUNC_CHECK + +/// Uncomment to disable rAthena's anonymous stat report +/// We kindly ask you to consider keeping it enabled, it helps us improve rAthena. +//#define STATS_OPT_OUT + +/// uncomment to enable query_sql script command and mysql logs to function on it's own thread +/// be aware this feature is under tests and you should use at your own risk, we however +/// welcome any feedback you may have regarding this feature, please send us all bug reports. +//#define BETA_THREAD_TEST + +/// Uncomment to enable the Cell Stack Limit mod. +/// It's only config is the battle_config cell_stack_limit. +/// Only chars affected are those defined in BL_CHAR (mobs and players currently) +//#define CELL_NOSTACK + +/// Uncomment to enable circular area checks. +/// By default, all range checks in Aegis are of Square shapes, so a weapon range +/// - of 10 allows you to attack from anywhere within a 21x21 area. +/// Enabling this changes such checks to circular checks, which is more realistic, +/// - but is not the official behaviour. +//#define CIRCULAR_AREA + +/// Uncomment to enable Non Stackable items unique ID +/// By enabling it, the system will create an unique id for each new non stackable item created +//#define NSI_UNIQUE_ID + +/** + * No settings past this point + **/ +#include "./renewal.h" +#include "./secure.h" +#include "./classes/general.h" + +/** + * Constants come last; so they process anything that could've been modified in early includes + **/ +#include "./const.h" + +#endif // _CONFIG_CORE_H_ diff --git a/src/config/renewal.h b/src/config/renewal.h new file mode 100644 index 000000000..339937adb --- /dev/null +++ b/src/config/renewal.h @@ -0,0 +1,71 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _CONFIG_RENEWAL_H_ +#define _CONFIG_RENEWAL_H_ + +/** + * rAthena configuration file (http://rathena.org) + * For detailed guidance on these check http://rathena.org/wiki/SRC/config/ + **/ + +/** + * @INFO: This file holds general-purpose renewal settings, for class-specific ones check /src/config/classes folder + **/ + +/// game renewal server mode +/// (disable by commenting the line) +/// +/// leave this line to enable renewal specific support such as renewal formulas +#define RENEWAL + +/// renewal cast time +/// (disable by commenting the line) +/// +/// leave this line to enable renewal casting time algorithms +/// cast time is decreased by DEX * 2 + INT while 20% of the cast time is not reduced by stats. +/// example: +/// on a skill whos cast time is 10s, only 8s may be reduced. the other 2s are part of a +/// "fixed cast time" which can only be reduced by specialist items and skills +#define RENEWAL_CAST + +/// renewal drop rate algorithms +/// (disable by commenting the line) +/// +/// leave this line to enable renewal item drop rate algorithms +/// while enabled a special modified based on the difference between the player and monster level is applied +/// based on the http://irowiki.org/wiki/Drop_System#Level_Factor table +#define RENEWAL_DROP + +/// renewal exp rate algorithms +/// (disable by commenting the line) +/// +/// leave this line to enable renewal item exp rate algorithms +/// while enabled a special modified based on the difference between the player and monster level is applied +#define RENEWAL_EXP + +/// renewal level modifier on damage +/// (disable by commenting the line) +/// +// leave this line to enable renewal base level modifier on skill damage (selected skills only) +#define RENEWAL_LVDMG + +/// renewal enchant deadly poison algorithm +/// +/// leave this line to enable the renewed EDP algorithm +/// under renewal mode: +/// - damage is NOT increased by 400% +/// - it does NOT affect grimtooth +/// - weapon and status ATK are increased +#define RENEWAL_EDP + +/// renewal ASPD [malufett] +/// (disable by commenting the line) +/// +/// leave this line to enable renewal ASPD +/// - shield penalty is applied +/// - AGI has a greater factor in ASPD increase +/// - there is a change in how skills/items give ASPD +/// - some skill/item ASPD bonuses won't stack +#define RENEWAL_ASPD + +#endif // _CONFIG_RENEWAL_H_ diff --git a/src/config/secure.h b/src/config/secure.h new file mode 100644 index 000000000..c57db018a --- /dev/null +++ b/src/config/secure.h @@ -0,0 +1,33 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _CONFIG_SECURE_H_ +#define _CONFIG_SECURE_H_ + +/** + * rAthena configuration file (http://rathena.org) + * For detailed guidance on these check http://rathena.org/wiki/SRC/config/ + **/ + +/** + * @INFO: This file holds optional security settings + **/ + +/** + * Optional NPC Dialog Timer + * When enabled all npcs dialog will 'timeout' if user is on idle for longer than the amount of seconds allowed + * - On 'timeout' the npc dialog window changes its next/menu to a 'close' button + * @values + * - ? : Desired idle time in seconds (e.g. 10) + * - 0 : Disabled + **/ +#define SECURE_NPCTIMEOUT 0 + +/** + * (Secure) Optional NPC Dialog Timer + * @requirement : SECURE_NPCTIMEOUT must be enabled + * Minimum Interval Between timeout checks in seconds + * Default: 1s + **/ +#define SECURE_NPCTIMEOUT_INTERVAL 1 + +#endif // _CONFIG_SECURE_H_ diff --git a/src/login/CMakeLists.txt b/src/login/CMakeLists.txt new file mode 100644 index 000000000..fa657f8fd --- /dev/null +++ b/src/login/CMakeLists.txt @@ -0,0 +1,12 @@ + +# +# setup +# +set( LOGIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) +set( SQL_LOGIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) + + +# +# targets +# +add_subdirectory( sql ) diff --git a/src/login/Makefile.in b/src/login/Makefile.in new file mode 100644 index 000000000..d0fc34756 --- /dev/null +++ b/src/login/Makefile.in @@ -0,0 +1,83 @@ + +COMMON_H = $(shell ls ../common/*.h) + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +LOGIN_OBJ = login.o +LOGIN_SQL_OBJ = $(LOGIN_OBJ:%=obj_sql/%) \ + obj_sql/account_sql.o obj_sql/ipban_sql.o obj_sql/loginlog_sql.o +LOGIN_H = login.h account.h ipban.h loginlog.h + +HAVE_MYSQL=@HAVE_MYSQL@ +ifeq ($(HAVE_MYSQL),yes) + LOGIN_SERVER_SQL_DEPENDS=obj_sql $(LOGIN_SQL_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) +else + LOGIN_SERVER_SQL_DEPENDS=needs_mysql +endif + +@SET_MAKE@ + +##################################################################### +.PHONY :all sql clean help + +all: sql + +sql: obj_sql login-server_sql + +clean: + @echo " CLEAN login" + @rm -rf *.o obj_sql ../../login-server@EXEEXT@ ../../login-server_sql@EXEEXT@ + +help: + @echo "possible targets are 'sql' 'all' 'clean' 'help'" + @echo "'sql' - login server (SQL version)" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +needs_mysql: + @echo "MySQL not found or disabled by the configure script" + @exit 1 + +# object directories + +obj_sql: + @echo " MKDIR obj_sql" + @-mkdir obj_sql + +#executables + +login-server_sql: $(LOGIN_SERVER_SQL_DEPENDS) + @echo " LD $@" + @@CC@ @LDFLAGS@ -o ../../login-server_sql@EXEEXT@ $(LOGIN_SQL_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) @LIBS@ @MYSQL_LIBS@ + + +# login object files + +obj_sql/%.o: %.c $(LOGIN_H) $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) -DWITH_SQL @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +# missing object files +../common/obj_all/common.a: + @$(MAKE) -C ../common sql + +../common/obj_sql/common_sql.a: + @$(MAKE) -C ../common sql + +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/login/account.h b/src/login/account.h new file mode 100644 index 000000000..1b567be70 --- /dev/null +++ b/src/login/account.h @@ -0,0 +1,156 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef __ACCOUNT_H_INCLUDED__ +#define __ACCOUNT_H_INCLUDED__ + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" // ACCOUNT_REG2_NUM + +typedef struct AccountDB AccountDB; +typedef struct AccountDBIterator AccountDBIterator; + + +// standard engines +AccountDB* account_db_sql(void); + +// extra engines (will probably use the other txt functions) +#define ACCOUNTDB_CONSTRUCTOR_(engine) account_db_##engine +#define ACCOUNTDB_CONSTRUCTOR(engine) ACCOUNTDB_CONSTRUCTOR_(engine) +#ifdef ACCOUNTDB_ENGINE_0 +AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_0)(void); +#endif +#ifdef ACCOUNTDB_ENGINE_1 +AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_1)(void); +#endif +#ifdef ACCOUNTDB_ENGINE_2 +AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_2)(void); +#endif +#ifdef ACCOUNTDB_ENGINE_3 +AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_3)(void); +#endif +#ifdef ACCOUNTDB_ENGINE_4 +AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_4)(void); +#endif + + +struct mmo_account +{ + int account_id; + char userid[NAME_LENGTH]; + char pass[32+1]; // 23+1 for plaintext, 32+1 for md5-ed passwords + char sex; // gender (M/F/S) + char email[40]; // e-mail (by default: a@a.com) + int group_id; // player group id + unsigned int state; // packet 0x006a value + 1 (0: compte OK) + time_t unban_time; // (timestamp): ban time limit of the account (0 = no ban) + time_t expiration_time; // (timestamp): validity limit of the account (0 = unlimited) + unsigned int logincount;// number of successful auth attempts + char lastlogin[24]; // date+time of last successful login + char last_ip[16]; // save of last IP of connection + char birthdate[10+1]; // assigned birth date (format: YYYY-MM-DD, default: 0000-00-00) + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; // account script variables (stored on login server) +}; + + +struct AccountDBIterator +{ + /// Destroys this iterator, releasing all allocated memory (including itself). + /// + /// @param self Iterator + void (*destroy)(AccountDBIterator* self); + + /// Fetches the next account in the database. + /// Fills acc with the account data. + /// @param self Iterator + /// @param acc Account data + /// @return true if successful + bool (*next)(AccountDBIterator* self, struct mmo_account* acc); +}; + + +struct AccountDB +{ + /// Initializes this database, making it ready for use. + /// Call this after setting the properties. + /// + /// @param self Database + /// @return true if successful + bool (*init)(AccountDB* self); + + /// Destroys this database, releasing all allocated memory (including itself). + /// + /// @param self Database + void (*destroy)(AccountDB* self); + + /// Gets a property from this database. + /// These read-only properties must be implemented: + /// "engine.name" -> "txt", "sql", ... + /// "engine.version" -> internal version + /// "engine.comment" -> anything (suggestion: description or specs of the engine) + /// + /// @param self Database + /// @param key Property name + /// @param buf Buffer for the value + /// @param buflen Buffer length + /// @return true if successful + bool (*get_property)(AccountDB* self, const char* key, char* buf, size_t buflen); + + /// Sets a property in this database. + /// + /// @param self Database + /// @param key Property name + /// @param value Property value + /// @return true if successful + bool (*set_property)(AccountDB* self, const char* key, const char* value); + + /// Creates a new account in this database. + /// If acc->account_id is not -1, the provided value will be used. + /// Otherwise the account_id will be auto-generated and written to acc->account_id. + /// + /// @param self Database + /// @param acc Account data + /// @return true if successful + bool (*create)(AccountDB* self, struct mmo_account* acc); + + /// Removes an account from this database. + /// + /// @param self Database + /// @param account_id Account id + /// @return true if successful + bool (*remove)(AccountDB* self, const int account_id); + + /// Modifies the data of an existing account. + /// Uses acc->account_id to identify the account. + /// + /// @param self Database + /// @param acc Account data + /// @return true if successful + bool (*save)(AccountDB* self, const struct mmo_account* acc); + + /// Finds an account with account_id and copies it to acc. + /// + /// @param self Database + /// @param acc Pointer that receives the account data + /// @param account_id Target account id + /// @return true if successful + bool (*load_num)(AccountDB* self, struct mmo_account* acc, const int account_id); + + /// Finds an account with userid and copies it to acc. + /// + /// @param self Database + /// @param acc Pointer that receives the account data + /// @param userid Target username + /// @return true if successful + bool (*load_str)(AccountDB* self, struct mmo_account* acc, const char* userid); + + /// Returns a new forward iterator. + /// + /// @param self Database + /// @return Iterator + AccountDBIterator* (*iterator)(AccountDB* self); +}; + + +#endif // __ACCOUNT_H_INCLUDED__ diff --git a/src/login/account_sql.c b/src/login/account_sql.c new file mode 100644 index 000000000..5073941e2 --- /dev/null +++ b/src/login/account_sql.c @@ -0,0 +1,680 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/malloc.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/sql.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "account.h" +#include <stdlib.h> +#include <string.h> + +/// global defines +#define ACCOUNT_SQL_DB_VERSION 20110114 + +/// internal structure +typedef struct AccountDB_SQL +{ + AccountDB vtable; // public interface + + Sql* accounts; // SQL accounts storage + + // global sql settings + char global_db_hostname[32]; + uint16 global_db_port; + char global_db_username[32]; + char global_db_password[32]; + char global_db_database[32]; + char global_codepage[32]; + // local sql settings + char db_hostname[32]; + uint16 db_port; + char db_username[32]; + char db_password[32]; + char db_database[32]; + char codepage[32]; + // other settings + bool case_sensitive; + char account_db[32]; + char accreg_db[32]; + +} AccountDB_SQL; + +/// internal structure +typedef struct AccountDBIterator_SQL +{ + AccountDBIterator vtable; // public interface + + AccountDB_SQL* db; + int last_account_id; +} AccountDBIterator_SQL; + +/// internal functions +static bool account_db_sql_init(AccountDB* self); +static void account_db_sql_destroy(AccountDB* self); +static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen); +static bool account_db_sql_set_property(AccountDB* self, const char* option, const char* value); +static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc); +static bool account_db_sql_remove(AccountDB* self, const int account_id); +static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc); +static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id); +static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid); +static AccountDBIterator* account_db_sql_iterator(AccountDB* self); +static void account_db_sql_iter_destroy(AccountDBIterator* self); +static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc); + +static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id); +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new); + +/// public constructor +AccountDB* account_db_sql(void) +{ + AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL)); + + // set up the vtable + db->vtable.init = &account_db_sql_init; + db->vtable.destroy = &account_db_sql_destroy; + db->vtable.get_property = &account_db_sql_get_property; + db->vtable.set_property = &account_db_sql_set_property; + db->vtable.save = &account_db_sql_save; + db->vtable.create = &account_db_sql_create; + db->vtable.remove = &account_db_sql_remove; + db->vtable.load_num = &account_db_sql_load_num; + db->vtable.load_str = &account_db_sql_load_str; + db->vtable.iterator = &account_db_sql_iterator; + + // initialize to default values + db->accounts = NULL; + // global sql settings + safestrncpy(db->global_db_hostname, "127.0.0.1", sizeof(db->global_db_hostname)); + db->global_db_port = 3306; + safestrncpy(db->global_db_username, "ragnarok", sizeof(db->global_db_username)); + safestrncpy(db->global_db_password, "ragnarok", sizeof(db->global_db_password)); + safestrncpy(db->global_db_database, "ragnarok", sizeof(db->global_db_database)); + safestrncpy(db->global_codepage, "", sizeof(db->global_codepage)); + // local sql settings + safestrncpy(db->db_hostname, "", sizeof(db->db_hostname)); + db->db_port = 3306; + safestrncpy(db->db_username, "", sizeof(db->db_username)); + safestrncpy(db->db_password, "", sizeof(db->db_password)); + safestrncpy(db->db_database, "", sizeof(db->db_database)); + safestrncpy(db->codepage, "", sizeof(db->codepage)); + // other settings + db->case_sensitive = false; + safestrncpy(db->account_db, "login", sizeof(db->account_db)); + safestrncpy(db->accreg_db, "global_reg_value", sizeof(db->accreg_db)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/// establishes database connection +static bool account_db_sql_init(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + Sql* sql_handle; + const char* username; + const char* password; + const char* hostname; + uint16 port; + const char* database; + const char* codepage; + + db->accounts = Sql_Malloc(); + sql_handle = db->accounts; + + if( db->db_hostname[0] != '\0' ) + {// local settings + username = db->db_username; + password = db->db_password; + hostname = db->db_hostname; + port = db->db_port; + database = db->db_database; + codepage = db->codepage; + } + else + {// global settings + username = db->global_db_username; + password = db->global_db_password; + hostname = db->global_db_hostname; + port = db->global_db_port; + database = db->global_db_database; + codepage = db->global_codepage; + } + + if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(db->accounts); + db->accounts = NULL; + return false; + } + + if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) + Sql_ShowDebug(sql_handle); + + return true; +} + +/// disconnects from database +static void account_db_sql_destroy(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + Sql_Free(db->accounts); + db->accounts = NULL; + aFree(db); +} + +/// Gets a property from this database. +static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + const char* signature; + + signature = "engine."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "name") == 0 ) + safesnprintf(buf, buflen, "sql"); + else + if( strcmpi(key, "version") == 0 ) + safesnprintf(buf, buflen, "%d", ACCOUNT_SQL_DB_VERSION); + else + if( strcmpi(key, "comment") == 0 ) + safesnprintf(buf, buflen, "SQL Account Database"); + else + return false;// not found + return true; + } + + signature = "sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_db_hostname); + else + if( strcmpi(key, "db_port") == 0 ) + safesnprintf(buf, buflen, "%d", db->global_db_port); + else + if( strcmpi(key, "db_username") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_db_username); + else + if( strcmpi(key, "db_password") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_db_password); + else + if( strcmpi(key, "db_database") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_db_database); + else + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_codepage); + else + return false;// not found + return true; + } + + signature = "account.sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname); + else + if( strcmpi(key, "db_port") == 0 ) + safesnprintf(buf, buflen, "%d", db->db_port); + else + if( strcmpi(key, "db_username") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username); + else + if( strcmpi(key, "db_password") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password); + else + if( strcmpi(key, "db_database") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database); + else + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + if( strcmpi(key, "account_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->account_db); + else + if( strcmpi(key, "accreg_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->accreg_db); + else + return false;// not found + return true; + } + + return false;// not found +} + +/// if the option is supported, adjusts the internal state +static bool account_db_sql_set_property(AccountDB* self, const char* key, const char* value) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + const char* signature; + + + signature = "sql."; + if( strncmp(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safestrncpy(db->global_db_hostname, value, sizeof(db->global_db_hostname)); + else + if( strcmpi(key, "db_port") == 0 ) + db->global_db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "db_username") == 0 ) + safestrncpy(db->global_db_username, value, sizeof(db->global_db_username)); + else + if( strcmpi(key, "db_password") == 0 ) + safestrncpy(db->global_db_password, value, sizeof(db->global_db_password)); + else + if( strcmpi(key, "db_database") == 0 ) + safestrncpy(db->global_db_database, value, sizeof(db->global_db_database)); + else + if( strcmpi(key, "codepage") == 0 ) + safestrncpy(db->global_codepage, value, sizeof(db->global_codepage)); + else + return false;// not found + return true; + } + + signature = "account.sql."; + if( strncmp(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safestrncpy(db->db_hostname, value, sizeof(db->db_hostname)); + else + if( strcmpi(key, "db_port") == 0 ) + db->db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "db_username") == 0 ) + safestrncpy(db->db_username, value, sizeof(db->db_username)); + else + if( strcmpi(key, "db_password") == 0 ) + safestrncpy(db->db_password, value, sizeof(db->db_password)); + else + if( strcmpi(key, "db_database") == 0 ) + safestrncpy(db->db_database, value, sizeof(db->db_database)); + else + if( strcmpi(key, "codepage") == 0 ) + safestrncpy(db->codepage, value, sizeof(db->codepage)); + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = config_switch(value); + else + if( strcmpi(key, "account_db") == 0 ) + safestrncpy(db->account_db, value, sizeof(db->account_db)); + else + if( strcmpi(key, "accreg_db") == 0 ) + safestrncpy(db->accreg_db, value, sizeof(db->accreg_db)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/// create a new account entry +/// If acc->account_id is -1, the account id will be auto-generated, +/// and its value will be written to acc->account_id if everything succeeds. +static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + Sql* sql_handle = db->accounts; + + // decide on the account id to assign + int account_id; + if( acc->account_id != -1 ) + {// caller specifies it manually + account_id = acc->account_id; + } + else + {// ask the database + char* data; + size_t len; + + if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT MAX(`account_id`)+1 FROM `%s`", db->account_db) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_ShowDebug(sql_handle); + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, &len); + account_id = ( data != NULL ) ? atoi(data) : 0; + Sql_FreeResult(sql_handle); + + if( account_id < START_ACCOUNT_NUM ) + account_id = START_ACCOUNT_NUM; + + } + + // zero value is prohibited + if( account_id == 0 ) + return false; + + // absolute maximum + if( account_id > END_ACCOUNT_NUM ) + return false; + + // insert the data into the database + acc->account_id = account_id; + return mmo_auth_tosql(db, acc, true); +} + +/// delete an existing account entry + its regs +static bool account_db_sql_remove(AccountDB* self, const int account_id) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + Sql* sql_handle = db->accounts; + bool result = false; + + if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") + || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->account_db, account_id) + || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->accreg_db, account_id) ) + Sql_ShowDebug(sql_handle); + else + result = true; + + result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); + + return result; +} + +/// update an existing account with the provided new data (both account and regs) +static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + return mmo_auth_tosql(db, acc, false); +} + +/// retrieve data from db and store it in the provided data structure +static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + return mmo_auth_fromsql(db, acc, account_id); +} + +/// retrieve data from db and store it in the provided data structure +static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + Sql* sql_handle = db->accounts; + char esc_userid[2*NAME_LENGTH+1]; + int account_id; + char* data; + + Sql_EscapeString(sql_handle, esc_userid, userid); + + // get the list of account IDs for this user ID + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'", + db->account_db, (db->case_sensitive ? "BINARY" : ""), esc_userid) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( Sql_NumRows(sql_handle) > 1 ) + {// serious problem - duplicit account + ShowError("account_db_sql_load_str: multiple accounts found when retrieving data for account '%s'!\n", userid); + Sql_FreeResult(sql_handle); + return false; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + {// no such entry + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, NULL); + account_id = atoi(data); + + return account_db_sql_load_num(self, acc, account_id); +} + + +/// Returns a new forward iterator. +static AccountDBIterator* account_db_sql_iterator(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)aCalloc(1, sizeof(AccountDBIterator_SQL)); + + // set up the vtable + iter->vtable.destroy = &account_db_sql_iter_destroy; + iter->vtable.next = &account_db_sql_iter_next; + + // fill data + iter->db = db; + iter->last_account_id = -1; + + return &iter->vtable; +} + + +/// Destroys this iterator, releasing all allocated memory (including itself). +static void account_db_sql_iter_destroy(AccountDBIterator* self) +{ + AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; + aFree(iter); +} + + +/// Fetches the next account in the database. +static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc) +{ + AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; + AccountDB_SQL* db = (AccountDB_SQL*)iter->db; + Sql* sql_handle = db->accounts; + int account_id; + char* data; + + // get next account ID + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `account_id` > '%d' ORDER BY `account_id` ASC LIMIT 1", + db->account_db, iter->last_account_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) && + SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, NULL) && + data != NULL ) + {// get account data + account_id = atoi(data); + if( mmo_auth_fromsql(db, acc, account_id) ) + { + iter->last_account_id = account_id; + Sql_FreeResult(sql_handle); + return true; + } + } + Sql_FreeResult(sql_handle); + return false; +} + + +static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id) +{ + Sql* sql_handle = db->accounts; + char* data; + int i = 0; + + // retrieve login entry for the specified account + if( SQL_ERROR == Sql_Query(sql_handle, + "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`group_id`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip`,`birthdate` FROM `%s` WHERE `account_id` = %d", + db->account_db, account_id ) + ) { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) + {// no such entry + Sql_FreeResult(sql_handle); + return false; + } + + Sql_GetData(sql_handle, 0, &data, NULL); acc->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(acc->userid, data, sizeof(acc->userid)); + Sql_GetData(sql_handle, 2, &data, NULL); safestrncpy(acc->pass, data, sizeof(acc->pass)); + Sql_GetData(sql_handle, 3, &data, NULL); acc->sex = data[0]; + Sql_GetData(sql_handle, 4, &data, NULL); safestrncpy(acc->email, data, sizeof(acc->email)); + Sql_GetData(sql_handle, 5, &data, NULL); acc->group_id = atoi(data); + Sql_GetData(sql_handle, 6, &data, NULL); acc->state = strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 7, &data, NULL); acc->unban_time = atol(data); + Sql_GetData(sql_handle, 8, &data, NULL); acc->expiration_time = atol(data); + Sql_GetData(sql_handle, 9, &data, NULL); acc->logincount = strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 10, &data, NULL); safestrncpy(acc->lastlogin, data, sizeof(acc->lastlogin)); + Sql_GetData(sql_handle, 11, &data, NULL); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip)); + Sql_GetData(sql_handle, 12, &data, NULL); safestrncpy(acc->birthdate, data, sizeof(acc->birthdate)); + + Sql_FreeResult(sql_handle); + + + // retrieve account regs for the specified user + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`,`value` FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + acc->account_reg2_num = (int)Sql_NumRows(sql_handle); + + while( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + Sql_GetData(sql_handle, 0, &data, NULL); safestrncpy(acc->account_reg2[i].str, data, sizeof(acc->account_reg2[i].str)); + Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(acc->account_reg2[i].value, data, sizeof(acc->account_reg2[i].value)); + ++i; + } + Sql_FreeResult(sql_handle); + + if( i != acc->account_reg2_num ) + return false; + + return true; +} + +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new) +{ + Sql* sql_handle = db->accounts; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + int i; + + // try + do + { + + if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") ) + { + Sql_ShowDebug(sql_handle); + break; + } + + if( is_new ) + {// insert into account table + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, + "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `group_id`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`, `birthdate`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + db->account_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)&acc->account_id, sizeof(acc->account_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_STRING, (void*)&acc->email, strlen(acc->email)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_INT, (void*)&acc->group_id, sizeof(acc->group_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_UINT, (void*)&acc->state, sizeof(acc->state)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_INT, (void*)&acc->expiration_time, sizeof(acc->expiration_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_UINT, (void*)&acc->logincount, sizeof(acc->logincount)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_STRING, (void*)&acc->lastlogin, strlen(acc->lastlogin)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_STRING, (void*)&acc->birthdate, strlen(acc->birthdate)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + {// update account table + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=? WHERE `account_id` = '%d'", db->account_db, acc->account_id) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void*)acc->email, strlen(acc->email)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT, (void*)&acc->group_id, sizeof(acc->group_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_UINT, (void*)&acc->state, sizeof(acc->state)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_LONG, (void*)&acc->expiration_time, sizeof(acc->expiration_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_UINT, (void*)&acc->logincount, sizeof(acc->logincount)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_STRING, (void*)&acc->lastlogin, strlen(acc->lastlogin)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_STRING, (void*)&acc->birthdate, strlen(acc->birthdate)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + + // remove old account regs + if( SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) ) + { + Sql_ShowDebug(sql_handle); + break; + } + // insert new account regs + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "INSERT INTO `%s` (`type`, `account_id`, `str`, `value`) VALUES ( 1 , '%d' , ? , ? );", db->accreg_db, acc->account_id) ) + { + SqlStmt_ShowDebug(stmt); + break; + } + for( i = 0; i < acc->account_reg2_num; ++i ) + { + if( SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)acc->account_reg2[i].str, strlen(acc->account_reg2[i].str)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->account_reg2[i].value, strlen(acc->account_reg2[i].value)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + if( i < acc->account_reg2_num ) + { + result = false; + break; + } + + // if we got this far, everything was successful + result = true; + + } while(0); + // finally + + result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); + SqlStmt_Free(stmt); + + return result; +} diff --git a/src/login/ipban.h b/src/login/ipban.h new file mode 100644 index 000000000..b2a1a7d9e --- /dev/null +++ b/src/login/ipban.h @@ -0,0 +1,25 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef __IPBAN_H_INCLUDED__ +#define __IPBAN_H_INCLUDED__ + +#include "../common/cbasetypes.h" + +// initialize +void ipban_init(void); + +// finalize +void ipban_final(void); + +// check ip against ban list +bool ipban_check(uint32 ip); + +// increases failure count for the specified IP +void ipban_log(uint32 ip); + +// parses configuration option +bool ipban_config_read(const char* key, const char* value); + + +#endif // __IPBAN_H_INCLUDED__ diff --git a/src/login/ipban_sql.c b/src/login/ipban_sql.c new file mode 100644 index 000000000..c75a1f956 --- /dev/null +++ b/src/login/ipban_sql.c @@ -0,0 +1,258 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/sql.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "login.h" +#include "ipban.h" +#include "loginlog.h" +#include <stdlib.h> +#include <string.h> + +// global sql settings +static char global_db_hostname[32] = "127.0.0.1"; +static uint16 global_db_port = 3306; +static char global_db_username[32] = "ragnarok"; +static char global_db_password[32] = "ragnarok"; +static char global_db_database[32] = "ragnarok"; +static char global_codepage[32] = ""; +// local sql settings +static char ipban_db_hostname[32] = ""; +static uint16 ipban_db_port = 0; +static char ipban_db_username[32] = ""; +static char ipban_db_password[32] = ""; +static char ipban_db_database[32] = ""; +static char ipban_codepage[32] = ""; +static char ipban_table[32] = "ipbanlist"; + +// globals +static Sql* sql_handle = NULL; +static int cleanup_timer_id = INVALID_TIMER; +static bool ipban_inited = false; + +int ipban_cleanup(int tid, unsigned int tick, int id, intptr_t data); + + +// initialize +void ipban_init(void) +{ + const char* username; + const char* password; + const char* hostname; + uint16 port; + const char* database; + const char* codepage; + + ipban_inited = true; + + if( !login_config.ipban ) + return;// ipban disabled + + if( ipban_db_hostname[0] != '\0' ) + {// local settings + username = ipban_db_username; + password = ipban_db_password; + hostname = ipban_db_hostname; + port = ipban_db_port; + database = ipban_db_database; + codepage = ipban_codepage; + } + else + {// global settings + username = global_db_username; + password = global_db_password; + hostname = global_db_hostname; + port = global_db_port; + database = global_db_database; + codepage = global_codepage; + } + + // establish connections + sql_handle = Sql_Malloc(); + if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(sql_handle); + exit(EXIT_FAILURE); + } + if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) + Sql_ShowDebug(sql_handle); + + if( login_config.ipban_cleanup_interval > 0 ) + { // set up periodic cleanup of connection history and active bans + add_timer_func_list(ipban_cleanup, "ipban_cleanup"); + cleanup_timer_id = add_timer_interval(gettick()+10, ipban_cleanup, 0, 0, login_config.ipban_cleanup_interval*1000); + } else // make sure it gets cleaned up on login-server start regardless of interval-based cleanups + ipban_cleanup(0,0,0,0); +} + +// finalize +void ipban_final(void) +{ + if( !login_config.ipban ) + return;// ipban disabled + + if( login_config.ipban_cleanup_interval > 0 ) + // release data + delete_timer(cleanup_timer_id, ipban_cleanup); + + ipban_cleanup(0,0,0,0); // always clean up on login-server stop + + // close connections + Sql_Free(sql_handle); + sql_handle = NULL; +} + +// load configuration options +bool ipban_config_read(const char* key, const char* value) +{ + const char* signature; + + if( ipban_inited ) + return false;// settings can only be changed before init + + signature = "sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safestrncpy(global_db_hostname, value, sizeof(global_db_hostname)); + else + if( strcmpi(key, "db_port") == 0 ) + global_db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "db_username") == 0 ) + safestrncpy(global_db_username, value, sizeof(global_db_username)); + else + if( strcmpi(key, "db_password") == 0 ) + safestrncpy(global_db_password, value, sizeof(global_db_password)); + else + if( strcmpi(key, "db_database") == 0 ) + safestrncpy(global_db_database, value, sizeof(global_db_database)); + else + if( strcmpi(key, "codepage") == 0 ) + safestrncpy(global_codepage, value, sizeof(global_codepage)); + else + return false;// not found + return true; + } + + signature = "ipban.sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safestrncpy(ipban_db_hostname, value, sizeof(ipban_db_hostname)); + else + if( strcmpi(key, "db_port") == 0 ) + ipban_db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "db_username") == 0 ) + safestrncpy(ipban_db_username, value, sizeof(ipban_db_username)); + else + if( strcmpi(key, "db_password") == 0 ) + safestrncpy(ipban_db_password, value, sizeof(ipban_db_password)); + else + if( strcmpi(key, "db_database") == 0 ) + safestrncpy(ipban_db_database, value, sizeof(ipban_db_database)); + else + if( strcmpi(key, "codepage") == 0 ) + safestrncpy(ipban_codepage, value, sizeof(ipban_codepage)); + else + if( strcmpi(key, "ipban_table") == 0 ) + safestrncpy(ipban_table, value, sizeof(ipban_table)); + else + return false;// not found + return true; + } + + signature = "ipban."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "enable") == 0 ) + login_config.ipban = (bool)config_switch(value); + else + if( strcmpi(key, "dynamic_pass_failure_ban") == 0 ) + login_config.dynamic_pass_failure_ban = (bool)config_switch(value); + else + if( strcmpi(key, "dynamic_pass_failure_ban_interval") == 0 ) + login_config.dynamic_pass_failure_ban_interval = atoi(value); + else + if( strcmpi(key, "dynamic_pass_failure_ban_limit") == 0 ) + login_config.dynamic_pass_failure_ban_limit = atoi(value); + else + if( strcmpi(key, "dynamic_pass_failure_ban_duration") == 0 ) + login_config.dynamic_pass_failure_ban_duration = atoi(value); + else + return false;// not found + return true; + } + + return false;// not found +} + +// check ip against active bans list +bool ipban_check(uint32 ip) +{ + uint8* p = (uint8*)&ip; + char* data = NULL; + int matches; + + if( !login_config.ipban ) + return false;// ipban disabled + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT count(*) FROM `%s` WHERE `rtime` > NOW() AND (`list` = '%u.*.*.*' OR `list` = '%u.%u.*.*' OR `list` = '%u.%u.%u.*' OR `list` = '%u.%u.%u.%u')", + ipban_table, p[3], p[3], p[2], p[3], p[2], p[1], p[3], p[2], p[1], p[0]) ) + { + Sql_ShowDebug(sql_handle); + // close connection because we can't verify their connectivity. + return true; + } + + if( SQL_ERROR == Sql_NextRow(sql_handle) ) + return true;// Shouldn't happen, but just in case... + + Sql_GetData(sql_handle, 0, &data, NULL); + matches = atoi(data); + Sql_FreeResult(sql_handle); + + return( matches > 0 ); +} + +// log failed attempt +void ipban_log(uint32 ip) +{ + unsigned long failures; + + if( !login_config.ipban ) + return;// ipban disabled + + failures = loginlog_failedattempts(ip, login_config.dynamic_pass_failure_ban_interval);// how many times failed account? in one ip. + + // if over the limit, add a temporary ban entry + if( failures >= login_config.dynamic_pass_failure_ban_limit ) + { + uint8* p = (uint8*)&ip; + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`list`,`btime`,`rtime`,`reason`) VALUES ('%u.%u.%u.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban')", + ipban_table, p[3], p[2], p[1], login_config.dynamic_pass_failure_ban_duration) ) + Sql_ShowDebug(sql_handle); + } +} + +// remove expired bans +int ipban_cleanup(int tid, unsigned int tick, int id, intptr_t data) +{ + if( !login_config.ipban ) + return 0;// ipban disabled + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()") ) + Sql_ShowDebug(sql_handle); + + return 0; +} diff --git a/src/login/login.c b/src/login/login.c new file mode 100644 index 000000000..e079dbaf2 --- /dev/null +++ b/src/login/login.c @@ -0,0 +1,1883 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/core.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/md5calc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "account.h" +#include "ipban.h" +#include "login.h" +#include "loginlog.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct Login_Config login_config; + +int login_fd; // login server socket +struct mmo_char_server server[MAX_SERVERS]; // char server data + +// Account engines available +static struct{ + AccountDB* (*constructor)(void); + AccountDB* db; +} account_engines[] = { + {account_db_sql, NULL}, +#ifdef ACCOUNTDB_ENGINE_0 + {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_0), NULL}, +#endif +#ifdef ACCOUNTDB_ENGINE_1 + {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_1), NULL}, +#endif +#ifdef ACCOUNTDB_ENGINE_2 + {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_2), NULL}, +#endif +#ifdef ACCOUNTDB_ENGINE_3 + {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_3), NULL}, +#endif +#ifdef ACCOUNTDB_ENGINE_4 + {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_4), NULL}, +#endif + // end of structure + {NULL, NULL} +}; +// account database +AccountDB* accounts = NULL; + +//Account registration flood protection [Kevin] +int allowed_regs = 1; +int time_allowed = 10; //in seconds + +// Advanced subnet check [LuzZza] +struct s_subnet { + uint32 mask; + uint32 char_ip; + uint32 map_ip; +} subnet[16]; + +int subnet_count = 0; + +int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip); + +//----------------------------------------------------- +// Auth database +//----------------------------------------------------- +#define AUTH_TIMEOUT 30000 + +struct auth_node { + + int account_id; + uint32 login_id1; + uint32 login_id2; + uint32 ip; + char sex; + uint32 version; + uint8 clienttype; +}; + +static DBMap* auth_db; // int account_id -> struct auth_node* + + +//----------------------------------------------------- +// Online User Database [Wizputer] +//----------------------------------------------------- +struct online_login_data { + + int account_id; + int waiting_disconnect; + int char_server; +}; + +static DBMap* online_db; // int account_id -> struct online_login_data* +static int waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data); + +/** + * @see DBCreateData + */ +static DBData create_online_user(DBKey key, va_list args) +{ + struct online_login_data* p; + CREATE(p, struct online_login_data, 1); + p->account_id = key.i; + p->char_server = -1; + p->waiting_disconnect = INVALID_TIMER; + return db_ptr2data(p); +} + +struct online_login_data* add_online_user(int char_server, int account_id) +{ + struct online_login_data* p; + p = idb_ensure(online_db, account_id, create_online_user); + p->char_server = char_server; + if( p->waiting_disconnect != INVALID_TIMER ) + { + delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + p->waiting_disconnect = INVALID_TIMER; + } + return p; +} + +void remove_online_user(int account_id) +{ + struct online_login_data* p; + p = (struct online_login_data*)idb_get(online_db, account_id); + if( p == NULL ) + return; + if( p->waiting_disconnect != INVALID_TIMER ) + delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + + idb_remove(online_db, account_id); +} + +static int waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct online_login_data* p = (struct online_login_data*)idb_get(online_db, id); + if( p != NULL && p->waiting_disconnect == tid && p->account_id == id ) + { + p->waiting_disconnect = INVALID_TIMER; + remove_online_user(id); + idb_remove(auth_db, id); + } + return 0; +} + +/** + * @see DBApply + */ +static int online_db_setoffline(DBKey key, DBData *data, va_list ap) +{ + struct online_login_data* p = db_data2ptr(data); + int server = va_arg(ap, int); + if( server == -1 ) + { + p->char_server = -1; + if( p->waiting_disconnect != INVALID_TIMER ) + { + delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + p->waiting_disconnect = INVALID_TIMER; + } + } + else if( p->char_server == server ) + p->char_server = -2; //Char server disconnected. + return 0; +} + +/** + * @see DBApply + */ +static int online_data_cleanup_sub(DBKey key, DBData *data, va_list ap) +{ + struct online_login_data *character= db_data2ptr(data); + if (character->char_server == -2) //Unknown server.. set them offline + remove_online_user(character->account_id); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, intptr_t data) +{ + online_db->foreach(online_db, online_data_cleanup_sub); + return 0; +} + + +//-------------------------------------------------------------------- +// Packet send to all char-servers, except one (wos: without our self) +//-------------------------------------------------------------------- +int charif_sendallwos(int sfd, uint8* buf, size_t len) +{ + int i, c; + + for( i = 0, c = 0; i < ARRAYLENGTH(server); ++i ) + { + int fd = server[i].fd; + if( session_isValid(fd) && fd != sfd ) + { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + ++c; + } + } + + return c; +} + + +/// Initializes a server structure. +void chrif_server_init(int id) +{ + memset(&server[id], 0, sizeof(server[id])); + server[id].fd = -1; +} + + +/// Destroys a server structure. +void chrif_server_destroy(int id) +{ + if( server[id].fd != -1 ) + { + do_close(server[id].fd); + server[id].fd = -1; + } +} + + +/// Resets all the data related to a server. +void chrif_server_reset(int id) +{ + online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char server to offline. + chrif_server_destroy(id); + chrif_server_init(id); +} + + +/// Called when the connection to Char Server is disconnected. +void chrif_on_disconnect(int id) +{ + ShowStatus("Char-server '%s' has disconnected.\n", server[id].name); + chrif_server_reset(id); +} + + +//----------------------------------------------------- +// periodic ip address synchronization +//----------------------------------------------------- +static int sync_ip_addresses(int tid, unsigned int tick, int id, intptr_t data) +{ + uint8 buf[2]; + ShowInfo("IP Sync in progress...\n"); + WBUFW(buf,0) = 0x2735; + charif_sendallwos(-1, buf, 2); + return 0; +} + + +//----------------------------------------------------- +// encrypted/unencrypted password check (from eApp) +//----------------------------------------------------- +bool check_encrypted(const char* str1, const char* str2, const char* passwd) +{ + char tmpstr[64+1], md5str[32+1]; + + safesnprintf(tmpstr, sizeof(tmpstr), "%s%s", str1, str2); + MD5_String(tmpstr, md5str); + + return (0==strcmp(passwd, md5str)); +} + +bool check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass) +{ + if(passwdenc == 0) + { + return (0==strcmp(passwd, refpass)); + } + else + { + // password mode set to 1 -> md5(md5key, refpass) enable with <passwordencrypt></passwordencrypt> + // password mode set to 2 -> md5(refpass, md5key) enable with <passwordencrypt2></passwordencrypt2> + + return ((passwdenc&0x01) && check_encrypted(md5key, refpass, passwd)) || + ((passwdenc&0x02) && check_encrypted(refpass, md5key, passwd)); + } +} + + +//----------------------------------------------------- +// custom timestamp formatting (from eApp) +//----------------------------------------------------- +const char* timestamp2string(char* str, size_t size, time_t timestamp, const char* format) +{ + size_t len = strftime(str, size, format, localtime(×tamp)); + memset(str + len, '\0', size - len); + return str; +} + + +//-------------------------------------------- +// 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) ); + return ( i < subnet_count ) ? subnet[i].char_ip : 0; +} + +//---------------------------------- +// Reading Lan Support configuration +//---------------------------------- +int login_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; + } + + 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; + } + + 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++; + } + } + + if( subnet_count > 1 ) /* only useful if there is more than 1 available */ + ShowStatus("Read information about %d subnetworks.\n", subnet_count); + + fclose(fp); + return 0; +} + +//----------------------- +// Console Command Parser [Wizputer] +//----------------------- +int parse_console(const char* command) +{ + ShowNotice("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("To shutdown the server:\n"); + ShowInfo(" 'shutdown|exit|quit|end'\n"); + ShowInfo("To know if server is alive:\n"); + ShowInfo(" 'alive|status'\n"); + ShowInfo("To create a new account:\n"); + ShowInfo(" 'create'\n"); + } + else + {// commands with parameters + char cmd[128], params[256]; + + if( sscanf(command, "%127s %255[^\r\n]", cmd, params) < 2 ) + { + return 0; + } + + if( strcmpi(cmd, "create") == 0 ) + { + char username[NAME_LENGTH], password[NAME_LENGTH], sex; + + if( sscanf(params, "%23s %23s %c", username, password, &sex) < 3 || strnlen(username, sizeof(username)) < 4 || strnlen(password, sizeof(password)) < 1 ) + { + ShowWarning("Console: Invalid parameters for '%s'. Usage: %s <username> <password> <sex:F/M>\n", cmd, cmd); + return 0; + } + + if( mmo_auth_new(username, password, TOUPPER(sex), "0.0.0.0") != -1 ) + { + ShowError("Console: Account creation failed.\n"); + return 0; + } + ShowStatus("Console: Account '%s' created successfully.\n", username); + } + } + + return 0; +} + + +//-------------------------------- +// Packet parsing for char-servers +//-------------------------------- +int parse_fromchar(int fd) +{ + int j, id; + uint32 ipl; + char ip[16]; + + ARR_FIND( 0, ARRAYLENGTH(server), id, server[id].fd == fd ); + if( id == ARRAYLENGTH(server) ) + {// not a char server + ShowDebug("parse_fromchar: Disconnecting invalid session #%d (is not a char-server)\n", fd); + set_eof(fd); + do_close(fd); + return 0; + } + + if( session[fd]->flag.eof ) + { + do_close(fd); + server[id].fd = -1; + chrif_on_disconnect(id); + return 0; + } + + ipl = server[id].ip; + ip2str(ipl, ip); + + while( RFIFOREST(fd) >= 2 ) + { + uint16 command = RFIFOW(fd,0); + + switch( command ) + { + + case 0x2712: // request from char-server to authenticate an account + if( RFIFOREST(fd) < 23 ) + return 0; + { + struct auth_node* node; + + int account_id = RFIFOL(fd,2); + uint32 login_id1 = RFIFOL(fd,6); + uint32 login_id2 = RFIFOL(fd,10); + uint8 sex = RFIFOB(fd,14); + //uint32 ip_ = ntohl(RFIFOL(fd,15)); + int request_id = RFIFOL(fd,19); + RFIFOSKIP(fd,23); + + node = (struct auth_node*)idb_get(auth_db, account_id); + if( runflag == LOGINSERVER_ST_RUNNING && + node != NULL && + node->account_id == account_id && + node->login_id1 == login_id1 && + node->login_id2 == login_id2 && + node->sex == sex_num2str(sex) /*&& + node->ip == ip_*/ ) + {// found + //ShowStatus("Char-server '%s': authentication of the account %d accepted (ip: %s).\n", server[id].name, account_id, ip); + + // send ack + WFIFOHEAD(fd,25); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = login_id1; + WFIFOL(fd,10) = login_id2; + WFIFOB(fd,14) = sex; + WFIFOB(fd,15) = 0;// ok + WFIFOL(fd,16) = request_id; + WFIFOL(fd,20) = node->version; + WFIFOB(fd,24) = node->clienttype; + WFIFOSET(fd,25); + + // each auth entry can only be used once + idb_remove(auth_db, account_id); + } + else + {// authentication not found + ShowStatus("Char-server '%s': authentication of the account %d REFUSED (ip: %s).\n", server[id].name, account_id, ip); + WFIFOHEAD(fd,25); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = login_id1; + WFIFOL(fd,10) = login_id2; + WFIFOB(fd,14) = sex; + WFIFOB(fd,15) = 1;// auth failed + WFIFOL(fd,16) = request_id; + WFIFOL(fd,20) = 0; + WFIFOB(fd,24) = 0; + WFIFOSET(fd,25); + } + } + break; + + case 0x2714: + if( RFIFOREST(fd) < 6 ) + return 0; + { + int users = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + // how many users on world? (update) + if( server[id].users != users ) + { + ShowStatus("set users %s : %d\n", server[id].name, users); + + server[id].users = users; + } + } + break; + + case 0x2715: // request from char server to change e-email from default "a@a.com" + if (RFIFOREST(fd) < 46) + return 0; + { + struct mmo_account acc; + char email[40]; + + int account_id = RFIFOL(fd,2); + safestrncpy(email, (char*)RFIFOP(fd,6), 40); remove_control_chars(email); + RFIFOSKIP(fd,46); + + if( e_mail_check(email) == 0 ) + ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)\n", server[id].name, account_id, ip); + else + if( !accounts->load_num(accounts, &acc, account_id) || strcmp(acc.email, "a@a.com") == 0 || acc.email[0] == '\0' ) + ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s).\n", server[id].name, account_id, ip); + else { + memcpy(acc.email, email, 40); + ShowNotice("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s).\n", server[id].name, account_id, email, ip); + // Save + accounts->save(accounts, &acc); + } + } + break; + + case 0x2716: // request account data + if( RFIFOREST(fd) < 6 ) + return 0; + { + struct mmo_account acc; + time_t expiration_time = 0; + char email[40] = ""; + int group_id = 0; + char birthdate[10+1] = ""; + + int account_id = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': account %d NOT found (ip: %s).\n", server[id].name, account_id, ip); + else + { + safestrncpy(email, acc.email, sizeof(email)); + expiration_time = acc.expiration_time; + group_id = acc.group_id; + safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); + } + + WFIFOHEAD(fd,62); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = account_id; + safestrncpy((char*)WFIFOP(fd,6), email, 40); + WFIFOL(fd,46) = (uint32)expiration_time; + WFIFOB(fd,50) = group_id; + safestrncpy((char*)WFIFOP(fd,51), birthdate, 10+1); + WFIFOSET(fd,62); + } + break; + + case 0x2719: // ping request from charserver + RFIFOSKIP(fd,2); + + WFIFOHEAD(fd,2); + WFIFOW(fd,0) = 0x2718; + WFIFOSET(fd,2); + break; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + if (RFIFOREST(fd) < 86) + return 0; + { + struct mmo_account acc; + char actual_email[40]; + char new_email[40]; + + int account_id = RFIFOL(fd,2); + safestrncpy(actual_email, (char*)RFIFOP(fd,6), 40); + safestrncpy(new_email, (char*)RFIFOP(fd,46), 40); + RFIFOSKIP(fd, 86); + + if( e_mail_check(actual_email) == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)\n", server[id].name, account_id, ip); + else + if( e_mail_check(new_email) == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)\n", server[id].name, account_id, ip); + else + if( strcmpi(new_email, "a@a.com") == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)\n", server[id].name, account_id, ip); + else + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s).\n", server[id].name, account_id, ip); + else + if( strcmpi(acc.email, actual_email) != 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s).\n", server[id].name, account_id, acc.userid, acc.email, actual_email, ip); + else { + safestrncpy(acc.email, new_email, 40); + ShowNotice("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s).\n", server[id].name, account_id, acc.userid, new_email, ip); + // Save + accounts->save(accounts, &acc); + } + } + break; + + case 0x2724: // Receiving an account state update request from a map-server (relayed via char-server) + if (RFIFOREST(fd) < 10) + return 0; + { + struct mmo_account acc; + + int account_id = RFIFOL(fd,2); + unsigned int state = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s).\n", server[id].name, account_id, state, ip); + else + if( acc.state == state ) + ShowNotice("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s).\n", server[id].name, account_id, state, ip); + else { + ShowNotice("Char-server '%s': Status change (account: %d, new status %d, ip: %s).\n", server[id].name, account_id, state, ip); + + acc.state = state; + // Save + accounts->save(accounts, &acc); + + // notify other servers + if (state != 0) { + uint8 buf[11]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = 0; // 0: change of state, 1: ban + WBUFL(buf,7) = state; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + } + } + break; + + case 0x2725: // Receiving of map-server via char-server a ban request + if (RFIFOREST(fd) < 18) + return 0; + { + struct mmo_account acc; + + int account_id = RFIFOL(fd,2); + int year = (short)RFIFOW(fd,6); + int month = (short)RFIFOW(fd,8); + int mday = (short)RFIFOW(fd,10); + int hour = (short)RFIFOW(fd,12); + int min = (short)RFIFOW(fd,14); + int sec = (short)RFIFOW(fd,16); + RFIFOSKIP(fd,18); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of ban request (account: %d not found, ip: %s).\n", server[id].name, account_id, ip); + else + { + time_t timestamp; + struct tm *tmtime; + if (acc.unban_time == 0 || acc.unban_time < time(NULL)) + timestamp = time(NULL); // new ban + else + timestamp = acc.unban_time; // add to existing ban + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + year; + tmtime->tm_mon = tmtime->tm_mon + month; + tmtime->tm_mday = tmtime->tm_mday + mday; + tmtime->tm_hour = tmtime->tm_hour + hour; + tmtime->tm_min = tmtime->tm_min + min; + tmtime->tm_sec = tmtime->tm_sec + sec; + timestamp = mktime(tmtime); + if (timestamp == -1) + ShowNotice("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s).\n", server[id].name, account_id, ip); + else + if( timestamp <= time(NULL) || timestamp == 0 ) + ShowNotice("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s).\n", server[id].name, account_id, ip); + else + { + uint8 buf[11]; + char tmpstr[24]; + timestamp2string(tmpstr, sizeof(tmpstr), timestamp, login_config.date_format); + ShowNotice("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s).\n", server[id].name, account_id, timestamp, tmpstr, ip); + + acc.unban_time = timestamp; + + // Save + accounts->save(accounts, &acc); + + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = 1; // 0: change of status, 1: ban + WBUFL(buf,7) = (uint32)timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + } + } + break; + + case 0x2727: // Change of sex (sex is reversed) + if( RFIFOREST(fd) < 6 ) + return 0; + { + struct mmo_account acc; + + int account_id = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of sex change (account: %d not found, ip: %s).\n", server[id].name, account_id, ip); + else + if( acc.sex == 'S' ) + ShowNotice("Char-server '%s': Error of sex change - account to change is a Server account (account: %d, ip: %s).\n", server[id].name, account_id, ip); + else + { + unsigned char buf[7]; + char sex = ( acc.sex == 'M' ) ? 'F' : 'M'; //Change gender + + ShowNotice("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s).\n", server[id].name, account_id, sex, ip); + + acc.sex = sex; + // Save + accounts->save(accounts, &acc); + + // announce to other servers + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = sex_str2num(sex); + charif_sendallwos(-1, buf, 7); + } + } + break; + + case 0x2728: // We receive account_reg2 from a char-server, and we send them to other map-servers. + if( RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2) ) + return 0; + { + struct mmo_account acc; + + int account_id = RFIFOL(fd,4); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowStatus("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s).\n", server[id].name, account_id, ip); + else + { + int len; + int p; + ShowNotice("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s).\n", server[id].name, account_id, ip); + for( j = 0, p = 13; j < ACCOUNT_REG2_NUM && p < RFIFOW(fd,2); ++j ) + { + sscanf((char*)RFIFOP(fd,p), "%31c%n", acc.account_reg2[j].str, &len); + acc.account_reg2[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf((char*)RFIFOP(fd,p), "%255c%n", acc.account_reg2[j].value, &len); + acc.account_reg2[j].value[len]='\0'; + p +=len+1; + remove_control_chars(acc.account_reg2[j].str); + remove_control_chars(acc.account_reg2[j].value); + } + acc.account_reg2_num = j; + + // Save + accounts->save(accounts, &acc); + + // Sending information towards the other char-servers. + RFIFOW(fd,0) = 0x2729;// reusing read buffer + charif_sendallwos(fd, RFIFOP(fd,0), RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + } + break; + + case 0x272a: // Receiving of map-server via char-server an unban request + if( RFIFOREST(fd) < 6 ) + return 0; + { + struct mmo_account acc; + + int account_id = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s).\n", server[id].name, account_id, ip); + else + if( acc.unban_time == 0 ) + ShowNotice("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s).\n", server[id].name, account_id, ip); + else + { + ShowNotice("Char-server '%s': UnBan request (account: %d, ip: %s).\n", server[id].name, account_id, ip); + acc.unban_time = 0; + accounts->save(accounts, &acc); + } + } + break; + + case 0x272b: // Set account_id to online [Wizputer] + if( RFIFOREST(fd) < 6 ) + return 0; + add_online_user(id, RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + break; + + case 0x272c: // Set account_id to offline [Wizputer] + if( RFIFOREST(fd) < 6 ) + return 0; + remove_online_user(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + break; + + case 0x272d: // Receive list of all online accounts. [Skotlex] + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct online_login_data *p; + int aid; + uint32 i, users; + online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char-server offline first + users = RFIFOW(fd,4); + for (i = 0; i < users; i++) { + aid = RFIFOL(fd,6+i*4); + p = idb_ensure(online_db, aid, create_online_user); + p->char_server = id; + if (p->waiting_disconnect != INVALID_TIMER) + { + delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + p->waiting_disconnect = INVALID_TIMER; + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x272e: //Request account_reg2 for a character. + if (RFIFOREST(fd) < 10) + return 0; + { + struct mmo_account acc; + size_t off; + + int account_id = RFIFOL(fd,2); + int char_id = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + + WFIFOHEAD(fd,ACCOUNT_REG2_NUM*sizeof(struct global_reg)); + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 1; //Type 1 for Account2 registry + + off = 13; + if( accounts->load_num(accounts, &acc, account_id) ) + { + for( j = 0; j < acc.account_reg2_num; j++ ) + { + if( acc.account_reg2[j].str[0] != '\0' ) + { + off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].str)+1; //We add 1 to consider the '\0' in place. + off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].value)+1; + } + } + } + + WFIFOW(fd,2) = (uint16)off; + WFIFOSET(fd,WFIFOW(fd,2)); + } + break; + + case 0x2736: // WAN IP update from char-server + if( RFIFOREST(fd) < 6 ) + return 0; + server[id].ip = ntohl(RFIFOL(fd,2)); + ShowInfo("Updated IP of Server #%d to %d.%d.%d.%d.\n",id, CONVIP(server[id].ip)); + RFIFOSKIP(fd,6); + break; + + case 0x2737: //Request to set all offline. + ShowInfo("Setting accounts from char-server %d offline.\n", id); + online_db->foreach(online_db, online_db_setoffline, id); + RFIFOSKIP(fd,2); + break; + + default: + ShowError("parse_fromchar: Unknown packet 0x%x from a char-server! Disconnecting!\n", command); + set_eof(fd); + return 0; + } // switch + } // while + + return 0; +} + + +//------------------------------------- +// Make new account +//------------------------------------- +int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip) { + static int num_regs = 0; // registration counter + static unsigned int new_reg_tick = 0; + unsigned int tick = gettick(); + struct mmo_account acc; + + //Account Registration Flood Protection by [Kevin] + if( new_reg_tick == 0 ) + new_reg_tick = gettick(); + if( DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= allowed_regs ) { + ShowNotice("Account registration denied (registration limit exceeded)\n"); + return 3; + } + + if( login_config.new_acc_length_limit && ( strlen(userid) < 4 || strlen(pass) < 4 ) ) + return 1; + + // check for invalid inputs + if( sex != 'M' && sex != 'F' ) + return 0; // 0 = Unregistered ID + + // check if the account doesn't exist already + if( accounts->load_str(accounts, &acc, userid) ) { + ShowNotice("Attempt of creation of an already existant account (account: %s_%c, pass: %s, received pass: %s)\n", userid, sex, acc.pass, pass); + return 1; // 1 = Incorrect Password + } + + memset(&acc, '\0', sizeof(acc)); + acc.account_id = -1; // assigned by account db + safestrncpy(acc.userid, userid, sizeof(acc.userid)); + safestrncpy(acc.pass, pass, sizeof(acc.pass)); + acc.sex = sex; + safestrncpy(acc.email, "a@a.com", sizeof(acc.email)); + acc.expiration_time = ( login_config.start_limited_time != -1 ) ? time(NULL) + login_config.start_limited_time : 0; + safestrncpy(acc.lastlogin, "0000-00-00 00:00:00", sizeof(acc.lastlogin)); + safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip)); + safestrncpy(acc.birthdate, "0000-00-00", sizeof(acc.birthdate)); + + if( !accounts->create(accounts, &acc) ) + return 0; + + ShowNotice("Account creation (account %s, id: %d, pass: %s, sex: %c)\n", acc.userid, acc.account_id, acc.pass, acc.sex); + + if( DIFF_TICK(tick, new_reg_tick) > 0 ) {// Update the registration check. + num_regs = 0; + new_reg_tick = tick + time_allowed*1000; + } + ++num_regs; + + return -1; +} + +//----------------------------------------------------- +// Check/authentication of a connection +//----------------------------------------------------- +int mmo_auth(struct login_session_data* sd, bool isServer) { + struct mmo_account acc; + int len; + + char ip[16]; + ip2str(session[sd->fd]->client_addr, ip); + + // DNS Blacklist check + if( login_config.use_dnsbl ) { + char r_ip[16]; + char ip_dnsbl[256]; + char* dnsbl_serv; + uint8* sin_addr = (uint8*)&session[sd->fd]->client_addr; + + sprintf(r_ip, "%u.%u.%u.%u", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + for( dnsbl_serv = strtok(login_config.dnsbl_servs,","); dnsbl_serv != NULL; dnsbl_serv = strtok(NULL,",") ) { + sprintf(ip_dnsbl, "%s.%s", r_ip, trim(dnsbl_serv)); + if( host2ip(ip_dnsbl) ) { + ShowInfo("DNSBL: (%s) Blacklisted. User Kicked.\n", r_ip); + return 3; + } + } + + } + + //Client Version check + if( login_config.check_client_version && sd->version != login_config.client_version_to_connect ) + return 5; + + len = strnlen(sd->userid, NAME_LENGTH); + + // Account creation with _M/_F + if( login_config.new_account_flag ) { + if( len > 2 && strnlen(sd->passwd, NAME_LENGTH) > 0 && // valid user and password lengths + sd->passwdenc == 0 && // unencoded password + sd->userid[len-2] == '_' && memchr("FfMm", sd->userid[len-1], 4) ) // _M/_F suffix + { + int result; + + // remove the _M/_F suffix + len -= 2; + sd->userid[len] = '\0'; + + result = mmo_auth_new(sd->userid, sd->passwd, TOUPPER(sd->userid[len+1]), ip); + if( result != -1 ) + return result;// Failed to make account. [Skotlex]. + } + } + + if( !accounts->load_str(accounts, &acc, sd->userid) ) { + ShowNotice("Unknown account (account: %s, received pass: %s, ip: %s)\n", sd->userid, sd->passwd, ip); + return 0; // 0 = Unregistered ID + } + + if( !check_password(sd->md5key, sd->passwdenc, sd->passwd, acc.pass) ) { + ShowNotice("Invalid password (account: '%s', pass: '%s', received pass: '%s', ip: %s)\n", sd->userid, acc.pass, sd->passwd, ip); + return 1; // 1 = Incorrect Password + } + + if( acc.expiration_time != 0 && acc.expiration_time < time(NULL) ) { + ShowNotice("Connection refused (account: %s, pass: %s, expired ID, ip: %s)\n", sd->userid, sd->passwd, ip); + return 2; // 2 = This ID is expired + } + + if( acc.unban_time != 0 && acc.unban_time > time(NULL) ) { + char tmpstr[24]; + timestamp2string(tmpstr, sizeof(tmpstr), acc.unban_time, login_config.date_format); + ShowNotice("Connection refused (account: %s, pass: %s, banned until %s, ip: %s)\n", sd->userid, sd->passwd, tmpstr, ip); + return 6; // 6 = Your are Prohibited to log in until %s + } + + if( acc.state != 0 ) { + ShowNotice("Connection refused (account: %s, pass: %s, state: %d, ip: %s)\n", sd->userid, sd->passwd, acc.state, ip); + return acc.state - 1; + } + + if( login_config.client_hash_check && !isServer ) { + struct client_hash_node *node = login_config.client_hash_nodes; + bool match = false; + + if( !sd->has_client_hash ) { + ShowNotice("Client doesn't sent client hash (account: %s, pass: %s, ip: %s)\n", sd->userid, sd->passwd, acc.state, ip); + return 5; + } + + while( node ) { + if( node->group_id <= acc.group_id && memcmp(node->hash, sd->client_hash, 16) == 0 ) { + match = true; + break; + } + + node = node->next; + } + + if( !match ) { + char smd5[33]; + int i; + + for( i = 0; i < 16; i++ ) + sprintf(&smd5[i * 2], "%02x", sd->client_hash[i]); + + ShowNotice("Invalid client hash (account: %s, pass: %s, sent md5: %d, ip: %s)\n", sd->userid, sd->passwd, smd5, ip); + return 5; + } + } + + ShowNotice("Authentication accepted (account: %s, id: %d, ip: %s)\n", sd->userid, acc.account_id, ip); + + // update session data + sd->account_id = acc.account_id; + sd->login_id1 = rnd(); + sd->login_id2 = rnd(); + safestrncpy(sd->lastlogin, acc.lastlogin, sizeof(sd->lastlogin)); + sd->sex = acc.sex; + sd->group_id = acc.group_id; + + // update account data + timestamp2string(acc.lastlogin, sizeof(acc.lastlogin), time(NULL), "%Y-%m-%d %H:%M:%S"); + safestrncpy(acc.last_ip, ip, sizeof(acc.last_ip)); + acc.unban_time = 0; + acc.logincount++; + + accounts->save(accounts, &acc); + + if( sd->sex != 'S' && sd->account_id < START_ACCOUNT_NUM ) + ShowWarning("Account %s has account id %d! Account IDs must be over %d to work properly!\n", sd->userid, sd->account_id, START_ACCOUNT_NUM); + + return -1; // account OK +} + +void login_auth_ok(struct login_session_data* sd) +{ + int fd = sd->fd; + uint32 ip = session[fd]->client_addr; + + uint8 server_num, n; + uint32 subnet_char_ip; + struct auth_node* node; + int i; + + if( runflag != LOGINSERVER_ST_RUNNING ) + { + // players can only login while running + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1;// server closed + WFIFOSET(fd,3); + return; + } + + if( login_config.group_id_to_connect >= 0 && sd->group_id != login_config.group_id_to_connect ) { + ShowStatus("Connection refused: the required group id for connection is %d (account: %s, group: %d).\n", login_config.group_id_to_connect, sd->userid, sd->group_id); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + return; + } else if( login_config.min_group_id_to_connect >= 0 && login_config.group_id_to_connect == -1 && sd->group_id < login_config.min_group_id_to_connect ) { + ShowStatus("Connection refused: the minium group id required for connection is %d (account: %s, group: %d).\n", login_config.min_group_id_to_connect, sd->userid, sd->group_id); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + return; + } + + server_num = 0; + for( i = 0; i < ARRAYLENGTH(server); ++i ) + if( session_isActive(server[i].fd) ) + server_num++; + + if( server_num == 0 ) + {// if no char-server, don't send void list of servers, just disconnect the player with proper message + ShowStatus("Connection refused: there is no char-server online (account: %s).\n", sd->userid); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + return; + } + + { + struct online_login_data* data = (struct online_login_data*)idb_get(online_db, sd->account_id); + if( data ) + {// account is already marked as online! + if( data->char_server > -1 ) + {// Request char servers to kick this account out. [Skotlex] + uint8 buf[6]; + ShowNotice("User '%s' is already online - Rejected.\n", sd->userid); + WBUFW(buf,0) = 0x2734; + WBUFL(buf,2) = sd->account_id; + charif_sendallwos(-1, buf, 6); + if( data->waiting_disconnect == INVALID_TIMER ) + data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, waiting_disconnect_timer, sd->account_id, 0); + + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; // 08 = Server still recognizes your last login + WFIFOSET(fd,3); + return; + } + else + if( data->char_server == -1 ) + {// client has authed but did not access char-server yet + // wipe previous session + idb_remove(auth_db, sd->account_id); + remove_online_user(sd->account_id); + data = NULL; + } + } + } + + login_log(ip, sd->userid, 100, "login ok"); + ShowStatus("Connection of the account '%s' accepted.\n", sd->userid); + + WFIFOHEAD(fd,47+32*server_num); + WFIFOW(fd,0) = 0x69; + WFIFOW(fd,2) = 47+32*server_num; + WFIFOL(fd,4) = sd->login_id1; + WFIFOL(fd,8) = sd->account_id; + WFIFOL(fd,12) = sd->login_id2; + WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) + //memcpy(WFIFOP(fd,20), sd->lastlogin, 24); // in old version, that was for name (not more used) + memset(WFIFOP(fd,20), 0, 24); + WFIFOW(fd,44) = 0; // unknown + WFIFOB(fd,46) = sex_str2num(sd->sex); + for( i = 0, n = 0; i < ARRAYLENGTH(server); ++i ) + { + if( !session_isValid(server[i].fd) ) + continue; + + subnet_char_ip = lan_subnetcheck(ip); // Advanced subnet check [LuzZza] + WFIFOL(fd,47+n*32) = htonl((subnet_char_ip) ? subnet_char_ip : server[i].ip); + WFIFOW(fd,47+n*32+4) = ntows(htons(server[i].port)); // [!] LE byte order here [!] + memcpy(WFIFOP(fd,47+n*32+6), server[i].name, 20); + WFIFOW(fd,47+n*32+26) = server[i].users; + WFIFOW(fd,47+n*32+28) = server[i].type; + WFIFOW(fd,47+n*32+30) = server[i].new_; + n++; + } + WFIFOSET(fd,47+32*server_num); + + // create temporary auth entry + CREATE(node, struct auth_node, 1); + node->account_id = sd->account_id; + node->login_id1 = sd->login_id1; + node->login_id2 = sd->login_id2; + node->sex = sd->sex; + node->ip = ip; + node->version = sd->version; + node->clienttype = sd->clienttype; + idb_put(auth_db, sd->account_id, node); + + { + struct online_login_data* data; + + // mark client as 'online' + data = add_online_user(-1, sd->account_id); + + // schedule deletion of this node + data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, waiting_disconnect_timer, sd->account_id, 0); + } +} + +void login_auth_failed(struct login_session_data* sd, int result) +{ + int fd = sd->fd; + uint32 ip = session[fd]->client_addr; + + if (login_config.log_login) + { + const char* error; + switch( result ) { + case 0: error = "Unregistered ID."; break; // 0 = Unregistered ID + case 1: error = "Incorrect Password."; break; // 1 = Incorrect Password + case 2: error = "Account Expired."; break; // 2 = This ID is expired + case 3: error = "Rejected from server."; break; // 3 = Rejected from Server + case 4: error = "Blocked by GM."; break; // 4 = You have been blocked by the GM Team + case 5: error = "Not latest game EXE."; break; // 5 = Your Game's EXE file is not the latest version + case 6: error = "Banned."; break; // 6 = Your are Prohibited to log in until %s + case 7: error = "Server Over-population."; break; // 7 = Server is jammed due to over populated + case 8: error = "Account limit from company"; break; // 8 = No more accounts may be connected from this company + case 9: error = "Ban by DBA"; break; // 9 = MSI_REFUSE_BAN_BY_DBA + case 10: error = "Email not confirmed"; break; // 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED + case 11: error = "Ban by GM"; break; // 11 = MSI_REFUSE_BAN_BY_GM + case 12: error = "Working in DB"; break; // 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK + case 13: error = "Self Lock"; break; // 13 = MSI_REFUSE_SELF_LOCK + case 14: error = "Not Permitted Group"; break; // 14 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 15: error = "Not Permitted Group"; break; // 15 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 99: error = "Account gone."; break; // 99 = This ID has been totally erased + case 100: error = "Login info remains."; break; // 100 = Login information remains at %s + case 101: error = "Hacking investigation."; break; // 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information + case 102: error = "Bug investigation."; break; // 102 = This account has been temporarily prohibited from login due to a bug-related investigation + case 103: error = "Deleting char."; break; // 103 = This character is being deleted. Login is temporarily unavailable for the time being + case 104: error = "Deleting spouse char."; break; // 104 = This character is being deleted. Login is temporarily unavailable for the time being + default : error = "Unknown Error."; break; + } + + login_log(ip, sd->userid, result, error); + } + + if( result == 1 && login_config.dynamic_pass_failure_ban ) + ipban_log(ip); // log failed password attempt + + WFIFOHEAD(fd,23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = (uint8)result; + if( result != 6 ) + memset(WFIFOP(fd,3), '\0', 20); + else + {// 6 = Your are Prohibited to log in until %s + struct mmo_account acc; + time_t unban_time = ( accounts->load_str(accounts, &acc, sd->userid) ) ? acc.unban_time : 0; + timestamp2string((char*)WFIFOP(fd,3), 20, unban_time, login_config.date_format); + } + WFIFOSET(fd,23); +} + + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or char-server connection requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) +{ + struct login_session_data* sd = (struct login_session_data*)session[fd]->session_data; + int result; + + char ip[16]; + uint32 ipl = session[fd]->client_addr; + ip2str(ipl, ip); + + if( session[fd]->flag.eof ) + { + ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip); + do_close(fd); + return 0; + } + + if( sd == NULL ) + { + // Perform ip-ban check + if( login_config.ipban && ipban_check(ipl) ) + { + ShowStatus("Connection refused: IP isn't authorised (deny/allow, ip: %s).\n", ip); + login_log(ipl, "unknown", -3, "ip banned"); + WFIFOHEAD(fd,23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // 3 = Rejected from Server + WFIFOSET(fd,23); + set_eof(fd); + return 0; + } + + // create a session for this new connection + CREATE(session[fd]->session_data, struct login_session_data, 1); + sd = (struct login_session_data*)session[fd]->session_data; + sd->fd = fd; + } + + while( RFIFOREST(fd) >= 2 ) + { + uint16 command = RFIFOW(fd,0); + + switch( command ) + { + + case 0x0200: // New alive packet: structure: 0x200 <account.userid>.24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + // client md5 hash (binary) + case 0x0204: // S 0204 <md5 hash>.16B (kRO 2004-05-31aSakexe langtype 0 and 6) + if (RFIFOREST(fd) < 18) + return 0; + + sd->has_client_hash = 1; + memcpy(sd->client_hash, RFIFOP(fd, 2), 16); + + RFIFOSKIP(fd,18); + break; + + // request client login (raw password) + case 0x0064: // S 0064 <version>.L <username>.24B <password>.24B <clienttype>.B + case 0x0277: // S 0277 <version>.L <username>.24B <password>.24B <clienttype>.B <ip address>.16B <adapter address>.13B + case 0x02b0: // S 02b0 <version>.L <username>.24B <password>.24B <clienttype>.B <ip address>.16B <adapter address>.13B <g_isGravityID>.B + // request client login (md5-hashed password) + case 0x01dd: // S 01dd <version>.L <username>.24B <password hash>.16B <clienttype>.B + case 0x01fa: // S 01fa <version>.L <username>.24B <password hash>.16B <clienttype>.B <?>.B(index of the connection in the clientinfo file (+10 if the command-line contains "pc")) + case 0x027c: // S 027c <version>.L <username>.24B <password hash>.16B <clienttype>.B <?>.13B(junk) + case 0x0825: // S 0825 <packetsize>.W <version>.L <clienttype>.B <userid>.24B <password>.27B <mac>.17B <ip>.15B <token>.(packetsize - 0x5C)B + { + size_t packet_len = RFIFOREST(fd); + + if( (command == 0x0064 && packet_len < 55) + || (command == 0x0277 && packet_len < 84) + || (command == 0x02b0 && packet_len < 85) + || (command == 0x01dd && packet_len < 47) + || (command == 0x01fa && packet_len < 48) + || (command == 0x027c && packet_len < 60) + || (command == 0x0825 && (packet_len < 4 || packet_len < RFIFOW(fd, 2))) ) + return 0; + } + { + uint32 version; + char username[NAME_LENGTH]; + char password[NAME_LENGTH]; + unsigned char passhash[16]; + uint8 clienttype; + bool israwpass = (command==0x0064 || command==0x0277 || command==0x02b0 || command == 0x0825); + + // Shinryo: For the time being, just use token as password. + if(command == 0x0825) + { + char *accname = (char *)RFIFOP(fd, 9); + char *token = (char *)RFIFOP(fd, 0x5C); + size_t uAccLen = strlen(accname); + size_t uTokenLen = RFIFOREST(fd) - 0x5C; + + version = RFIFOL(fd,4); + + if(uAccLen > NAME_LENGTH - 1 || uAccLen <= 0 || uTokenLen > NAME_LENGTH - 1 || uTokenLen <= 0) + { + login_auth_failed(sd, 3); + return 0; + } + + safestrncpy(username, accname, uAccLen + 1); + safestrncpy(password, token, uTokenLen + 1); + clienttype = RFIFOB(fd, 8); + } + else + { + version = RFIFOL(fd,2); + safestrncpy(username, (const char*)RFIFOP(fd,6), NAME_LENGTH); + if( israwpass ) + { + safestrncpy(password, (const char*)RFIFOP(fd,30), NAME_LENGTH); + clienttype = RFIFOB(fd,54); + } + else + { + memcpy(passhash, RFIFOP(fd,30), 16); + clienttype = RFIFOB(fd,46); + } + } + RFIFOSKIP(fd,RFIFOREST(fd)); // assume no other packet was sent + + sd->clienttype = clienttype; + sd->version = version; + safestrncpy(sd->userid, username, NAME_LENGTH); + if( israwpass ) + { + ShowStatus("Request for connection of %s (ip: %s).\n", sd->userid, ip); + safestrncpy(sd->passwd, password, NAME_LENGTH); + if( login_config.use_md5_passwds ) + MD5_String(sd->passwd, sd->passwd); + sd->passwdenc = 0; + } + else + { + ShowStatus("Request for connection (passwdenc mode) of %s (ip: %s).\n", sd->userid, ip); + bin2hex(sd->passwd, passhash, 16); // raw binary data here! + sd->passwdenc = PASSWORDENC; + } + + if( sd->passwdenc != 0 && login_config.use_md5_passwds ) + { + login_auth_failed(sd, 3); // send "rejected from server" + return 0; + } + + result = mmo_auth(sd, false); + + if( result == -1 ) + login_auth_ok(sd); + else + login_auth_failed(sd, result); + } + break; + + case 0x01db: // Sending request of the coding key + RFIFOSKIP(fd,2); + { + memset(sd->md5key, '\0', sizeof(sd->md5key)); + sd->md5keylen = (uint16)(12 + rnd() % 4); + MD5_Salt(sd->md5keylen, sd->md5key); + + WFIFOHEAD(fd,4 + sd->md5keylen); + WFIFOW(fd,0) = 0x01dc; + WFIFOW(fd,2) = 4 + sd->md5keylen; + memcpy(WFIFOP(fd,4), sd->md5key, sd->md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + } + break; + + case 0x2710: // Connection request of a char-server + if (RFIFOREST(fd) < 86) + return 0; + { + char server_name[20]; + char message[256]; + uint32 server_ip; + uint16 server_port; + uint16 type; + uint16 new_; + + safestrncpy(sd->userid, (char*)RFIFOP(fd,2), NAME_LENGTH); + safestrncpy(sd->passwd, (char*)RFIFOP(fd,26), NAME_LENGTH); + if( login_config.use_md5_passwds ) + MD5_String(sd->passwd, sd->passwd); + sd->passwdenc = 0; + sd->version = login_config.client_version_to_connect; // hack to skip version check + server_ip = ntohl(RFIFOL(fd,54)); + server_port = ntohs(RFIFOW(fd,58)); + safestrncpy(server_name, (char*)RFIFOP(fd,60), 20); + type = RFIFOW(fd,82); + new_ = RFIFOW(fd,84); + RFIFOSKIP(fd,86); + + ShowInfo("Connection request of the char-server '%s' @ %u.%u.%u.%u:%u (account: '%s', pass: '%s', ip: '%s')\n", server_name, CONVIP(server_ip), server_port, sd->userid, sd->passwd, ip); + sprintf(message, "charserver - %s@%u.%u.%u.%u:%u", server_name, CONVIP(server_ip), server_port); + login_log(session[fd]->client_addr, sd->userid, 100, message); + + result = mmo_auth(sd, true); + if( runflag == LOGINSERVER_ST_RUNNING && + result == -1 && + sd->sex == 'S' && + sd->account_id >= 0 && sd->account_id < ARRAYLENGTH(server) && + !session_isValid(server[sd->account_id].fd) ) + { + ShowStatus("Connection of the char-server '%s' accepted.\n", server_name); + safestrncpy(server[sd->account_id].name, server_name, sizeof(server[sd->account_id].name)); + server[sd->account_id].fd = fd; + server[sd->account_id].ip = server_ip; + server[sd->account_id].port = server_port; + server[sd->account_id].users = 0; + server[sd->account_id].type = type; + server[sd->account_id].new_ = new_; + + session[fd]->func_parse = parse_fromchar; + session[fd]->flag.server = 1; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + // send connection success + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + else + { + ShowNotice("Connection of the char-server '%s' REFUSED.\n", server_name); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } + } + return 0; // processing will continue elsewhere + + default: + ShowNotice("Abnormal end of connection (ip: %s): Unknown packet 0x%x\n", ip, command); + set_eof(fd); + return 0; + } + } + + return 0; +} + + +void login_set_defaults() +{ + login_config.login_ip = INADDR_ANY; + login_config.login_port = 6900; + login_config.ipban_cleanup_interval = 60; + login_config.ip_sync_interval = 0; + login_config.log_login = true; + safestrncpy(login_config.date_format, "%Y-%m-%d %H:%M:%S", sizeof(login_config.date_format)); + login_config.console = false; + login_config.new_account_flag = true; + login_config.new_acc_length_limit = true; + login_config.use_md5_passwds = false; + login_config.group_id_to_connect = -1; + login_config.min_group_id_to_connect = -1; + login_config.check_client_version = false; + login_config.client_version_to_connect = 20; + + login_config.ipban = true; + login_config.dynamic_pass_failure_ban = true; + login_config.dynamic_pass_failure_ban_interval = 5; + login_config.dynamic_pass_failure_ban_limit = 7; + login_config.dynamic_pass_failure_ban_duration = 5; + login_config.use_dnsbl = false; + safestrncpy(login_config.dnsbl_servs, "", sizeof(login_config.dnsbl_servs)); + safestrncpy(login_config.account_engine, "auto", sizeof(login_config.account_engine)); + + login_config.client_hash_check = 0; + login_config.client_hash_nodes = NULL; +} + +//----------------------------------- +// Reading main configuration file +//----------------------------------- +int login_config_read(const char* cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE* fp = fopen(cfgName, "r"); + if (fp == NULL) { + ShowError("Configuration file (%s) not found.\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,"timestamp_format")) + strncpy(timestamp_format, w2, 20); + else if(!strcmpi(w1,"stdout_with_ansisequence")) + stdout_with_ansisequence = config_switch(w2); + else if(!strcmpi(w1,"console_silent")) { + msg_silent = atoi(w2); + if( msg_silent ) /* only bother if we actually have this enabled */ + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + } + else if( !strcmpi(w1, "bind_ip") ) { + char ip_str[16]; + login_config.login_ip = host2ip(w2); + if( login_config.login_ip ) + ShowStatus("Login server binding IP address : %s -> %s\n", w2, ip2str(login_config.login_ip, ip_str)); + } + else if( !strcmpi(w1, "login_port") ) { + login_config.login_port = (uint16)atoi(w2); + } + else if(!strcmpi(w1, "log_login")) + login_config.log_login = (bool)config_switch(w2); + + else if(!strcmpi(w1, "new_account")) + login_config.new_account_flag = (bool)config_switch(w2); + else if(!strcmpi(w1, "new_acc_length_limit")) + login_config.new_acc_length_limit = (bool)config_switch(w2); + else if(!strcmpi(w1, "start_limited_time")) + login_config.start_limited_time = atoi(w2); + else if(!strcmpi(w1, "check_client_version")) + login_config.check_client_version = (bool)config_switch(w2); + else if(!strcmpi(w1, "client_version_to_connect")) + login_config.client_version_to_connect = strtoul(w2, NULL, 10); + else if(!strcmpi(w1, "use_MD5_passwords")) + login_config.use_md5_passwds = (bool)config_switch(w2); + else if(!strcmpi(w1, "group_id_to_connect")) + login_config.group_id_to_connect = atoi(w2); + else if(!strcmpi(w1, "min_group_id_to_connect")) + login_config.min_group_id_to_connect = atoi(w2); + else if(!strcmpi(w1, "date_format")) + safestrncpy(login_config.date_format, w2, sizeof(login_config.date_format)); + else if(!strcmpi(w1, "console")) + login_config.console = (bool)config_switch(w2); + else if(!strcmpi(w1, "allowed_regs")) //account flood protection system + allowed_regs = atoi(w2); + else if(!strcmpi(w1, "time_allowed")) + time_allowed = atoi(w2); + else if(!strcmpi(w1, "use_dnsbl")) + login_config.use_dnsbl = (bool)config_switch(w2); + else if(!strcmpi(w1, "dnsbl_servers")) + safestrncpy(login_config.dnsbl_servs, w2, sizeof(login_config.dnsbl_servs)); + else if(!strcmpi(w1, "ipban_cleanup_interval")) + login_config.ipban_cleanup_interval = (unsigned int)atoi(w2); + else if(!strcmpi(w1, "ip_sync_interval")) + login_config.ip_sync_interval = (unsigned int)1000*60*atoi(w2); //w2 comes in minutes. + else if(!strcmpi(w1, "client_hash_check")) + login_config.client_hash_check = config_switch(w2); + else if(!strcmpi(w1, "client_hash")) { + int group = 0; + char md5[33]; + int i; + + if (sscanf(w2, "%d, %32s", &group, md5) == 2) { + struct client_hash_node *nnode; + CREATE(nnode, struct client_hash_node, 1); + + for (i = 0; i < 32; i += 2) { + char buf[3]; + unsigned int byte; + + memcpy(buf, &md5[i], 2); + buf[2] = 0; + + sscanf(buf, "%x", &byte); + nnode->hash[i / 2] = (uint8)(byte & 0xFF); + } + + nnode->group_id = group; + nnode->next = login_config.client_hash_nodes; + + login_config.client_hash_nodes = nnode; + } + } + else if(!strcmpi(w1, "import")) + login_config_read(w2); + else + if(!strcmpi(w1, "account.engine")) + safestrncpy(login_config.account_engine, w2, sizeof(login_config.account_engine)); + else + {// try the account engines + int i; + for( i = 0; account_engines[i].constructor; ++i ) + { + AccountDB* db = account_engines[i].db; + if( db && db->set_property(db, w1, w2) ) + break; + } + // try others + ipban_config_read(w1, w2); + loginlog_config_read(w1, w2); + } + } + fclose(fp); + ShowInfo("Finished reading %s.\n", cfgName); + return 0; +} + +/// Get the engine selected in the config settings. +/// Updates the config setting with the selected engine if 'auto'. +static AccountDB* get_account_engine(void) +{ + int i; + bool get_first = (strcmp(login_config.account_engine,"auto") == 0); + + for( i = 0; account_engines[i].constructor; ++i ) + { + char name[sizeof(login_config.account_engine)]; + AccountDB* db = account_engines[i].db; + if( db && db->get_property(db, "engine.name", name, sizeof(name)) && + (get_first || strcmp(name, login_config.account_engine) == 0) ) + { + if( get_first ) + safestrncpy(login_config.account_engine, name, sizeof(login_config.account_engine)); + return db; + } + } + return NULL; +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) +{ + int i; + struct client_hash_node *hn = login_config.client_hash_nodes; + + while (hn) + { + struct client_hash_node *tmp = hn; + hn = hn->next; + aFree(tmp); + } + + login_log(0, "login server", 100, "login server shutdown"); + ShowStatus("Terminating...\n"); + + if( login_config.log_login ) + loginlog_final(); + + ipban_final(); + + for( i = 0; account_engines[i].constructor; ++i ) + {// destroy all account engines + AccountDB* db = account_engines[i].db; + if( db ) + { + db->destroy(db); + account_engines[i].db = NULL; + } + } + accounts = NULL; // destroyed in account_engines + online_db->destroy(online_db, NULL); + auth_db->destroy(auth_db, NULL); + + for( i = 0; i < ARRAYLENGTH(server); ++i ) + chrif_server_destroy(i); + + if( login_fd != -1 ) + { + do_close(login_fd); + login_fd = -1; + } + + ShowStatus("Finished.\n"); +} + +//------------------------------ +// Function called when the server +// has received a crash signal. +//------------------------------ +void do_abort(void) +{ +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_LOGIN; +} + + +/// Called when a terminate signal is received. +void do_shutdown(void) +{ + if( runflag != LOGINSERVER_ST_SHUTDOWN ) + { + int id; + runflag = LOGINSERVER_ST_SHUTDOWN; + ShowStatus("Shutting down...\n"); + // TODO proper shutdown procedure; kick all characters, wait for acks, ... [FlavioJS] + for( id = 0; id < ARRAYLENGTH(server); ++id ) + chrif_server_reset(id); + flush_fifos(); + runflag = CORE_ST_STOP; + } +} + + +//------------------------------ +// Login server initialization +//------------------------------ +int do_init(int argc, char** argv) +{ + int i; + + // intialize engines (to accept config settings) + for( i = 0; account_engines[i].constructor; ++i ) + account_engines[i].db = account_engines[i].constructor(); + + // read login-server configuration + login_set_defaults(); + login_config_read((argc > 1) ? argv[1] : LOGIN_CONF_NAME); + login_lan_config_read((argc > 2) ? argv[2] : LAN_CONF_NAME); + + rnd_init(); + + for( i = 0; i < ARRAYLENGTH(server); ++i ) + chrif_server_init(i); + + // initialize logging + if( login_config.log_login ) + loginlog_init(); + + // initialize static and dynamic ipban system + ipban_init(); + + // Online user database init + online_db = idb_alloc(DB_OPT_RELEASE_DATA); + add_timer_func_list(waiting_disconnect_timer, "waiting_disconnect_timer"); + + // Interserver auth init + auth_db = idb_alloc(DB_OPT_RELEASE_DATA); + + // set default parser as parse_login function + set_defaultparse(parse_login); + + // every 10 minutes cleanup online account db. + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600*1000); + + // add timer to detect ip address change and perform update + if (login_config.ip_sync_interval) { + add_timer_func_list(sync_ip_addresses, "sync_ip_addresses"); + add_timer_interval(gettick() + login_config.ip_sync_interval, sync_ip_addresses, 0, 0, login_config.ip_sync_interval); + } + + // Account database init + accounts = get_account_engine(); + if( accounts == NULL ) { + ShowFatalError("do_init: account engine '%s' not found.\n", login_config.account_engine); + exit(EXIT_FAILURE); + } else { + + if(!accounts->init(accounts)) { + ShowFatalError("do_init: Failed to initialize account engine '%s'.\n", login_config.account_engine); + exit(EXIT_FAILURE); + } + } + + if( login_config.console ) + { + //##TODO invoke a CONSOLE_START plugin event + } + + // server port open & binding + login_fd = make_listen_bind(login_config.login_ip, login_config.login_port); + + if( runflag != CORE_ST_STOP ) + { + shutdown_callback = do_shutdown; + runflag = LOGINSERVER_ST_RUNNING; + } + + ShowStatus("The login-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %u).\n\n", login_config.login_port); + login_log(0, "login server", 100, "login server started"); + + return 0; +} diff --git a/src/login/login.h b/src/login/login.h new file mode 100644 index 000000000..bedf5e179 --- /dev/null +++ b/src/login/login.h @@ -0,0 +1,102 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#include "../common/mmo.h" // NAME_LENGTH,SEX_* +#include "../common/core.h" // CORE_ST_LAST + +enum E_LOGINSERVER_ST +{ + LOGINSERVER_ST_RUNNING = CORE_ST_LAST, + LOGINSERVER_ST_SHUTDOWN, + LOGINSERVER_ST_LAST +}; + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define LAN_CONF_NAME "conf/subnet_athena.conf" + +// supported encryption types: 1- passwordencrypt, 2- passwordencrypt2, 3- both +#define PASSWORDENC 3 + +struct login_session_data { + int account_id; + long login_id1; + long login_id2; + char sex;// 'F','M','S' + + char userid[NAME_LENGTH]; + char passwd[32+1]; // 23+1 for plaintext, 32+1 for md5-ed passwords + int passwdenc; + char md5key[20]; + uint16 md5keylen; + + char lastlogin[24]; + uint8 group_id; + uint8 clienttype; + uint32 version; + + uint8 client_hash[16]; + int has_client_hash; + + int fd; +}; + +struct mmo_char_server { + + char name[20]; + int fd; + uint32 ip; + uint16 port; + uint16 users; // user count on this server + uint16 type; // 0=normal, 1=maintenance, 2=over 18, 3=paying, 4=P2P + uint16 new_; // should display as 'new'? +}; + +struct client_hash_node { + int group_id; + uint8 hash[16]; + struct client_hash_node *next; +}; + +struct Login_Config { + + uint32 login_ip; // the address to bind to + uint16 login_port; // the port to bind to + unsigned int ipban_cleanup_interval; // interval (in seconds) to clean up expired IP bans + unsigned int ip_sync_interval; // interval (in minutes) to execute a DNS/IP update (for dynamic IPs) + bool log_login; // whether to log login server actions or not + char date_format[32]; // date format used in messages + bool console; // console input system enabled? + bool new_account_flag,new_acc_length_limit; // autoregistration via _M/_F ? / if yes minimum length is 4? + int start_limited_time; // new account expiration time (-1: unlimited) + bool use_md5_passwds; // work with password hashes instead of plaintext passwords? + int group_id_to_connect; // required group id to connect + int min_group_id_to_connect; // minimum group id to connect + bool check_client_version; // check the clientversion set in the clientinfo ? + uint32 client_version_to_connect; // the client version needed to connect (if checking is enabled) + + bool ipban; // perform IP blocking (via contents of `ipbanlist`) ? + bool dynamic_pass_failure_ban; // automatic IP blocking due to failed login attemps ? + unsigned int dynamic_pass_failure_ban_interval; // how far to scan the loginlog for password failures + unsigned int dynamic_pass_failure_ban_limit; // number of failures needed to trigger the ipban + unsigned int dynamic_pass_failure_ban_duration; // duration of the ipban + bool use_dnsbl; // dns blacklist blocking ? + char dnsbl_servs[1024]; // comma-separated list of dnsbl servers + + char account_engine[256]; // name of the engine to use (defaults to auto, for the first available engine) + + int client_hash_check; // flags for checking client md5 + struct client_hash_node *client_hash_nodes; // linked list containg md5 hash for each gm group +}; + +#define sex_num2str(num) ( (num == SEX_FEMALE ) ? 'F' : (num == SEX_MALE ) ? 'M' : 'S' ) +#define sex_str2num(str) ( (str == 'F' ) ? SEX_FEMALE : (str == 'M' ) ? SEX_MALE : SEX_SERVER ) + +#define MAX_SERVERS 30 +extern struct mmo_char_server server[MAX_SERVERS]; +extern struct Login_Config login_config; + + +#endif /* _LOGIN_H_ */ diff --git a/src/login/loginlog.h b/src/login/loginlog.h new file mode 100644 index 000000000..a1ffaae85 --- /dev/null +++ b/src/login/loginlog.h @@ -0,0 +1,15 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef __LOGINLOG_H_INCLUDED__ +#define __LOGINLOG_H_INCLUDED__ + + +unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes); +void login_log(uint32 ip, const char* username, int rcode, const char* message); +bool loginlog_init(void); +bool loginlog_final(void); +bool loginlog_config_read(const char* w1, const char* w2); + + +#endif // __LOGINLOG_H_INCLUDED__ diff --git a/src/login/loginlog_sql.c b/src/login/loginlog_sql.c new file mode 100644 index 000000000..d61172697 --- /dev/null +++ b/src/login/loginlog_sql.c @@ -0,0 +1,184 @@ +// 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/socket.h" +#include "../common/sql.h" +#include "../common/strlib.h" +#include <string.h> +#include <stdlib.h> // exit + +// global sql settings (in ipban_sql.c) +static char global_db_hostname[32] = "127.0.0.1"; +static uint16 global_db_port = 3306; +static char global_db_username[32] = "ragnarok"; +static char global_db_password[32] = "ragnarok"; +static char global_db_database[32] = "ragnarok"; +static char global_codepage[32] = ""; +// local sql settings +static char log_db_hostname[32] = ""; +static uint16 log_db_port = 0; +static char log_db_username[32] = ""; +static char log_db_password[32] = ""; +static char log_db_database[32] = ""; +static char log_codepage[32] = ""; +static char log_login_db[256] = "loginlog"; + +static Sql* sql_handle = NULL; +static bool enabled = false; + + +// Returns the number of failed login attemps by the ip in the last minutes. +unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes) +{ + unsigned long failures = 0; + + if( !enabled ) + return 0; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT count(*) FROM `%s` WHERE `ip` = '%s' AND `rcode` = '1' AND `time` > NOW() - INTERVAL %d MINUTE", + log_login_db, ip2str(ip,NULL), minutes) )// how many times failed account? in one ip. + Sql_ShowDebug(sql_handle); + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ) + { + char* data; + Sql_GetData(sql_handle, 0, &data, NULL); + failures = strtoul(data, NULL, 10); + Sql_FreeResult(sql_handle); + } + return failures; +} + + +/*============================================= + * Records an event in the login log + *---------------------------------------------*/ +void login_log(uint32 ip, const char* username, int rcode, const char* message) +{ + char esc_username[NAME_LENGTH*2+1]; + char esc_message[255*2+1]; + int retcode; + + if( !enabled ) + return; + + Sql_EscapeStringLen(sql_handle, esc_username, username, strnlen(username, NAME_LENGTH)); + Sql_EscapeStringLen(sql_handle, esc_message, message, strnlen(message, 255)); + + retcode = Sql_Query(sql_handle, + "INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%s', '%s', '%d', '%s')", + log_login_db, ip2str(ip,NULL), esc_username, rcode, esc_message); + + if( retcode != SQL_SUCCESS ) + Sql_ShowDebug(sql_handle); +} + +bool loginlog_init(void) +{ + const char* username; + const char* password; + const char* hostname; + uint16 port; + const char* database; + const char* codepage; + + if( log_db_hostname[0] != '\0' ) + {// local settings + username = log_db_username; + password = log_db_password; + hostname = log_db_hostname; + port = log_db_port; + database = log_db_database; + codepage = log_codepage; + } + else + {// global settings + username = global_db_username; + password = global_db_password; + hostname = global_db_hostname; + port = global_db_port; + database = global_db_database; + codepage = global_codepage; + } + + sql_handle = Sql_Malloc(); + + if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(sql_handle); + exit(EXIT_FAILURE); + } + + if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) + Sql_ShowDebug(sql_handle); + + enabled = true; + + return true; +} + +bool loginlog_final(void) +{ + Sql_Free(sql_handle); + sql_handle = NULL; + return true; +} + +bool loginlog_config_read(const char* key, const char* value) +{ + const char* signature; + + signature = "sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "db_hostname") == 0 ) + safestrncpy(global_db_hostname, value, sizeof(global_db_hostname)); + else + if( strcmpi(key, "db_port") == 0 ) + global_db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "db_username") == 0 ) + safestrncpy(global_db_username, value, sizeof(global_db_username)); + else + if( strcmpi(key, "db_password") == 0 ) + safestrncpy(global_db_password, value, sizeof(global_db_password)); + else + if( strcmpi(key, "db_database") == 0 ) + safestrncpy(global_db_database, value, sizeof(global_db_database)); + else + if( strcmpi(key, "codepage") == 0 ) + safestrncpy(global_codepage, value, sizeof(global_codepage)); + else + return false;// not found + return true; + } + + if( strcmpi(key, "log_db_ip") == 0 ) + safestrncpy(log_db_hostname, value, sizeof(log_db_hostname)); + else + if( strcmpi(key, "log_db_port") == 0 ) + log_db_port = (uint16)strtoul(value, NULL, 10); + else + if( strcmpi(key, "log_db_id") == 0 ) + safestrncpy(log_db_username, value, sizeof(log_db_username)); + else + if( strcmpi(key, "log_db_pw") == 0 ) + safestrncpy(log_db_password, value, sizeof(log_db_password)); + else + if( strcmpi(key, "log_db_db") == 0 ) + safestrncpy(log_db_database, value, sizeof(log_db_database)); + else + if( strcmpi(key, "log_codepage") == 0 ) + safestrncpy(log_codepage, value, sizeof(log_codepage)); + else + if( strcmpi(key, "log_login_db") == 0 ) + safestrncpy(log_login_db, value, sizeof(log_login_db)); + else + return false; + + return true; +} diff --git a/src/login/sql/CMakeLists.txt b/src/login/sql/CMakeLists.txt new file mode 100644 index 000000000..1355f17ee --- /dev/null +++ b/src/login/sql/CMakeLists.txt @@ -0,0 +1,39 @@ + +# +# login sql +# +if( BUILD_SQL_SERVERS ) +message( STATUS "Creating target login-server_sql" ) +set( SQL_LOGIN_HEADERS + "${SQL_LOGIN_SOURCE_DIR}/account.h" + "${SQL_LOGIN_SOURCE_DIR}/ipban.h" + "${SQL_LOGIN_SOURCE_DIR}/login.h" + "${SQL_LOGIN_SOURCE_DIR}/loginlog.h" + ) +set( SQL_LOGIN_SOURCES + "${SQL_LOGIN_SOURCE_DIR}/account_sql.c" + "${SQL_LOGIN_SOURCE_DIR}/ipban_sql.c" + "${SQL_LOGIN_SOURCE_DIR}/login.c" + "${SQL_LOGIN_SOURCE_DIR}/loginlog_sql.c" + ) +set( DEPENDENCIES common_sql ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS} -DWITH_SQL" ) +set( SOURCE_FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ${SQL_LOGIN_HEADERS} ${SQL_LOGIN_SOURCES} ) +source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ) +source_group( login FILES ${SQL_LOGIN_HEADERS} ${SQL_LOGIN_SOURCES} ) +include_directories( ${INCLUDE_DIRS} ) +add_executable( login-server_sql ${SOURCE_FILES} ) +add_dependencies( login-server_sql ${DEPENDENCIES} ) +target_link_libraries( login-server_sql ${LIBRARIES} ${DEPENDENCIES} ) +set_target_properties( login-server_sql PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +if( INSTALL_COMPONENT_RUNTIME ) + cpack_add_component( Runtime_loginserver_sql DESCRIPTION "login-server (sql version)" DISPLAY_NAME "login-server_sql" GROUP Runtime ) + install( TARGETS login-server_sql + DESTINATION "." + COMPONENT Runtime_loginserver_sql ) +endif( INSTALL_COMPONENT_RUNTIME ) +set( TARGET_LIST ${TARGET_LIST} login-server_sql CACHE INTERNAL "" ) +message( STATUS "Creating target login-server_sql - done" ) +endif( BUILD_SQL_SERVERS ) diff --git a/src/map/CMakeLists.txt b/src/map/CMakeLists.txt new file mode 100644 index 000000000..51c3538ef --- /dev/null +++ b/src/map/CMakeLists.txt @@ -0,0 +1,12 @@ + +# +# setup +# +set( MAP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) +set( SQL_MAP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) + + +# +# targets +# +add_subdirectory( sql ) diff --git a/src/map/Makefile.in b/src/map/Makefile.in new file mode 100644 index 000000000..8e97221a7 --- /dev/null +++ b/src/map/Makefile.in @@ -0,0 +1,114 @@ + +COMMON_H = $(shell ls ../common/*.h) + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +MAP_OBJ = map.o chrif.o clif.o pc.o status.o npc.o \ + npc_chat.o chat.o path.o itemdb.o mob.o script.o \ + storage.o skill.o atcommand.o battle.o battleground.o \ + intif.o trade.o party.o vending.o guild.o pet.o \ + log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o \ + buyingstore.o searchstore.o duel.o pc_groups.o elemental.o +MAP_SQL_OBJ = $(MAP_OBJ:%=obj_sql/%) \ + obj_sql/mapreg_sql.o +MAP_H = map.h chrif.h clif.h pc.h status.h npc.h \ + chat.h itemdb.h mob.h script.h path.h \ + storage.h skill.h atcommand.h battle.h battleground.h \ + intif.h trade.h party.h vending.h guild.h pet.h \ + log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h \ + buyingstore.h searchstore.h duel.h pc_groups.h \ + ../config/core.h ../config/renewal.h ../config/secure.h ../config/const.h \ + ../config/classes/general.h elemental.h + +HAVE_MYSQL=@HAVE_MYSQL@ +ifeq ($(HAVE_MYSQL),yes) + ALL_DEPENDS=txt sql + SQL_DEPENDS=map-server_sql +else + ALL_TARGET=txt + SQL_DEPENDS=needs_mysql +endif +TXT_DEPENDS=map-server + +HAVE_PCRE=@HAVE_PCRE@ +ifeq ($(HAVE_PCRE),yes) + PCRE_CFLAGS=-DPCRE_SUPPORT @PCRE_CFLAGS@ +else + PCRE_CFLAGS= +endif + +@SET_MAKE@ + +##################################################################### +.PHONY : all txt sql clean help + +all: $(ALL_DEPENDS) + +txt: $(TXT_DEPENDS) + +sql: $(SQL_DEPENDS) + +clean: + @echo " CLEAN map" + @rm -rf *.o obj_txt obj_sql ../../map-server@EXEEXT@ ../../map-server_sql@EXEEXT@ + +help: +ifeq ($(HAVE_MYSQL),yes) + @echo "possible targets are 'sql' 'txt' 'all' 'clean' 'help'" + @echo "'sql' - map server (SQL version)" +else + @echo "possible targets are 'txt' 'all' 'clean' 'help'" +endif + @echo "'txt' - map server (TXT version)" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +needs_mysql: + @echo "MySQL not found or disabled by the configure script" + @exit 1 + +# object directories +obj_txt: + @echo " MKDIR obj_txt" + @-mkdir obj_txt + +obj_sql: + @echo " MKDIR obj_sql" + @-mkdir obj_sql + +# executables + +map-server_sql: obj_sql $(MAP_SQL_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a + @echo " LD $@" + @@CC@ @LDFLAGS@ -o ../../map-server_sql@EXEEXT@ $(MAP_SQL_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) @LIBS@ @PCRE_LIBS@ @MYSQL_LIBS@ + +# map object files + +obj_sql/%.o: %.c $(MAP_H) $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) $(PCRE_CFLAGS) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +# missing object files +../common/obj_all/common.a: + @$(MAKE) -C ../common sql + +../common/obj_sql/common_sql.a: + @$(MAKE) -C ../common sql + +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/map/atcommand.c b/src/map/atcommand.c new file mode 100644 index 000000000..c6292a7a9 --- /dev/null +++ b/src/map/atcommand.c @@ -0,0 +1,9514 @@ +// 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/timer.h" +#include "../common/nullpo.h" +#include "../common/core.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/conf.h" + +#include "atcommand.h" +#include "battle.h" +#include "chat.h" +#include "clif.h" +#include "chrif.h" +#include "duel.h" +#include "intif.h" +#include "itemdb.h" +#include "log.h" +#include "map.h" +#include "pc.h" +#include "pc_groups.h" // groupid2name +#include "status.h" +#include "skill.h" +#include "mob.h" +#include "npc.h" +#include "pet.h" +#include "homunculus.h" +#include "mail.h" +#include "mercenary.h" +#include "elemental.h" +#include "party.h" +#include "guild.h" +#include "script.h" +#include "storage.h" +#include "trade.h" +#include "unit.h" +#include "mapreg.h" +#include "quest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + + +#define ATCOMMAND_LENGTH 50 +#define ACMD_FUNC(x) static int atcommand_ ## x (const int fd, struct map_session_data* sd, const char* command, const char* message) +#define MAX_MSG 1500 + + +typedef struct AtCommandInfo AtCommandInfo; +typedef struct AliasInfo AliasInfo; + +int atcmd_binding_count = 0; + +struct AtCommandInfo { + char command[ATCOMMAND_LENGTH]; + AtCommandFunc func; + char* at_groups;/* quick @commands "can-use" lookup */ + char* char_groups;/* quick @charcommands "can-use" lookup */ +}; + +struct AliasInfo { + AtCommandInfo *command; + char alias[ATCOMMAND_LENGTH]; +}; + + +char atcommand_symbol = '@'; // first char of the commands +char charcommand_symbol = '#'; + +static char* msg_table[MAX_MSG]; // Server messages (0-499 reserved for GM commands, 500-999 reserved for others) +static DBMap* atcommand_db = NULL; //name -> AtCommandInfo +static DBMap* atcommand_alias_db = NULL; //alias -> AtCommandInfo +static config_t atcommand_config; + +static char atcmd_output[CHAT_SIZE_MAX]; +static char atcmd_player_name[NAME_LENGTH]; + +static AtCommandInfo* get_atcommandinfo_byname(const char *name); // @help +static const char* atcommand_checkalias(const char *aliasname); // @help +static void atcommand_get_suggestions(struct map_session_data* sd, const char *name, bool atcommand); // @help + +// @commands (script-based) +struct atcmd_binding_data* get_atcommandbind_byname(const char* name) { + int i = 0; + + if( *name == atcommand_symbol || *name == charcommand_symbol ) + name++; // for backwards compatibility + + ARR_FIND( 0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command, name) == 0 ); + + return ( i < atcmd_binding_count ) ? atcmd_binding[i] : NULL; +} + +//----------------------------------------------------------- +// Return the message string of the specified number by [Yor] +//----------------------------------------------------------- +const char* msg_txt(int msg_number) +{ + if (msg_number >= 0 && msg_number < MAX_MSG && + msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0') + return msg_table[msg_number]; + + return "??"; +} + +/*========================================== + * Read Message Data + *------------------------------------------*/ +int msg_config_read(const char* cfgName) +{ + int msg_number; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + static int called = 1; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("Messages file not found: %s\n", cfgName); + return 1; + } + + if ((--called) == 0) + memset(msg_table, 0, sizeof(msg_table[0]) * MAX_MSG); + + while(fgets(line, sizeof(line), fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "import") == 0) + msg_config_read(w2); + else + { + msg_number = atoi(w1); + if (msg_number >= 0 && msg_number < MAX_MSG) + { + if (msg_table[msg_number] != NULL) + aFree(msg_table[msg_number]); + msg_table[msg_number] = (char *)aMalloc((strlen(w2) + 1)*sizeof (char)); + strcpy(msg_table[msg_number],w2); + } + } + } + + fclose(fp); + + return 0; +} + +/*========================================== + * Cleanup Message Data + *------------------------------------------*/ +void do_final_msg(void) +{ + int i; + for (i = 0; i < MAX_MSG; i++) + aFree(msg_table[i]); +} + +/** + * retrieves the help string associated with a given command. + * + * @param name the name of the command to retrieve help information for + * @return the string associated with the command, or NULL + */ +static const char* atcommand_help_string(const char* command) +{ + const char* str = NULL; + config_setting_t* info; + + if( *command == atcommand_symbol || *command == charcommand_symbol ) + {// remove the prefix symbol for the raw name of the command + command ++; + } + + // convert alias to the real command name + command = atcommand_checkalias(command); + + // attept to find the first default help command + info = config_lookup(&atcommand_config, "help"); + + if( info == NULL ) + {// failed to find the help property in the configuration file + return NULL; + } + + if( !config_setting_lookup_string( info, command, &str ) ) + {// failed to find the matching help string + return NULL; + } + + // push the result from the method + return str; +} + + +/*========================================== + * @send (used for testing packet sends from the client) + *------------------------------------------*/ +ACMD_FUNC(send) +{ + int len=0,off,end,type; + long num; + + // read message type as hex number (without the 0x) + if(!message || !*message || + !((sscanf(message, "len %x", &type)==1 && (len=1)) + || sscanf(message, "%x", &type)==1) ) + { + int i; + for (i = 900; i <= 903; ++i) + clif_displaymessage(fd, msg_txt(i)); + return -1; + } + +#define PARSE_ERROR(error,p) \ + {\ + clif_displaymessage(fd, (error));\ + sprintf(atcmd_output, ">%s", (p));\ + clif_displaymessage(fd, atcmd_output);\ + } +//define PARSE_ERROR + +#define CHECK_EOS(p) \ + if(*(p) == 0){\ + clif_displaymessage(fd, "Unexpected end of string");\ + return -1;\ + } +//define CHECK_EOS + +#define SKIP_VALUE(p) \ + {\ + while(*(p) && !ISSPACE(*(p))) ++(p); /* non-space */\ + while(*(p) && ISSPACE(*(p))) ++(p); /* space */\ + } +//define SKIP_VALUE + +#define GET_VALUE(p,num) \ + {\ + if(sscanf((p), "x%lx", &(num)) < 1 && sscanf((p), "%ld ", &(num)) < 1){\ + PARSE_ERROR("Invalid number in:",(p));\ + return -1;\ + }\ + } +//define GET_VALUE + + if (type > 0 && type < MAX_PACKET_DB) { + + if(len) + {// show packet length + sprintf(atcmd_output, msg_txt(904), type, packet_db[sd->packet_ver][type].len); // Packet 0x%x length: %d + clif_displaymessage(fd, atcmd_output); + return 0; + } + + len=packet_db[sd->packet_ver][type].len; + off=2; + if(len == 0) + {// unknown packet - ERROR + sprintf(atcmd_output, msg_txt(905), type); // Unknown packet: 0x%x + clif_displaymessage(fd, atcmd_output); + return -1; + } else if(len == -1) + {// dynamic packet + len=SHRT_MAX-4; // maximum length + off=4; + } + WFIFOHEAD(fd, len); + WFIFOW(fd,0)=TOW(type); + + // parse packet contents + SKIP_VALUE(message); + while(*message != 0 && off < len){ + if(ISDIGIT(*message) || *message == '-' || *message == '+') + {// default (byte) + GET_VALUE(message,num); + WFIFOB(fd,off)=TOB(num); + ++off; + } else if(TOUPPER(*message) == 'B') + {// byte + ++message; + GET_VALUE(message,num); + WFIFOB(fd,off)=TOB(num); + ++off; + } else if(TOUPPER(*message) == 'W') + {// word (2 bytes) + ++message; + GET_VALUE(message,num); + WFIFOW(fd,off)=TOW(num); + off+=2; + } else if(TOUPPER(*message) == 'L') + {// long word (4 bytes) + ++message; + GET_VALUE(message,num); + WFIFOL(fd,off)=TOL(num); + off+=4; + } else if(TOUPPER(*message) == 'S') + {// string - escapes are valid + // get string length - num <= 0 means not fixed length (default) + ++message; + if(*message == '"'){ + num=0; + } else { + GET_VALUE(message,num); + while(*message != '"') + {// find start of string + if(*message == 0 || ISSPACE(*message)){ + PARSE_ERROR(msg_txt(906),message); // Not a string: + return -1; + } + ++message; + } + } + + // parse string + ++message; + CHECK_EOS(message); + end=(num<=0? 0: min(off+((int)num),len)); + for(; *message != '"' && (off < end || end == 0); ++off){ + if(*message == '\\'){ + ++message; + CHECK_EOS(message); + switch(*message){ + case 'a': num=0x07; break; // Bell + case 'b': num=0x08; break; // Backspace + case 't': num=0x09; break; // Horizontal tab + case 'n': num=0x0A; break; // Line feed + case 'v': num=0x0B; break; // Vertical tab + case 'f': num=0x0C; break; // Form feed + case 'r': num=0x0D; break; // Carriage return + case 'e': num=0x1B; break; // Escape + default: num=*message; break; + case 'x': // Hexadecimal + { + ++message; + CHECK_EOS(message); + if(!ISXDIGIT(*message)){ + PARSE_ERROR(msg_txt(907),message); // Not a hexadecimal digit: + return -1; + } + num=(ISDIGIT(*message)?*message-'0':TOLOWER(*message)-'a'+10); + if(ISXDIGIT(*message)){ + ++message; + CHECK_EOS(message); + num<<=8; + num+=(ISDIGIT(*message)?*message-'0':TOLOWER(*message)-'a'+10); + } + WFIFOB(fd,off)=TOB(num); + ++message; + CHECK_EOS(message); + continue; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': // Octal + { + num=*message-'0'; // 1st octal digit + ++message; + CHECK_EOS(message); + if(ISDIGIT(*message) && *message < '8'){ + num<<=3; + num+=*message-'0'; // 2nd octal digit + ++message; + CHECK_EOS(message); + if(ISDIGIT(*message) && *message < '8'){ + num<<=3; + num+=*message-'0'; // 3rd octal digit + ++message; + CHECK_EOS(message); + } + } + WFIFOB(fd,off)=TOB(num); + continue; + } + } + } else + num=*message; + WFIFOB(fd,off)=TOB(num); + ++message; + CHECK_EOS(message); + }//for + while(*message != '"') + {// ignore extra characters + ++message; + CHECK_EOS(message); + } + + // terminate the string + if(off < end) + {// fill the rest with 0's + memset(WFIFOP(fd,off),0,end-off); + off=end; + } + } else + {// unknown + PARSE_ERROR(msg_txt(908),message); // Unknown type of value in: + return -1; + } + SKIP_VALUE(message); + } + + if(packet_db[sd->packet_ver][type].len == -1) + {// send dynamic packet + WFIFOW(fd,2)=TOW(off); + WFIFOSET(fd,off); + } else + {// send static packet + if(off < len) + memset(WFIFOP(fd,off),0,len-off); + WFIFOSET(fd,len); + } + } else { + clif_displaymessage(fd, msg_txt(259)); // Invalid packet + return -1; + } + sprintf (atcmd_output, msg_txt(258), type, type); // Sent packet 0x%x (%d) + clif_displaymessage(fd, atcmd_output); + return 0; +#undef PARSE_ERROR +#undef CHECK_EOS +#undef SKIP_VALUE +#undef GET_VALUE +} + +/*========================================== + * @rura, @warp, @mapmove + *------------------------------------------*/ +ACMD_FUNC(mapmove) +{ + char map_name[MAP_NAME_LENGTH_EXT]; + unsigned short mapindex; + short x = 0, y = 0; + int16 m = -1; + + nullpo_retr(-1, sd); + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || + (sscanf(message, "%15s %hd %hd", map_name, &x, &y) < 3 && + sscanf(message, "%15[^,],%hd,%hd", map_name, &x, &y) < 1)) { + + clif_displaymessage(fd, msg_txt(909)); // Please enter a map (usage: @warp/@rura/@mapmove <mapname> <x> <y>). + return -1; + } + + mapindex = mapindex_name2id(map_name); + if (mapindex) + m = map_mapindex2mapid(mapindex); + + if (!mapindex) { // m < 0 means on different server! [Kevin] + clif_displaymessage(fd, msg_txt(1)); // Map not found. + return -1; + } + + if ((x || y) && map_getcell(m, x, y, CELL_CHKNOPASS)) + { //This is to prevent the pc_setpos call from printing an error. + clif_displaymessage(fd, msg_txt(2)); + if (!map_search_freecell(NULL, m, &x, &y, 10, 10, 1)) + x = y = 0; //Invalid cell, use random spot. + } + if (map[m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(247)); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(248)); + return -1; + } + if (pc_setpos(sd, mapindex, x, y, CLR_TELEPORT) != 0) { + clif_displaymessage(fd, msg_txt(1)); // Map not found. + return -1; + } + + clif_displaymessage(fd, msg_txt(0)); // Warped. + return 0; +} + +/*========================================== + * Displays where a character is. Corrected version by Silent. [Skotlex] + *------------------------------------------*/ +ACMD_FUNC(where) +{ + struct map_session_data* pl_sd; + + nullpo_retr(-1, sd); + memset(atcmd_player_name, '\0', sizeof atcmd_player_name); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(910)); // Please enter a player name (usage: @where <char name>). + return -1; + } + + pl_sd = map_nick2sd(atcmd_player_name); + if (pl_sd == NULL || + strncmp(pl_sd->status.name, atcmd_player_name, NAME_LENGTH) != 0 || + (pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) && pc_get_group_level(pl_sd) > pc_get_group_level(sd) && !pc_has_permission(sd, PC_PERM_WHO_DISPLAY_AID)) + ) { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + snprintf(atcmd_output, sizeof atcmd_output, "%s %s %d %d", pl_sd->status.name, mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(jumpto) +{ + struct map_session_data *pl_sd = NULL; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(911)); // Please enter a player name (usage: @jumpto/@warpto/@goto <char name/ID>). + return -1; + } + + if((pl_sd=map_nick2sd((char *)message)) == NULL && (pl_sd=map_charid2sd(atoi(message))) == NULL) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) + { + clif_displaymessage(fd, msg_txt(247)); // You are not authorized to warp to this map. + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) + { + clif_displaymessage(fd, msg_txt(248)); // You are not authorized to warp from your current map. + return -1; + } + + if( pc_isdead(sd) ) + { + clif_displaymessage(fd, msg_txt(664)); + return -1; + } + + pc_setpos(sd, pl_sd->mapindex, pl_sd->bl.x, pl_sd->bl.y, CLR_TELEPORT); + sprintf(atcmd_output, msg_txt(4), pl_sd->status.name); // Jumped to %s + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(jump) +{ + short x = 0, y = 0; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + sscanf(message, "%hd %hd", &x, &y); + + if (map[sd->bl.m].flag.noteleport && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(248)); // You are not authorized to warp from your current map. + return -1; + } + + if( pc_isdead(sd) ) + { + clif_displaymessage(fd, msg_txt(664)); + return -1; + } + + if ((x || y) && map_getcell(sd->bl.m, x, y, CELL_CHKNOPASS)) + { //This is to prevent the pc_setpos call from printing an error. + clif_displaymessage(fd, msg_txt(2)); + if (!map_search_freecell(NULL, sd->bl.m, &x, &y, 10, 10, 1)) + x = y = 0; //Invalid cell, use random spot. + } + + pc_setpos(sd, sd->mapindex, x, y, CLR_TELEPORT); + sprintf(atcmd_output, msg_txt(5), sd->bl.x, sd->bl.y); // Jumped to %d %d + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * Display list of online characters with + * various info. + *------------------------------------------*/ +ACMD_FUNC(who) +{ + struct map_session_data *pl_sd = NULL; + struct s_mapiterator *iter = NULL; + char map_name[MAP_NAME_LENGTH_EXT] = ""; + char player_name[NAME_LENGTH] = ""; + int count = 0; + int level = 0; + StringBuf buf; + /** + * 1 = @who : Player name, [Title], [Party name], [Guild name] + * 2 = @who2 : Player name, [Title], BLvl, JLvl, Job + * 3 = @who3 : [CID/AID] Player name [Title], Map, X, Y + */ + int display_type = 1; + int map_id = -1; + + nullpo_retr(-1, sd); + + if (strstr(command, "map") != NULL) { + if (sscanf(message, "%15s %23s", map_name, player_name) < 1 || (map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } else { + sscanf(message, "%23s", player_name); + } + + if (strstr(command, "2") != NULL) + display_type = 2; + else if (strstr(command, "3") != NULL) + display_type = 3; + + level = pc_get_group_level(sd); + StringBuf_Init(&buf); + + iter = mapit_getallusers(); + for (pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter)) { + if (!((pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) || (pl_sd->sc.option & OPTION_INVISIBLE)) && pc_get_group_level(pl_sd) > level)) { // you can look only lower or same level + if (stristr(pl_sd->status.name, player_name) == NULL // search with no case sensitive + || (map_id >= 0 && pl_sd->bl.m != map_id)) + continue; + switch (display_type) { + case 2: { + StringBuf_Printf(&buf, msg_txt(343), pl_sd->status.name); // "Name: %s " + if (pc_get_group_id(pl_sd) > 0) // Player title, if exists + StringBuf_Printf(&buf, msg_txt(344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) " + StringBuf_Printf(&buf, msg_txt(347), pl_sd->status.base_level, pl_sd->status.job_level, + job_name(pl_sd->status.class_)); // "| Lv:%d/%d | Job: %s" + break; + } + case 3: { + if (pc_has_permission(sd, PC_PERM_WHO_DISPLAY_AID)) + StringBuf_Printf(&buf, msg_txt(912), pl_sd->status.char_id, pl_sd->status.account_id); // "(CID:%d/AID:%d) " + StringBuf_Printf(&buf, msg_txt(343), pl_sd->status.name); // "Name: %s " + if (pc_get_group_id(pl_sd) > 0) // Player title, if exists + StringBuf_Printf(&buf, msg_txt(344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) " + StringBuf_Printf(&buf, msg_txt(348), mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); // "| Location: %s %d %d" + break; + } + default: { + struct party_data *p = party_search(pl_sd->status.party_id); + struct guild *g = guild_search(pl_sd->status.guild_id); + + StringBuf_Printf(&buf, msg_txt(343), pl_sd->status.name); // "Name: %s " + if (pc_get_group_id(pl_sd) > 0) // Player title, if exists + StringBuf_Printf(&buf, msg_txt(344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) " + if (p != NULL) + StringBuf_Printf(&buf, msg_txt(345), p->party.name); // " | Party: '%s'" + if (g != NULL) + StringBuf_Printf(&buf, msg_txt(346), g->name); // " | Guild: '%s'" + break; + } + } + clif_displaymessage(fd, StringBuf_Value(&buf)); + StringBuf_Clear(&buf); + count++; + } + } + mapit_free(iter); + + if (map_id < 0) { + if (count == 0) + StringBuf_Printf(&buf, msg_txt(28)); // No player found. + else if (count == 1) + StringBuf_Printf(&buf, msg_txt(29)); // 1 player found. + else + StringBuf_Printf(&buf, msg_txt(30), count); // %d players found. + } else { + if (count == 0) + StringBuf_Printf(&buf, msg_txt(54), map[map_id].name); // No player found in map '%s'. + else if (count == 1) + StringBuf_Printf(&buf, msg_txt(55), map[map_id].name); // 1 player found in map '%s'. + else + StringBuf_Printf(&buf, msg_txt(56), count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(whogm) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + int j, count; + int pl_level, level; + char match_text[CHAT_SIZE_MAX]; + char player_name[NAME_LENGTH]; + struct guild *g; + struct party_data *p; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%199[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = TOLOWER(match_text[j]); + + count = 0; + level = pc_get_group_level(sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + pl_level = pc_get_group_level(pl_sd); + if (!pl_level) + continue; + + if (match_text[0]) + { + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = TOLOWER(player_name[j]); + // search with no case sensitive + if (strstr(player_name, match_text) == NULL) + continue; + } + if (pl_level > level) { + if (pl_sd->sc.option & OPTION_INVISIBLE) + continue; + sprintf(atcmd_output, msg_txt(913), pl_sd->status.name); // Name: %s (GM) + clif_displaymessage(fd, atcmd_output); + count++; + continue; + } + + sprintf(atcmd_output, msg_txt(914), // Name: %s (GM:%d) | Location: %s %d %d + pl_sd->status.name, pl_level, + mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + + sprintf(atcmd_output, msg_txt(915), // BLvl: %d | Job: %s (Lvl: %d) + pl_sd->status.base_level, + job_name(pl_sd->status.class_), pl_sd->status.job_level); + clif_displaymessage(fd, atcmd_output); + + p = party_search(pl_sd->status.party_id); + g = guild_search(pl_sd->status.guild_id); + + sprintf(atcmd_output,msg_txt(916), // Party: '%s' | Guild: '%s' + p?p->party.name:msg_txt(917), g?g->name:msg_txt(917)); // None. + + clif_displaymessage(fd, atcmd_output); + count++; + } + mapit_free(iter); + + if (count == 0) + clif_displaymessage(fd, msg_txt(150)); // No GM found. + else if (count == 1) + clif_displaymessage(fd, msg_txt(151)); // 1 GM found. + else { + sprintf(atcmd_output, msg_txt(152), count); // %d GMs found. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(save) +{ + nullpo_retr(-1, sd); + + pc_setsavepoint(sd, sd->mapindex, sd->bl.x, sd->bl.y); + if (sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id, &sd->pd->pet); + + chrif_save(sd,0); + + clif_displaymessage(fd, msg_txt(6)); // Your save point has been changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(load) +{ + int16 m; + + nullpo_retr(-1, sd); + + m = map_mapindex2mapid(sd->status.save_point.map); + if (m >= 0 && map[m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(249)); // You are not authorized to warp to your save map. + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(248)); // You are not authorized to warp from your current map. + return -1; + } + + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_OUTSIGHT); + clif_displaymessage(fd, msg_txt(7)); // Warping to save point.. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(speed) +{ + int speed; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &speed) < 1) { + sprintf(atcmd_output, msg_txt(918), MIN_WALK_SPEED, MAX_WALK_SPEED); // Please enter a speed value (usage: @speed <%d-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + sd->base_status.speed = cap_value(speed, MIN_WALK_SPEED, MAX_WALK_SPEED); + status_calc_bl(&sd->bl, SCB_SPEED); + clif_displaymessage(fd, msg_txt(8)); // Speed changed. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(storage) +{ + nullpo_retr(-1, sd); + + if (sd->npc_id || sd->state.vending || sd->state.buyingstore || sd->state.trading || sd->state.storage_flag) + return -1; + + if (storage_storageopen(sd) == 1) + { //Already open. + clif_displaymessage(fd, msg_txt(250)); + return -1; + } + + clif_displaymessage(fd, msg_txt(919)); // Storage opened. + + return 0; +} + + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(guildstorage) +{ + nullpo_retr(-1, sd); + + if (!sd->status.guild_id) { + clif_displaymessage(fd, msg_txt(252)); + return -1; + } + + if (sd->npc_id || sd->state.vending || sd->state.buyingstore || sd->state.trading) + return -1; + + if (sd->state.storage_flag == 1) { + clif_displaymessage(fd, msg_txt(250)); + return -1; + } + + if (sd->state.storage_flag == 2) { + clif_displaymessage(fd, msg_txt(251)); + return -1; + } + + storage_guild_storageopen(sd); + clif_displaymessage(fd, msg_txt(920)); // Guild storage opened. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(option) +{ + int param1 = 0, param2 = 0, param3 = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d %d %d", ¶m1, ¶m2, ¶m3) < 1 || param1 < 0 || param2 < 0 || param3 < 0) + {// failed to match the parameters so inform the user of the options + const char* text = NULL; + + // attempt to find the setting information for this command + text = atcommand_help_string( command ); + + // notify the user of the requirement to enter an option + clif_displaymessage(fd, msg_txt(921)); // Please enter at least one option. + + if( text ) + {// send the help text associated with this command + clif_displaymessage( fd, text ); + } + + return -1; + } + + sd->sc.opt1 = param1; + sd->sc.opt2 = param2; + pc_setoption(sd, param3); + + clif_displaymessage(fd, msg_txt(9)); // Options changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(hide) +{ + nullpo_retr(-1, sd); + if (sd->sc.option & OPTION_INVISIBLE) { + sd->sc.option &= ~OPTION_INVISIBLE; + if (sd->disguise) + status_set_viewdata(&sd->bl, sd->disguise); + else + status_set_viewdata(&sd->bl, sd->status.class_); + clif_displaymessage(fd, msg_txt(10)); // Invisible: Off + + // increment the number of pvp players on the map + map[sd->bl.m].users_pvp++; + + if( map[sd->bl.m].flag.pvp && !map[sd->bl.m].flag.pvp_nocalcrank ) + {// register the player for ranking calculations + sd->pvp_timer = add_timer( gettick() + 200, pc_calc_pvprank_timer, sd->bl.id, 0 ); + } + //bugreport:2266 + map_foreachinmovearea(clif_insight, &sd->bl, AREA_SIZE, sd->bl.x, sd->bl.y, BL_ALL, &sd->bl); + } else { + sd->sc.option |= OPTION_INVISIBLE; + sd->vd.class_ = INVISIBLE_CLASS; + clif_displaymessage(fd, msg_txt(11)); // Invisible: On + + // decrement the number of pvp players on the map + map[sd->bl.m].users_pvp--; + + if( map[sd->bl.m].flag.pvp && !map[sd->bl.m].flag.pvp_nocalcrank && sd->pvp_timer != INVALID_TIMER ) + {// unregister the player for ranking + delete_timer( sd->pvp_timer, pc_calc_pvprank_timer ); + sd->pvp_timer = INVALID_TIMER; + } + } + clif_changeoption(&sd->bl); + + return 0; +} + +/*========================================== + * Changes a character's class + *------------------------------------------*/ +ACMD_FUNC(jobchange) +{ + int job = 0, upper = 0; + const char* text; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d %d", &job, &upper) < 1) { + int i, found = 0; + + for (i = JOB_NOVICE; i < JOB_MAX; ++i) { + if (strncmpi(message, job_name(i), 16) == 0) { + job = i; + upper = 0; + found = 1; + break; + } + } + + if (!found) { + text = atcommand_help_string(command); + if (text) + clif_displaymessage(fd, text); + return -1; + } + } + + if (job == JOB_KNIGHT2 || job == JOB_CRUSADER2 || job == JOB_WEDDING || job == JOB_XMAS || job == JOB_SUMMER + || job == JOB_LORD_KNIGHT2 || job == JOB_PALADIN2 || job == JOB_BABY_KNIGHT2 || job == JOB_BABY_CRUSADER2 || job == JOB_STAR_GLADIATOR2 + || (job >= JOB_RUNE_KNIGHT2 && job <= JOB_MECHANIC_T2) || (job >= JOB_BABY_RUNE2 && job <= JOB_BABY_MECHANIC2) + ) // Deny direct transformation into dummy jobs + {clif_displaymessage(fd, msg_txt(923)); //"You can not change to this job by command." + return 0;} + + if (pcdb_checkid(job)) + { + if (pc_jobchange(sd, job, upper) == 0) + clif_displaymessage(fd, msg_txt(12)); // Your job has been changed. + else { + clif_displaymessage(fd, msg_txt(155)); // You are unable to change your job. + return -1; + } + } else { + text = atcommand_help_string(command); + if (text) + clif_displaymessage(fd, text); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(kill) +{ + nullpo_retr(-1, sd); + status_kill(&sd->bl); + clif_displaymessage(sd->fd, msg_txt(13)); // A pity! You've died. + if (fd != sd->fd) + clif_displaymessage(fd, msg_txt(14)); // Character killed. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(alive) +{ + nullpo_retr(-1, sd); + if (!status_revive(&sd->bl, 100, 100)) + { + clif_displaymessage(fd, msg_txt(667)); + return -1; + } + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + clif_displaymessage(fd, msg_txt(16)); // You've been revived! It's a miracle! + return 0; +} + +/*========================================== + * +kamic [LuzZza] + *------------------------------------------*/ +ACMD_FUNC(kami) +{ + unsigned long color=0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if(*(command + 5) != 'c' && *(command + 5) != 'C') { + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(980)); // Please enter a message (usage: @kami <message>). + return -1; + } + + sscanf(message, "%199[^\n]", atcmd_output); + if (strstr(command, "l") != NULL) + clif_broadcast(&sd->bl, atcmd_output, strlen(atcmd_output) + 1, 0, ALL_SAMEMAP); + else + intif_broadcast(atcmd_output, strlen(atcmd_output) + 1, (*(command + 5) == 'b' || *(command + 5) == 'B') ? 0x10 : 0); + } else { + if(!message || !*message || (sscanf(message, "%lx %199[^\n]", &color, atcmd_output) < 2)) { + clif_displaymessage(fd, msg_txt(981)); // Please enter color and message (usage: @kamic <color> <message>). + return -1; + } + + if(color > 0xFFFFFF) { + clif_displaymessage(fd, msg_txt(982)); // Invalid color. + return -1; + } + intif_broadcast2(atcmd_output, strlen(atcmd_output) + 1, color, 0x190, 12, 0, 0); + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(heal) +{ + int hp = 0, sp = 0; // [Valaris] thanks to fov + nullpo_retr(-1, sd); + + sscanf(message, "%d %d", &hp, &sp); + + // some overflow checks + if( hp == INT_MIN ) hp++; + if( sp == INT_MIN ) sp++; + + if ( hp == 0 && sp == 0 ) { + if (!status_percent_heal(&sd->bl, 100, 100)) + clif_displaymessage(fd, msg_txt(157)); // HP and SP have already been recovered. + else + clif_displaymessage(fd, msg_txt(17)); // HP, SP recovered. + return 0; + } + + if ( hp > 0 && sp >= 0 ) { + if(!status_heal(&sd->bl, hp, sp, 0)) + clif_displaymessage(fd, msg_txt(157)); // HP and SP are already with the good value. + else + clif_displaymessage(fd, msg_txt(17)); // HP, SP recovered. + return 0; + } + + if ( hp < 0 && sp <= 0 ) { + status_damage(NULL, &sd->bl, -hp, -sp, 0, 0); + clif_damage(&sd->bl,&sd->bl, gettick(), 0, 0, -hp, 0, 4, 0); + clif_displaymessage(fd, msg_txt(156)); // HP or/and SP modified. + return 0; + } + + //Opposing signs. + if ( hp ) { + if (hp > 0) + status_heal(&sd->bl, hp, 0, 0); + else { + status_damage(NULL, &sd->bl, -hp, 0, 0, 0); + clif_damage(&sd->bl,&sd->bl, gettick(), 0, 0, -hp, 0, 4, 0); + } + } + + if ( sp ) { + if (sp > 0) + status_heal(&sd->bl, 0, sp, 0); + else + status_damage(NULL, &sd->bl, 0, -sp, 0, 0); + } + + clif_displaymessage(fd, msg_txt(156)); // HP or/and SP modified. + return 0; +} + +/*========================================== + * @item command (usage: @item <name/id_of_item> <quantity>) (modified by [Yor] for pet_egg) + *------------------------------------------*/ +ACMD_FUNC(item) +{ + char item_name[100]; + int number = 0, item_id, flag = 0; + struct item item_tmp; + struct item_data *item_data; + int get_count, i; + nullpo_retr(-1, sd); + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 && + sscanf(message, "%99s %d", item_name, &number) < 1 + )) { + clif_displaymessage(fd, msg_txt(983)); // Please enter an item name or ID (usage: @item <item name/ID> <quantity>). + return -1; + } + + if (number <= 0) + number = 1; + + if ((item_data = itemdb_searchname(item_name)) == NULL && + (item_data = itemdb_exists(atoi(item_name))) == NULL) + { + clif_displaymessage(fd, msg_txt(19)); // Invalid item ID or name. + return -1; + } + + item_id = item_data->nameid; + get_count = number; + //Check if it's stackable. + if (!itemdb_isstackable2(item_data)) + get_count = 1; + + for (i = 0; i < number; i += get_count) { + // if not pet egg + if (!pet_create_egg(sd, item_id)) { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = 1; + + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND))) + clif_additem(sd, 0, 0, flag); + } + } + + if (flag == 0) + clif_displaymessage(fd, msg_txt(18)); // Item created. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(item2) +{ + struct item item_tmp; + struct item_data *item_data; + char item_name[100]; + int item_id, number = 0; + int identify = 0, refine = 0, attr = 0; + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + int flag = 0; + int loop, get_count, i; + nullpo_retr(-1, sd); + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 && + sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 + )) { + clif_displaymessage(fd, msg_txt(984)); // Please enter all parameters (usage: @item2 <item name/ID> <quantity> + clif_displaymessage(fd, msg_txt(985)); // <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4>). + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + loop = 1; + get_count = number; + if (item_data->type == IT_WEAPON || item_data->type == IT_ARMOR || + item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) { + loop = number; + get_count = 1; + if (item_data->type == IT_PETEGG) { + identify = 1; + refine = 0; + } + if (item_data->type == IT_PETARMOR) + refine = 0; + if (refine > MAX_REFINE) + refine = MAX_REFINE; + } else { + identify = 1; + refine = attr = 0; + } + for (i = 0; i < loop; i++) { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = identify; + item_tmp.refine = refine; + item_tmp.attribute = attr; + item_tmp.card[0] = c1; + item_tmp.card[1] = c2; + item_tmp.card[2] = c3; + item_tmp.card[3] = c4; + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND))) + clif_additem(sd, 0, 0, flag); + } + + if (flag == 0) + clif_displaymessage(fd, msg_txt(18)); // Item created. + } else { + clif_displaymessage(fd, msg_txt(19)); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(itemreset) +{ + int i; + nullpo_retr(-1, sd); + + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount && sd->status.inventory[i].equip == 0) { + pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_COMMAND); + } + } + clif_displaymessage(fd, msg_txt(20)); // All of your items have been removed. + + return 0; +} + +/*========================================== + * Atcommand @lvlup + *------------------------------------------*/ +ACMD_FUNC(baselevelup) +{ + int level=0, i=0, status_point=0; + nullpo_retr(-1, sd); + level = atoi(message); + + if (!message || !*message || !level) { + clif_displaymessage(fd, msg_txt(986)); // Please enter a level adjustment (usage: @lvup/@blevel/@baselvlup <number of levels>). + return -1; + } + + if (level > 0) { + if (sd->status.base_level >= pc_maxbaselv(sd)) { // check for max level by Valaris + clif_displaymessage(fd, msg_txt(47)); // Base level can't go any higher. + return -1; + } // End Addition + if ((unsigned int)level > pc_maxbaselv(sd) || (unsigned int)level > pc_maxbaselv(sd) - sd->status.base_level) // fix positiv overflow + level = pc_maxbaselv(sd) - sd->status.base_level; + for (i = 0; i < level; i++) + status_point += pc_gets_status_point(sd->status.base_level + i); + + sd->status.status_point += status_point; + sd->status.base_level += (unsigned int)level; + status_percent_heal(&sd->bl, 100, 100); + clif_misceffect(&sd->bl, 0); + clif_displaymessage(fd, msg_txt(21)); // Base level raised. + } else { + if (sd->status.base_level == 1) { + clif_displaymessage(fd, msg_txt(158)); // Base level can't go any lower. + return -1; + } + level*=-1; + if ((unsigned int)level >= sd->status.base_level) + level = sd->status.base_level-1; + for (i = 0; i > -level; i--) + status_point += pc_gets_status_point(sd->status.base_level + i - 1); + if (sd->status.status_point < status_point) + pc_resetstate(sd); + if (sd->status.status_point < status_point) + sd->status.status_point = 0; + else + sd->status.status_point -= status_point; + sd->status.base_level -= (unsigned int)level; + clif_displaymessage(fd, msg_txt(22)); // Base level lowered. + } + sd->status.base_exp = 0; + clif_updatestatus(sd, SP_STATUSPOINT); + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_BASEEXP); + clif_updatestatus(sd, SP_NEXTBASEEXP); + status_calc_pc(sd, 0); + pc_baselevelchanged(sd); + if(sd->status.party_id) + party_send_levelup(sd); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(joblevelup) +{ + int level=0; + nullpo_retr(-1, sd); + + level = atoi(message); + + if (!message || !*message || !level) { + clif_displaymessage(fd, msg_txt(987)); // Please enter a level adjustment (usage: @joblvup/@jlevel/@joblvlup <number of levels>). + return -1; + } + if (level > 0) { + if (sd->status.job_level >= pc_maxjoblv(sd)) { + clif_displaymessage(fd, msg_txt(23)); // Job level can't go any higher. + return -1; + } + if ((unsigned int)level > pc_maxjoblv(sd) || (unsigned int)level > pc_maxjoblv(sd) - sd->status.job_level) // fix positiv overflow + level = pc_maxjoblv(sd) - sd->status.job_level; + sd->status.job_level += (unsigned int)level; + sd->status.skill_point += level; + clif_misceffect(&sd->bl, 1); + clif_displaymessage(fd, msg_txt(24)); // Job level raised. + } else { + if (sd->status.job_level == 1) { + clif_displaymessage(fd, msg_txt(159)); // Job level can't go any lower. + return -1; + } + level *=-1; + if ((unsigned int)level >= sd->status.job_level) // fix negativ overflow + level = sd->status.job_level-1; + sd->status.job_level -= (unsigned int)level; + if (sd->status.skill_point < level) + pc_resetskill(sd,0); //Reset skills since we need to substract more points. + if (sd->status.skill_point < level) + sd->status.skill_point = 0; + else + sd->status.skill_point -= level; + clif_displaymessage(fd, msg_txt(25)); // Job level lowered. + } + sd->status.job_exp = 0; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_JOBEXP); + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_SKILLPOINT); + status_calc_pc(sd, 0); + + return 0; +} + +/*========================================== + * @help + *------------------------------------------*/ +ACMD_FUNC(help) +{ + config_setting_t *help; + const char *text = NULL; + const char *command_name = NULL; + char *default_command = "help"; + + nullpo_retr(-1, sd); + + help = config_lookup(&atcommand_config, "help"); + if (help == NULL) { + clif_displaymessage(fd, msg_txt(27)); // "Commands help is not available." + return -1; + } + + if (!message || !*message) { + command_name = default_command; // If no command_name specified, display help for @help. + } else { + if (*message == atcommand_symbol || *message == charcommand_symbol) + ++message; + command_name = atcommand_checkalias(message); + } + + if (!pc_can_use_command(sd, command_name, COMMAND_ATCOMMAND)) { + sprintf(atcmd_output, msg_txt(153), message); // "%s is Unknown Command" + clif_displaymessage(fd, atcmd_output); + atcommand_get_suggestions(sd, command_name, true); + return -1; + } + + if (!config_setting_lookup_string(help, command_name, &text)) { + sprintf(atcmd_output, msg_txt(988), atcommand_symbol, command_name); // There is no help for %c%s. + clif_displaymessage(fd, atcmd_output); + atcommand_get_suggestions(sd, command_name, true); + return -1; + } + + sprintf(atcmd_output, msg_txt(989), atcommand_symbol, command_name); // Help for command %c%s: + clif_displaymessage(fd, atcmd_output); + + { // Display aliases + DBIterator* iter; + AtCommandInfo *command_info; + AliasInfo *alias_info = NULL; + StringBuf buf; + bool has_aliases = false; + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, msg_txt(990)); // Available aliases: + command_info = get_atcommandinfo_byname(command_name); + iter = db_iterator(atcommand_alias_db); + for (alias_info = dbi_first(iter); dbi_exists(iter); alias_info = dbi_next(iter)) { + if (alias_info->command == command_info) { + StringBuf_Printf(&buf, " %s", alias_info->alias); + has_aliases = true; + } + } + dbi_destroy(iter); + if (has_aliases) + clif_displaymessage(fd, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + } + + // Display help contents + clif_displaymessage(fd, text); + return 0; +} + +// helper function, used in foreach calls to stop auto-attack timers +// parameter: '0' - everyone, 'id' - only those attacking someone with that id +static int atcommand_stopattack(struct block_list *bl,va_list ap) +{ + struct unit_data *ud = unit_bl2ud(bl); + int id = va_arg(ap, int); + if (ud && ud->attacktimer != INVALID_TIMER && (!id || id == ud->target)) + { + unit_stop_attack(bl); + return 1; + } + return 0; +} +/*========================================== + * + *------------------------------------------*/ +static int atcommand_pvpoff_sub(struct block_list *bl,va_list ap) +{ + TBL_PC* sd = (TBL_PC*)bl; + clif_pvpset(sd, 0, 0, 2); + if (sd->pvp_timer != INVALID_TIMER) { + delete_timer(sd->pvp_timer, pc_calc_pvprank_timer); + sd->pvp_timer = INVALID_TIMER; + } + return 0; +} + +ACMD_FUNC(pvpoff) +{ + nullpo_retr(-1, sd); + + if (!map[sd->bl.m].flag.pvp) { + clif_displaymessage(fd, msg_txt(160)); // PvP is already Off. + return -1; + } + + map[sd->bl.m].flag.pvp = 0; + + if (!battle_config.pk_mode) + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_NOTHING); + map_foreachinmap(atcommand_pvpoff_sub,sd->bl.m, BL_PC); + map_foreachinmap(atcommand_stopattack,sd->bl.m, BL_CHAR, 0); + clif_displaymessage(fd, msg_txt(31)); // PvP: Off. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int atcommand_pvpon_sub(struct block_list *bl,va_list ap) +{ + TBL_PC* sd = (TBL_PC*)bl; + if (sd->pvp_timer == INVALID_TIMER) { + sd->pvp_timer = add_timer(gettick() + 200, pc_calc_pvprank_timer, sd->bl.id, 0); + sd->pvp_rank = 0; + sd->pvp_lastusers = 0; + sd->pvp_point = 5; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + return 0; +} + +ACMD_FUNC(pvpon) +{ + nullpo_retr(-1, sd); + + if (map[sd->bl.m].flag.pvp) { + clif_displaymessage(fd, msg_txt(161)); // PvP is already On. + return -1; + } + + map[sd->bl.m].flag.pvp = 1; + + if (!battle_config.pk_mode) + {// display pvp circle and rank + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_FREEPVPZONE); + map_foreachinmap(atcommand_pvpon_sub,sd->bl.m, BL_PC); + } + + clif_displaymessage(fd, msg_txt(32)); // PvP: On. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(gvgoff) +{ + nullpo_retr(-1, sd); + + if (!map[sd->bl.m].flag.gvg) { + clif_displaymessage(fd, msg_txt(162)); // GvG is already Off. + return -1; + } + + map[sd->bl.m].flag.gvg = 0; + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_NOTHING); + map_foreachinmap(atcommand_stopattack,sd->bl.m, BL_CHAR, 0); + clif_displaymessage(fd, msg_txt(33)); // GvG: Off. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(gvgon) +{ + nullpo_retr(-1, sd); + + if (map[sd->bl.m].flag.gvg) { + clif_displaymessage(fd, msg_txt(163)); // GvG is already On. + return -1; + } + + map[sd->bl.m].flag.gvg = 1; + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_AGITZONE); + clif_displaymessage(fd, msg_txt(34)); // GvG: On. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(model) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d %d %d", &hair_style, &hair_color, &cloth_color) < 1) { + sprintf(atcmd_output, msg_txt(991), // Please enter at least one value (usage: @model <hair ID: %d-%d> <hair color: %d-%d> <clothes color: %d-%d>). + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + pc_changelook(sd, LOOK_HAIR, hair_style); + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_txt(36)); // Appearence changed. + } else { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @dye && @ccolor + *------------------------------------------*/ +ACMD_FUNC(dye) +{ + int cloth_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &cloth_color) < 1) { + sprintf(atcmd_output, msg_txt(992), MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); // Please enter a clothes color (usage: @dye/@ccolor <clothes color: %d-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_txt(36)); // Appearence changed. + } else { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @hairstyle && @hstyle + *------------------------------------------*/ +ACMD_FUNC(hair_style) +{ + int hair_style = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &hair_style) < 1) { + sprintf(atcmd_output, msg_txt(993), MIN_HAIR_STYLE, MAX_HAIR_STYLE); // Please enter a hair style (usage: @hairstyle/@hstyle <hair ID: %d-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE) { + pc_changelook(sd, LOOK_HAIR, hair_style); + clif_displaymessage(fd, msg_txt(36)); // Appearence changed. + } else { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @haircolor && @hcolor + *------------------------------------------*/ +ACMD_FUNC(hair_color) +{ + int hair_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &hair_color) < 1) { + sprintf(atcmd_output, msg_txt(994), MIN_HAIR_COLOR, MAX_HAIR_COLOR); // Please enter a hair color (usage: @haircolor/@hcolor <hair color: %d-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR) { + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + clif_displaymessage(fd, msg_txt(36)); // Appearence changed. + } else { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @go [city_number or city_name] - Updated by Harbin + *------------------------------------------*/ +ACMD_FUNC(go) +{ + int i; + int town; + char map_name[MAP_NAME_LENGTH]; + int16 m; + + const struct { + char map[MAP_NAME_LENGTH]; + int x, y; + } data[] = { + { MAP_PRONTERA, 156, 191 }, // 0=Prontera + { MAP_MORROC, 156, 93 }, // 1=Morroc + { MAP_GEFFEN, 119, 59 }, // 2=Geffen + { MAP_PAYON, 162, 233 }, // 3=Payon + { MAP_ALBERTA, 192, 147 }, // 4=Alberta +#ifdef RENEWAL + { MAP_IZLUDE, 128, 146 }, // 5=Izlude (Renewal) +#else + { MAP_IZLUDE, 128, 114 }, // 5=Izlude +#endif + { MAP_ALDEBARAN, 140, 131 }, // 6=Al de Baran + { MAP_LUTIE, 147, 134 }, // 7=Lutie + { MAP_COMODO, 209, 143 }, // 8=Comodo + { MAP_YUNO, 157, 51 }, // 9=Yuno + { MAP_AMATSU, 198, 84 }, // 10=Amatsu + { MAP_GONRYUN, 160, 120 }, // 11=Gonryun + { MAP_UMBALA, 89, 157 }, // 12=Umbala + { MAP_NIFLHEIM, 21, 153 }, // 13=Niflheim + { MAP_LOUYANG, 217, 40 }, // 14=Louyang + { MAP_NOVICE, 53, 111 }, // 15=Training Grounds + { MAP_JAIL, 23, 61 }, // 16=Prison + { MAP_JAWAII, 249, 127 }, // 17=Jawaii + { MAP_AYOTHAYA, 151, 117 }, // 18=Ayothaya + { MAP_EINBROCH, 64, 200 }, // 19=Einbroch + { MAP_LIGHTHALZEN, 158, 92 }, // 20=Lighthalzen + { MAP_EINBECH, 70, 95 }, // 21=Einbech + { MAP_HUGEL, 96, 145 }, // 22=Hugel + { MAP_RACHEL, 130, 110 }, // 23=Rachel + { MAP_VEINS, 216, 123 }, // 24=Veins + { MAP_MOSCOVIA, 223, 184 }, // 25=Moscovia + { MAP_MIDCAMP, 180, 240 }, // 26=Midgard Camp + { MAP_MANUK, 282, 138 }, // 27=Manuk + { MAP_SPLENDIDE, 197, 176 }, // 28=Splendide + { MAP_BRASILIS, 182, 239 }, // 29=Brasilis + { MAP_DICASTES, 198, 187 }, // 30=El Dicastes + { MAP_MORA, 44, 151 }, // 31=Mora + { MAP_DEWATA, 200, 180 }, // 32=Dewata + { MAP_MALANGDO, 140, 114 }, // 33=Malangdo Island + { MAP_MALAYA, 242, 211 }, // 34=Malaya Port + { MAP_ECLAGE, 110, 39 }, // 35=Eclage + }; + + nullpo_retr(-1, sd); + + if( map[sd->bl.m].flag.nogo && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE) ) { + clif_displaymessage(sd->fd,msg_txt(995)); // You cannot use @go on this map. + return 0; + } + + memset(map_name, '\0', sizeof(map_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + // get the number + town = atoi(message); + + if (!message || !*message || sscanf(message, "%11s", map_name) < 1 || town < 0 || town >= ARRAYLENGTH(data)) + {// no value matched so send the list of locations + const char* text; + + // attempt to find the text help string + text = atcommand_help_string( command ); + + clif_displaymessage(fd, msg_txt(38)); // Invalid location number, or name. + + if( text ) + {// send the text to the client + clif_displaymessage( fd, text ); + } + + return -1; + } + + // get possible name of the city + map_name[MAP_NAME_LENGTH-1] = '\0'; + for (i = 0; map_name[i]; i++) + map_name[i] = TOLOWER(map_name[i]); + // try to identify the map name + if (strncmp(map_name, "prontera", 3) == 0) { + town = 0; + } else if (strncmp(map_name, "morocc", 4) == 0 || + strncmp(map_name, "morroc", 4) == 0) { + town = 1; + } else if (strncmp(map_name, "geffen", 3) == 0) { + town = 2; + } else if (strncmp(map_name, "payon", 3) == 0) { + town = 3; + } else if (strncmp(map_name, "alberta", 3) == 0) { + town = 4; + } else if (strncmp(map_name, "izlude", 3) == 0) { + town = 5; + } else if (strncmp(map_name, "aldebaran", 3) == 0) { + town = 6; + } else if (strncmp(map_name, "lutie", 3) == 0 || + strcmp(map_name, "christmas") == 0 || + strncmp(map_name, "xmas", 3) == 0 || + strncmp(map_name, "x-mas", 3) == 0) { + town = 7; + } else if (strncmp(map_name, "comodo", 3) == 0) { + town = 8; + } else if (strncmp(map_name, "juno", 3) == 0 || + strncmp(map_name, "yuno", 3) == 0) { + town = 9; + } else if (strncmp(map_name, "amatsu", 3) == 0) { + town = 10; + } else if (strncmp(map_name, "kunlun", 3) == 0 || + strncmp(map_name, "gonryun", 3) == 0) { + town = 11; + } else if (strncmp(map_name, "umbala", 3) == 0) { + town = 12; + } else if (strncmp(map_name, "niflheim", 3) == 0) { + town = 13; + } else if (strncmp(map_name, "louyang", 3) == 0) { + town = 14; + } else if (strncmp(map_name, "new_1-1", 3) == 0 || + strncmp(map_name, "startpoint", 3) == 0 || + strncmp(map_name, "beginning", 3) == 0) { + town = 15; + } else if (strncmp(map_name, "sec_pri", 3) == 0 || + strncmp(map_name, "prison", 3) == 0 || + strncmp(map_name, "jail", 3) == 0) { + town = 16; + } else if (strncmp(map_name, "jawaii", 3) == 0) { + town = 17; + } else if (strncmp(map_name, "ayothaya", 3) == 0) { + town = 18; + } else if (strncmp(map_name, "einbroch", 5) == 0) { + town = 19; + } else if (strncmp(map_name, "lighthalzen", 3) == 0) { + town = 20; + } else if (strncmp(map_name, "einbech", 5) == 0) { + town = 21; + } else if (strncmp(map_name, "hugel", 3) == 0) { + town = 22; + } else if (strncmp(map_name, "rachel", 3) == 0) { + town = 23; + } else if (strncmp(map_name, "veins", 3) == 0) { + town = 24; + } else if (strncmp(map_name, "moscovia", 3) == 0) { + town = 25; + } else if (strncmp(map_name, "mid_camp", 3) == 0) { + town = 26; + } else if (strncmp(map_name, "manuk", 3) == 0) { + town = 27; + } else if (strncmp(map_name, "splendide", 3) == 0) { + town = 28; + } else if (strncmp(map_name, "brasilis", 3) == 0) { + town = 29; + } else if (strncmp(map_name, "dicastes01", 3) == 0) { + town = 30; + } else if (strcmp(map_name, "mora") == 0) { + town = 31; + } else if (strncmp(map_name, "dewata", 3) == 0) { + town = 32; + } else if (strncmp(map_name, "malangdo", 5) == 0) { + town = 33; + } else if (strncmp(map_name, "malaya", 5) == 0) { + town = 34; + } else if (strncmp(map_name, "eclage", 3) == 0) { + town = 35; + } + + if (town >= 0 && town < ARRAYLENGTH(data)) + { + m = map_mapname2mapid(data[town].map); + if (m >= 0 && map[m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(247)); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(248)); + return -1; + } + if (pc_setpos(sd, mapindex_name2id(data[town].map), data[town].x, data[town].y, CLR_TELEPORT) == 0) { + clif_displaymessage(fd, msg_txt(0)); // Warped. + } else { + clif_displaymessage(fd, msg_txt(1)); // Map not found. + return -1; + } + } else { // if you arrive here, you have an error in town variable when reading of names + clif_displaymessage(fd, msg_txt(38)); // Invalid location number or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(monster) +{ + char name[NAME_LENGTH]; + char monster[NAME_LENGTH]; + char eventname[EVENT_NAME_LENGTH] = ""; + int mob_id; + int number = 0; + int count; + int i, k, range; + short mx, my; + unsigned int size; + nullpo_retr(-1, sd); + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(80)); // Give the display name or monster name/id please. + return -1; + } + if (sscanf(message, "\"%23[^\"]\" %23s %d", name, monster, &number) > 1 || + sscanf(message, "%23s \"%23[^\"]\" %d", monster, name, &number) > 1) { + //All data can be left as it is. + } else if ((count=sscanf(message, "%23s %d %23s", monster, &number, name)) > 1) { + //Here, it is possible name was not given and we are using monster for it. + if (count < 3) //Blank mob's name. + name[0] = '\0'; + } else if (sscanf(message, "%23s %23s %d", name, monster, &number) > 1) { + //All data can be left as it is. + } else if (sscanf(message, "%23s", monster) > 0) { + //As before, name may be already filled. + name[0] = '\0'; + } else { + clif_displaymessage(fd, msg_txt(80)); // Give a display name and monster name/id please. + return -1; + } + + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_txt(40)); // Invalid monster ID or name. + return -1; + } + + if (mob_id == MOBID_EMPERIUM) { + clif_displaymessage(fd, msg_txt(83)); // Monster 'Emperium' cannot be spawned. + return -1; + } + + if (number <= 0) + number = 1; + + if( !name[0] ) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (strcmp(command+1, "monstersmall") == 0) + size = SZ_MEDIUM; // This is just gorgeous [mkbu95] + else if (strcmp(command+1, "monsterbig") == 0) + size = SZ_BIG; + else + size = SZ_SMALL; + + if (battle_config.etc_log) + ShowInfo("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, sd->bl.x, sd->bl.y); + + count = 0; + range = (int)sqrt((float)number) +2; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + map_search_freecell(&sd->bl, 0, &mx, &my, range, range, 0); + k = mob_once_spawn(sd, sd->bl.m, mx, my, name, mob_id, 1, eventname, size, AI_NONE); + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_txt(39)); // All monster summoned! + else { + sprintf(atcmd_output, msg_txt(240), count); // %d monster(s) summoned! + clif_displaymessage(fd, atcmd_output); + } + else { + clif_displaymessage(fd, msg_txt(40)); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int atkillmonster_sub(struct block_list *bl, va_list ap) +{ + struct mob_data *md; + int flag; + + nullpo_ret(md=(struct mob_data *)bl); + flag = va_arg(ap, int); + + if (md->guardian_data) + return 0; //Do not touch WoE mobs! + + if (flag) + status_zap(bl,md->status.hp, 0); + else + status_kill(bl); + return 1; +} + +ACMD_FUNC(killmonster) +{ + int map_id, drop_flag; + char map_name[MAP_NAME_LENGTH_EXT]; + nullpo_retr(-1, sd); + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%15s", map_name) < 1) + map_id = sd->bl.m; + else { + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + drop_flag = strcmp(command+1, "killmonster2"); + + map_foreachinmap(atkillmonster_sub, map_id, BL_MOB, -drop_flag); + + clif_displaymessage(fd, msg_txt(165)); // All monsters killed! + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(refine) +{ + int i,j, position = 0, refine = 0, current_position, final_refine; + int count; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d %d", &position, &refine) < 2) { + clif_displaymessage(fd, msg_txt(996)); // Please enter a position and an amount (usage: @refine <equip position> <+/- amount>). + sprintf(atcmd_output, msg_txt(997), EQP_HEAD_LOW); // %d: Lower Headgear + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(998), EQP_HAND_R); // %d: Right Hand + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(999), EQP_GARMENT); // %d: Garment + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1000), EQP_ACC_L); // %d: Left Accessory + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1001), EQP_ARMOR); // %d: Body Armor + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1002), EQP_HAND_L); // %d: Left Hand + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1003), EQP_SHOES); // %d: Shoes + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1004), EQP_ACC_R); // %d: Right Accessory + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1005), EQP_HEAD_TOP); // %d: Top Headgear + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1006), EQP_HEAD_MID); // %d: Mid Headgear + clif_displaymessage(fd, atcmd_output); + return -1; + } + + refine = cap_value(refine, -MAX_REFINE, MAX_REFINE); + + count = 0; + for (j = 0; j < EQI_MAX-1; j++) { + if ((i = sd->equip_index[j]) < 0) + continue; + if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == i) + continue; + if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == i) + continue; + if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == i || sd->equip_index[EQI_HEAD_LOW] == i)) + continue; + + if(position && !(sd->status.inventory[i].equip & position)) + continue; + + final_refine = cap_value(sd->status.inventory[i].refine + refine, 0, MAX_REFINE); + if (sd->status.inventory[i].refine != final_refine) { + sd->status.inventory[i].refine = final_refine; + current_position = sd->status.inventory[i].equip; + pc_unequipitem(sd, i, 3); + clif_refine(fd, 0, i, sd->status.inventory[i].refine); + clif_delitem(sd, i, 1, 3); + clif_additem(sd, i, 1, 0); + pc_equipitem(sd, i, current_position); + clif_misceffect(&sd->bl, 3); + count++; + } + } + + if (count == 0) + clif_displaymessage(fd, msg_txt(166)); // No item has been refined. + else if (count == 1) + clif_displaymessage(fd, msg_txt(167)); // 1 item has been refined. + else { + sprintf(atcmd_output, msg_txt(168), count); // %d items have been refined. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(produce) +{ + char item_name[100]; + int item_id, attribute = 0, star = 0; + int flag = 0; + struct item_data *item_data; + struct item tmp_item; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d %d", item_name, &attribute, &star) < 1 && + sscanf(message, "%99s %d %d", item_name, &attribute, &star) < 1 + )) { + clif_displaymessage(fd, msg_txt(1007)); // Please enter at least one item name/ID (usage: @produce <equip name/ID> <element> <# of very's>). + return -1; + } + + if ( (item_data = itemdb_searchname(item_name)) == NULL && + (item_data = itemdb_exists(atoi(item_name))) == NULL ) { + clif_displaymessage(fd, msg_txt(170)); //This item is not an equipment. + return -1; + } + + item_id = item_data->nameid; + + if (itemdb_isequip2(item_data)) { + if (attribute < MIN_ATTRIBUTE || attribute > MAX_ATTRIBUTE) + attribute = ATTRIBUTE_NORMAL; + if (star < MIN_STAR || star > MAX_STAR) + star = 0; + memset(&tmp_item, 0, sizeof tmp_item); + tmp_item.nameid = item_id; + tmp_item.amount = 1; + tmp_item.identify = 1; + tmp_item.card[0] = CARD0_FORGE; + tmp_item.card[1] = item_data->type==IT_WEAPON? + ((star*5) << 8) + attribute:0; + tmp_item.card[2] = GetWord(sd->status.char_id, 0); + tmp_item.card[3] = GetWord(sd->status.char_id, 1); + clif_produceeffect(sd, 0, item_id); + clif_misceffect(&sd->bl, 3); + + if ((flag = pc_additem(sd, &tmp_item, 1, LOG_TYPE_COMMAND))) + clif_additem(sd, 0, 0, flag); + } else { + sprintf(atcmd_output, msg_txt(169), item_id, item_data->name); // The item (%d: '%s') is not equipable. + clif_displaymessage(fd, atcmd_output); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(memo) +{ + int position = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if( !message || !*message || sscanf(message, "%d", &position) < 1 ) + { + int i; + clif_displaymessage(sd->fd, msg_txt(668)); + for( i = 0; i < MAX_MEMOPOINTS; i++ ) + { + if( sd->status.memo_point[i].map ) + sprintf(atcmd_output, "%d - %s (%d,%d)", i, mapindex_id2name(sd->status.memo_point[i].map), sd->status.memo_point[i].x, sd->status.memo_point[i].y); + else + sprintf(atcmd_output, msg_txt(171), i); // %d - void + clif_displaymessage(sd->fd, atcmd_output); + } + return 0; + } + + if( position < 0 || position >= MAX_MEMOPOINTS ) + { + sprintf(atcmd_output, msg_txt(1008), 0, MAX_MEMOPOINTS-1); // Please enter a valid position (usage: @memo <memo_position:%d-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + pc_memo(sd, position); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(gat) +{ + int y; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + for (y = 2; y >= -2; y--) { + sprintf(atcmd_output, "%s (x= %d, y= %d) %02X %02X %02X %02X %02X", + map[sd->bl.m].name, sd->bl.x - 2, sd->bl.y + y, + map_getcell(sd->bl.m, sd->bl.x - 2, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x - 1, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x + 1, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x + 2, sd->bl.y + y, CELL_GETTYPE)); + + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(displaystatus) +{ + int i, type, flag, tick, val1 = 0, val2 = 0, val3 = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || (i = sscanf(message, "%d %d %d %d %d %d", &type, &flag, &tick, &val1, &val2, &val3)) < 1) { + clif_displaymessage(fd, msg_txt(1009)); // Please enter a status type/flag (usage: @displaystatus <status type> <flag> <tick> {<val1> {<val2> {<val3>}}}). + return -1; + } + if (i < 2) flag = 1; + if (i < 3) tick = 0; + + clif_status_change(&sd->bl, type, flag, tick, val1, val2, val3); + + return 0; +} + +/*========================================== + * @stpoint (Rewritten by [Yor]) + *------------------------------------------*/ +ACMD_FUNC(statuspoint) +{ + int point; + unsigned int new_status_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, msg_txt(1010)); // Please enter a number (usage: @stpoint <number of points>). + return -1; + } + + if(point < 0) + { + if(sd->status.status_point < (unsigned int)(-point)) + { + new_status_point = 0; + } + else + { + new_status_point = sd->status.status_point + point; + } + } + else if(UINT_MAX - sd->status.status_point < (unsigned int)point) + { + new_status_point = UINT_MAX; + } + else + { + new_status_point = sd->status.status_point + point; + } + + if (new_status_point != sd->status.status_point) { + sd->status.status_point = new_status_point; + clif_updatestatus(sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_txt(174)); // Number of status points changed. + } else { + if (point < 0) + clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + else + clif_displaymessage(fd, msg_txt(149)); // Unable to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @skpoint (Rewritten by [Yor]) + *------------------------------------------*/ +ACMD_FUNC(skillpoint) +{ + int point; + unsigned int new_skill_point; + nullpo_retr(-1, sd); + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, msg_txt(1011)); // Please enter a number (usage: @skpoint <number of points>). + return -1; + } + + if(point < 0) + { + if(sd->status.skill_point < (unsigned int)(-point)) + { + new_skill_point = 0; + } + else + { + new_skill_point = sd->status.skill_point + point; + } + } + else if(UINT_MAX - sd->status.skill_point < (unsigned int)point) + { + new_skill_point = UINT_MAX; + } + else + { + new_skill_point = sd->status.skill_point + point; + } + + if (new_skill_point != sd->status.skill_point) { + sd->status.skill_point = new_skill_point; + clif_updatestatus(sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_txt(175)); // Number of skill points changed. + } else { + if (point < 0) + clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + else + clif_displaymessage(fd, msg_txt(149)); // Unable to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @zeny + *------------------------------------------*/ +ACMD_FUNC(zeny) +{ + int zeny=0, ret=-1; + nullpo_retr(-1, sd); + + if (!message || !*message || (zeny = atoi(message)) == 0) { + clif_displaymessage(fd, msg_txt(1012)); // Please enter an amount (usage: @zeny <amount>). + return -1; + } + + if(zeny > 0){ + if((ret=pc_getzeny(sd,zeny,LOG_TYPE_COMMAND,NULL)) == 1) + clif_displaymessage(fd, msg_txt(149)); // Unable to increase the number/value. + } + else { + if( sd->status.zeny < -zeny ) zeny = -sd->status.zeny; + if((ret=pc_payzeny(sd,-zeny,LOG_TYPE_COMMAND,NULL)) == 1) + clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + } + if(!ret) clif_displaymessage(fd, msg_txt(176)); //ret=0 mean cmd success + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(param) +{ + int i, value = 0, new_value, max; + const char* param[] = { "str", "agi", "vit", "int", "dex", "luk" }; + short* status[6]; + //we don't use direct initialization because it isn't part of the c standard. + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) { + clif_displaymessage(fd, msg_txt(1013)); // Please enter a valid value (usage: @str/@agi/@vit/@int/@dex/@luk <+/-adjustment>). + return -1; + } + + ARR_FIND( 0, ARRAYLENGTH(param), i, strcmpi(command+1, param[i]) == 0 ); + + if( i == ARRAYLENGTH(param) || i > MAX_STATUS_TYPE) { // normally impossible... + clif_displaymessage(fd, msg_txt(1013)); // Please enter a valid value (usage: @str/@agi/@vit/@int/@dex/@luk <+/-adjustment>). + return -1; + } + + status[0] = &sd->status.str; + status[1] = &sd->status.agi; + status[2] = &sd->status.vit; + status[3] = &sd->status.int_; + status[4] = &sd->status.dex; + status[5] = &sd->status.luk; + + if( battle_config.atcommand_max_stat_bypass ) + max = SHRT_MAX; + else + max = pc_maxparameter(sd); + + if(value < 0 && *status[i] <= -value) { + new_value = 1; + } else if(max - *status[i] < value) { + new_value = max; + } else { + new_value = *status[i] + value; + } + + if (new_value != *status[i]) { + *status[i] = new_value; + clif_updatestatus(sd, SP_STR + i); + clif_updatestatus(sd, SP_USTR + i); + status_calc_pc(sd, 0); + clif_displaymessage(fd, msg_txt(42)); // Stat changed. + } else { + if (value < 0) + clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + else + clif_displaymessage(fd, msg_txt(149)); // Unable to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * Stat all by fritz (rewritten by [Yor]) + *------------------------------------------*/ +ACMD_FUNC(stat_all) +{ + int index, count, value, max, new_value; + short* status[6]; + //we don't use direct initialization because it isn't part of the c standard. + nullpo_retr(-1, sd); + + status[0] = &sd->status.str; + status[1] = &sd->status.agi; + status[2] = &sd->status.vit; + status[3] = &sd->status.int_; + status[4] = &sd->status.dex; + status[5] = &sd->status.luk; + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) { + value = pc_maxparameter(sd); + max = pc_maxparameter(sd); + } else { + if( battle_config.atcommand_max_stat_bypass ) + max = SHRT_MAX; + else + max = pc_maxparameter(sd); + } + + count = 0; + for (index = 0; index < ARRAYLENGTH(status); index++) { + + if (value > 0 && *status[index] > max - value) + new_value = max; + else if (value < 0 && *status[index] <= -value) + new_value = 1; + else + new_value = *status[index] +value; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + count++; + } + } + + if (count > 0) { // if at least 1 stat modified + status_calc_pc(sd, 0); + clif_displaymessage(fd, msg_txt(84)); // All stats changed! + } else { + if (value < 0) + clif_displaymessage(fd, msg_txt(177)); // You cannot decrease that stat anymore. + else + clif_displaymessage(fd, msg_txt(178)); // You cannot increase that stat anymore. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(guildlevelup) +{ + int level = 0; + short added_level; + struct guild *guild_info; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d", &level) < 1 || level == 0) { + clif_displaymessage(fd, msg_txt(1014)); // Please enter a valid level (usage: @guildlvup/@guildlvlup <# of levels>). + return -1; + } + + if (sd->status.guild_id <= 0 || (guild_info = guild_search(sd->status.guild_id)) == NULL) { + clif_displaymessage(fd, msg_txt(43)); // You're not in a guild. + return -1; + } + //if (strcmp(sd->status.name, guild_info->master) != 0) { + // clif_displaymessage(fd, msg_txt(44)); // You're not the master of your guild. + // return -1; + //} + + added_level = (short)level; + if (level > 0 && (level > MAX_GUILDLEVEL || added_level > ((short)MAX_GUILDLEVEL - guild_info->guild_lv))) // fix positiv overflow + added_level = (short)MAX_GUILDLEVEL - guild_info->guild_lv; + else if (level < 0 && (level < -MAX_GUILDLEVEL || added_level < (1 - guild_info->guild_lv))) // fix negativ overflow + added_level = 1 - guild_info->guild_lv; + + if (added_level != 0) { + intif_guild_change_basicinfo(guild_info->guild_id, GBI_GUILDLV, &added_level, sizeof(added_level)); + clif_displaymessage(fd, msg_txt(179)); // Guild level changed. + } else { + clif_displaymessage(fd, msg_txt(45)); // Guild level change failed. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(makeegg) +{ + struct item_data *item_data; + int id, pet_id; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1015)); // Please enter a monster/egg name/ID (usage: @makeegg <pet>). + return -1; + } + + if ((item_data = itemdb_searchname(message)) != NULL) // for egg name + id = item_data->nameid; + else + if ((id = mobdb_searchname(message)) != 0) // for monster name + ; + else + id = atoi(message); + + pet_id = search_petDB_index(id, PET_CLASS); + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class_; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } else { + clif_displaymessage(fd, msg_txt(180)); // The monster/egg name/id doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(hatch) +{ + nullpo_retr(-1, sd); + if (sd->status.pet_id <= 0) + clif_sendegg(sd); + else { + clif_displaymessage(fd, msg_txt(181)); // You already have a pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(petfriendly) +{ + int friendly; + struct pet_data *pd; + nullpo_retr(-1, sd); + + if (!message || !*message || (friendly = atoi(message)) < 0) { + clif_displaymessage(fd, msg_txt(1016)); // Please enter a valid value (usage: @petfriendly <0-1000>). + return -1; + } + + pd = sd->pd; + if (!pd) { + clif_displaymessage(fd, msg_txt(184)); // Sorry, but you have no pet. + return -1; + } + + if (friendly < 0 || friendly > 1000) + { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + + if (friendly == pd->pet.intimate) { + clif_displaymessage(fd, msg_txt(183)); // Pet intimacy is already at maximum. + return -1; + } + + pet_set_intimate(pd, friendly); + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_txt(182)); // Pet intimacy changed. + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(pethungry) +{ + int hungry; + struct pet_data *pd; + nullpo_retr(-1, sd); + + if (!message || !*message || (hungry = atoi(message)) < 0) { + clif_displaymessage(fd, msg_txt(1017)); // Please enter a valid number (usage: @pethungry <0-100>). + return -1; + } + + pd = sd->pd; + if (!sd->status.pet_id || !pd) { + clif_displaymessage(fd, msg_txt(184)); // Sorry, but you have no pet. + return -1; + } + if (hungry < 0 || hungry > 100) { + clif_displaymessage(fd, msg_txt(37)); // An invalid number was specified. + return -1; + } + if (hungry == pd->pet.hungry) { + clif_displaymessage(fd, msg_txt(186)); // Pet hunger is already at maximum. + return -1; + } + + pd->pet.hungry = hungry; + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_txt(185)); // Pet hunger changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(petrename) +{ + struct pet_data *pd; + nullpo_retr(-1, sd); + if (!sd->status.pet_id || !sd->pd) { + clif_displaymessage(fd, msg_txt(184)); // Sorry, but you have no pet. + return -1; + } + pd = sd->pd; + if (!pd->pet.rename_flag) { + clif_displaymessage(fd, msg_txt(188)); // You can already rename your pet. + return -1; + } + + pd->pet.rename_flag = 0; + intif_save_petdata(sd->status.account_id, &pd->pet); + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_txt(187)); // You can now rename your pet. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(recall) { + struct map_session_data *pl_sd = NULL; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1018)); // Please enter a player name (usage: @recall <char name/ID>). + return -1; + } + + if((pl_sd=map_nick2sd((char *)message)) == NULL && (pl_sd=map_charid2sd(atoi(message))) == NULL) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if ( pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + clif_displaymessage(fd, msg_txt(81)); // Your GM level doesn't authorize you to preform this action on the specified player. + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(1019)); // You are not authorized to warp someone to this map. + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(1020)); // You are not authorized to warp this player from their map. + return -1; + } + if (pl_sd->bl.m == sd->bl.m && pl_sd->bl.x == sd->bl.x && pl_sd->bl.y == sd->bl.y) { + return -1; + } + pc_setpos(pl_sd, sd->mapindex, sd->bl.x, sd->bl.y, CLR_RESPAWN); + sprintf(atcmd_output, msg_txt(46), pl_sd->status.name); // %s recalled! + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * charblock command (usage: charblock <player_name>) + * This command do a definitiv ban on a player + *------------------------------------------*/ +ACMD_FUNC(char_block) +{ + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1021)); // Please enter a player name (usage: @charblock/@block <char name>). + return -1; + } + + chrif_char_ask_name(sd->status.account_id, atcmd_player_name, 1, 0, 0, 0, 0, 0, 0); // type: 1 - block + clif_displaymessage(fd, msg_txt(88)); // Character name sent to char-server to ask it. + + return 0; +} + +/*========================================== + * charban command (usage: charban <time> <player_name>) + * This command do a limited ban on a player + * Time is done as follows: + * Adjustment value (-1, 1, +1, etc...) + * Modified element: + * a or y: year + * m: month + * j or d: day + * h: hour + * mn: minute + * s: second + * <example> @ban +1m-2mn1s-6y test_player + * this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. + *------------------------------------------*/ +ACMD_FUNC(char_ban) +{ + char * modif_p; + int year, month, day, hour, minute, second, value; + time_t timestamp; + struct tm *tmtime; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%s %23[^\n]", atcmd_output, atcmd_player_name) < 2) { + clif_displaymessage(fd, msg_txt(1022)); // Please enter ban time and a player name (usage: @charban/@ban/@banish/@charbanish <time> <char name>). + return -1; + } + + atcmd_output[sizeof(atcmd_output)-1] = '\0'; + + modif_p = atcmd_output; + year = month = day = hour = minute = second = 0; + while (modif_p[0] != '\0') { + value = atoi(modif_p); + if (value == 0) + modif_p++; + else { + if (modif_p[0] == '-' || modif_p[0] == '+') + modif_p++; + while (modif_p[0] >= '0' && modif_p[0] <= '9') + modif_p++; + if (modif_p[0] == 's') { + second = value; + modif_p++; + } else if (modif_p[0] == 'n') { + minute = value; + modif_p++; + } else if (modif_p[0] == 'm' && modif_p[1] == 'n') { + minute = value; + modif_p = modif_p + 2; + } else if (modif_p[0] == 'h') { + hour = value; + modif_p++; + } else if (modif_p[0] == 'd' || modif_p[0] == 'j') { + day = value; + modif_p++; + } else if (modif_p[0] == 'm') { + month = value; + modif_p++; + } else if (modif_p[0] == 'y' || modif_p[0] == 'a') { + year = value; + modif_p++; + } else if (modif_p[0] != '\0') { + modif_p++; + } + } + } + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + clif_displaymessage(fd, msg_txt(85)); // Invalid time for ban command. + return -1; + } + /** + * We now check if you can adjust the ban to negative (and if this is the case) + **/ + timestamp = time(NULL); + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + year; + tmtime->tm_mon = tmtime->tm_mon + month; + tmtime->tm_mday = tmtime->tm_mday + day; + tmtime->tm_hour = tmtime->tm_hour + hour; + tmtime->tm_min = tmtime->tm_min + minute; + tmtime->tm_sec = tmtime->tm_sec + second; + timestamp = mktime(tmtime); + if( timestamp <= time(NULL) && !pc_can_use_command(sd, "unban", COMMAND_ATCOMMAND) ) { + clif_displaymessage(fd,msg_txt(1023)); // You are not allowed to reduce the length of a ban. + return -1; + } + + chrif_char_ask_name(sd->status.account_id, atcmd_player_name, 2, year, month, day, hour, minute, second); // type: 2 - ban + clif_displaymessage(fd, msg_txt(88)); // Character name sent to char-server to ask it. + + return 0; +} + +/*========================================== + * charunblock command (usage: charunblock <player_name>) + *------------------------------------------*/ +ACMD_FUNC(char_unblock) +{ + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1024)); // Please enter a player name (usage: @charunblock <char name>). + return -1; + } + + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, atcmd_player_name, 3, 0, 0, 0, 0, 0, 0); // type: 3 - unblock + clif_displaymessage(fd, msg_txt(88)); // Character name sent to char-server to ask it. + + return 0; +} + +/*========================================== + * charunban command (usage: charunban <player_name>) + *------------------------------------------*/ +ACMD_FUNC(char_unban) +{ + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1025)); // Please enter a player name (usage: @charunban <char name>). + return -1; + } + + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, atcmd_player_name, 4, 0, 0, 0, 0, 0, 0); // type: 4 - unban + clif_displaymessage(fd, msg_txt(88)); // Character name sent to char-server to ask it. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(night) +{ + nullpo_retr(-1, sd); + + if (night_flag != 1) { + map_night_timer(night_timer_tid, 0, 0, 1); + } else { + clif_displaymessage(fd, msg_txt(89)); // Night mode is already enabled. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(day) +{ + nullpo_retr(-1, sd); + + if (night_flag != 0) { + map_day_timer(day_timer_tid, 0, 0, 1); + } else { + clif_displaymessage(fd, msg_txt(90)); // Day mode is already enabled. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(doom) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pl_sd->fd != fd && pc_get_group_level(sd) >= pc_get_group_level(pl_sd)) + { + status_kill(&pl_sd->bl); + clif_specialeffect(&pl_sd->bl,450,AREA); + clif_displaymessage(pl_sd->fd, msg_txt(61)); // The holy messenger has given judgement. + } + } + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(62)); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(doommap) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pl_sd->fd != fd && sd->bl.m == pl_sd->bl.m && pc_get_group_level(sd) >= pc_get_group_level(pl_sd)) + { + status_kill(&pl_sd->bl); + clif_specialeffect(&pl_sd->bl,450,AREA); + clif_displaymessage(pl_sd->fd, msg_txt(61)); // The holy messenger has given judgement. + } + } + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(62)); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static void atcommand_raise_sub(struct map_session_data* sd) { + + status_revive(&sd->bl, 100, 100); + + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + clif_displaymessage(sd->fd, msg_txt(63)); // Mercy has been shown. +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(raise) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + if( pc_isdead(pl_sd) ) + atcommand_raise_sub(pl_sd); + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(64)); // Mercy has been granted. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(raisemap) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + if (sd->bl.m == pl_sd->bl.m && pc_isdead(pl_sd) ) + atcommand_raise_sub(pl_sd); + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(64)); // Mercy has been granted. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(kick) +{ + struct map_session_data *pl_sd; + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1026)); // Please enter a player name (usage: @kick <char name/ID>). + return -1; + } + + if((pl_sd=map_nick2sd((char *)message)) == NULL && (pl_sd=map_charid2sd(atoi(message))) == NULL) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if ( pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + clif_GM_kick(sd, pl_sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(kickall) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pc_get_group_level(sd) >= pc_get_group_level(pl_sd)) { // you can kick only lower or same gm level + if (sd->status.account_id != pl_sd->status.account_id) + clif_GM_kick(NULL, pl_sd); + } + } + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(195)); // All players have been kicked! + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(allskill) +{ + nullpo_retr(-1, sd); + pc_allskillup(sd); // all skills + sd->status.skill_point = 0; // 0 skill points + clif_updatestatus(sd, SP_SKILLPOINT); // update + clif_displaymessage(fd, msg_txt(76)); // All skills have been added to your skill tree. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(questskill) +{ + uint16 skill_id; + nullpo_retr(-1, sd); + + if (!message || !*message || (skill_id = atoi(message)) <= 0) + {// also send a list of skills applicable to this command + const char* text; + + // attempt to find the text corresponding to this command + text = atcommand_help_string( command ); + + // send the error message as always + clif_displaymessage(fd, msg_txt(1027)); // Please enter a quest skill number. + + if( text ) + {// send the skill ID list associated with this command + clif_displaymessage( fd, text ); + } + + return -1; + } + if (skill_id >= MAX_SKILL_DB) { + clif_displaymessage(fd, msg_txt(198)); // This skill number doesn't exist. + return -1; + } + if (!(skill_get_inf2(skill_id) & INF2_QUEST_SKILL)) { + clif_displaymessage(fd, msg_txt(197)); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + if (pc_checkskill(sd, skill_id) > 0) { + clif_displaymessage(fd, msg_txt(196)); // You already have this quest skill. + return -1; + } + + pc_skill(sd, skill_id, 1, 0); + clif_displaymessage(fd, msg_txt(70)); // You have learned the skill. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(lostskill) +{ + uint16 skill_id; + nullpo_retr(-1, sd); + + if (!message || !*message || (skill_id = atoi(message)) <= 0) + {// also send a list of skills applicable to this command + const char* text; + + // attempt to find the text corresponding to this command + text = atcommand_help_string( command ); + + // send the error message as always + clif_displaymessage(fd, msg_txt(1027)); // Please enter a quest skill number. + + if( text ) + {// send the skill ID list associated with this command + clif_displaymessage( fd, text ); + } + + return -1; + } + if (skill_id >= MAX_SKILL) { + clif_displaymessage(fd, msg_txt(198)); // This skill number doesn't exist. + return -1; + } + if (!(skill_get_inf2(skill_id) & INF2_QUEST_SKILL)) { + clif_displaymessage(fd, msg_txt(197)); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + if (pc_checkskill(sd, skill_id) == 0) { + clif_displaymessage(fd, msg_txt(201)); // You don't have this quest skill. + return -1; + } + + sd->status.skill[skill_id].lv = 0; + sd->status.skill[skill_id].flag = 0; + clif_deleteskill(sd,skill_id); + clif_displaymessage(fd, msg_txt(71)); // You have forgotten the skill. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(spiritball) +{ + int max_spiritballs; + int number; + nullpo_retr(-1, sd); + + max_spiritballs = min(ARRAYLENGTH(sd->spirit_timer), 0x7FFF); + + if( !message || !*message || (number = atoi(message)) < 0 || number > max_spiritballs ) + { + char msg[CHAT_SIZE_MAX]; + safesnprintf(msg, sizeof(msg), msg_txt(1028), max_spiritballs); // Please enter a party name (usage: @party <party_name>). + clif_displaymessage(fd, msg); + return -1; + } + + if( sd->spiritball > 0 ) + pc_delspiritball(sd, sd->spiritball, 1); + sd->spiritball = number; + clif_spiritball(&sd->bl); + // no message, player can look the difference + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(party) +{ + char party[NAME_LENGTH]; + nullpo_retr(-1, sd); + + memset(party, '\0', sizeof(party)); + + if (!message || !*message || sscanf(message, "%23[^\n]", party) < 1) { + clif_displaymessage(fd, msg_txt(1029)); // Please enter a party name (usage: @party <party_name>). + return -1; + } + + party_create(sd, party, 0, 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(guild) +{ + char guild[NAME_LENGTH]; + int prev; + nullpo_retr(-1, sd); + + memset(guild, '\0', sizeof(guild)); + + if (!message || !*message || sscanf(message, "%23[^\n]", guild) < 1) { + clif_displaymessage(fd, msg_txt(1030)); // Please enter a guild name (usage: @guild <guild_name>). + return -1; + } + + prev = battle_config.guild_emperium_check; + battle_config.guild_emperium_check = 0; + guild_create(sd, guild); + battle_config.guild_emperium_check = prev; + + return 0; +} + +ACMD_FUNC(breakguild) +{ + int ret = 0; + struct guild *g; + nullpo_retr(-1, sd); + + if (sd->status.guild_id) { // Check if the player has a guild + g = guild_search(sd->status.guild_id); // Search the guild + if (g) { // Check if guild was found + if (sd->state.gmaster_flag) { // Check if player is guild master + ret = guild_break(sd, g->name); // Break guild + if (ret) { // Check if anything went wrong + return 0; // Guild was broken + } else { + return -1; // Something went wrong + } + } else { // Not guild master + clif_displaymessage(fd, msg_txt(1181)); // You need to be a Guild Master to use this command. + return -1; + } + } else { // Guild was not found. HOW? + clif_displaymessage(fd, msg_txt(252)); // You are not in a guild. + return -1; + } + } else { // Player does not have a guild + clif_displaymessage(fd, msg_txt(252)); // You are not in a guild. + return -1; + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(agitstart) +{ + nullpo_retr(-1, sd); + if (agit_flag == 1) { + clif_displaymessage(fd, msg_txt(73)); // War of Emperium is currently in progress. + return -1; + } + + agit_flag = 1; + guild_agit_start(); + clif_displaymessage(fd, msg_txt(72)); // War of Emperium has been initiated. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(agitstart2) +{ + nullpo_retr(-1, sd); + if (agit2_flag == 1) { + clif_displaymessage(fd, msg_txt(404)); // "War of Emperium SE is currently in progress." + return -1; + } + + agit2_flag = 1; + guild_agit2_start(); + clif_displaymessage(fd, msg_txt(403)); // "War of Emperium SE has been initiated." + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(agitend) +{ + nullpo_retr(-1, sd); + if (agit_flag == 0) { + clif_displaymessage(fd, msg_txt(75)); // War of Emperium is currently not in progress. + return -1; + } + + agit_flag = 0; + guild_agit_end(); + clif_displaymessage(fd, msg_txt(74)); // War of Emperium has been ended. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(agitend2) +{ + nullpo_retr(-1, sd); + if (agit2_flag == 0) { + clif_displaymessage(fd, msg_txt(406)); // "War of Emperium SE is currently not in progress." + return -1; + } + + agit2_flag = 0; + guild_agit2_end(); + clif_displaymessage(fd, msg_txt(405)); // "War of Emperium SE has been ended." + + return 0; +} + +/*========================================== + * @mapexit - shuts down the map server + *------------------------------------------*/ +ACMD_FUNC(mapexit) +{ + nullpo_retr(-1, sd); + + do_shutdown(); + return 0; +} + +/*========================================== + * idsearch <part_of_name>: revrited by [Yor] + *------------------------------------------*/ +ACMD_FUNC(idsearch) +{ + char item_name[100]; + unsigned int i, match; + struct item_data *item_array[MAX_SEARCH]; + nullpo_retr(-1, sd); + + memset(item_name, '\0', sizeof(item_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%99s", item_name) < 0) { + clif_displaymessage(fd, msg_txt(1031)); // Please enter part of an item name (usage: @idsearch <part_of_item_name>). + return -1; + } + + sprintf(atcmd_output, msg_txt(77), item_name); // The reference result of '%s' (name: id): + clif_displaymessage(fd, atcmd_output); + match = itemdb_searchname_array(item_array, MAX_SEARCH, item_name); + if (match > MAX_SEARCH) { + sprintf(atcmd_output, msg_txt(269), MAX_SEARCH, match); + clif_displaymessage(fd, atcmd_output); + match = MAX_SEARCH; + } + for(i = 0; i < match; i++) { + sprintf(atcmd_output, msg_txt(78), item_array[i]->jname, item_array[i]->nameid); // %s: %d + clif_displaymessage(fd, atcmd_output); + } + sprintf(atcmd_output, msg_txt(79), match); // It is %d affair above. + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * Recall All Characters Online To Your Location + *------------------------------------------*/ +ACMD_FUNC(recallall) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + int count; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(1032)); // You are not authorized to warp somenone to your current map. + return -1; + } + + count = 0; + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (sd->status.account_id != pl_sd->status.account_id && pc_get_group_level(sd) >= pc_get_group_level(pl_sd)) + { + if (pl_sd->bl.m == sd->bl.m && pl_sd->bl.x == sd->bl.x && pl_sd->bl.y == sd->bl.y) + continue; // Don't waste time warping the character to the same place. + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) + count++; + else { + if (pc_isdead(pl_sd)) { //Wake them up + pc_setstand(pl_sd); + pc_setrestartvalue(pl_sd,1); + } + pc_setpos(pl_sd, sd->mapindex, sd->bl.x, sd->bl.y, CLR_RESPAWN); + } + } + } + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(92)); // All characters recalled! + if (count) { + sprintf(atcmd_output, msg_txt(1033), count); // Because you are not authorized to warp from some maps, %d player(s) have not been recalled. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * Recall online characters of a guild to your location + *------------------------------------------*/ +ACMD_FUNC(guildrecall) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + int count; + char guild_name[NAME_LENGTH]; + struct guild *g; + nullpo_retr(-1, sd); + + memset(guild_name, '\0', sizeof(guild_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%23[^\n]", guild_name) < 1) { + clif_displaymessage(fd, msg_txt(1034)); // Please enter a guild name/ID (usage: @guildrecall <guild_name/ID>). + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(1032)); // You are not authorized to warp somenone to your current map. + return -1; + } + + if ((g = guild_searchname(guild_name)) == NULL && // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) == NULL) + { + clif_displaymessage(fd, msg_txt(94)); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + count = 0; + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (sd->status.account_id != pl_sd->status.account_id && pl_sd->status.guild_id == g->guild_id) + { + if (pc_get_group_level(pl_sd) > pc_get_group_level(sd) || (pl_sd->bl.m == sd->bl.m && pl_sd->bl.x == sd->bl.x && pl_sd->bl.y == sd->bl.y)) + continue; // Skip GMs greater than you... or chars already on the cell + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) + count++; + else + pc_setpos(pl_sd, sd->mapindex, sd->bl.x, sd->bl.y, CLR_RESPAWN); + } + } + mapit_free(iter); + + sprintf(atcmd_output, msg_txt(93), g->name); // All online characters of the %s guild have been recalled to your position. + clif_displaymessage(fd, atcmd_output); + if (count) { + sprintf(atcmd_output, msg_txt(1033), count); // Because you are not authorized to warp from some maps, %d player(s) have not been recalled. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * Recall online characters of a party to your location + *------------------------------------------*/ +ACMD_FUNC(partyrecall) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + char party_name[NAME_LENGTH]; + struct party_data *p; + int count; + nullpo_retr(-1, sd); + + memset(party_name, '\0', sizeof(party_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%23[^\n]", party_name) < 1) { + clif_displaymessage(fd, msg_txt(1035)); // Please enter a party name/ID (usage: @partyrecall <party_name/ID>). + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) { + clif_displaymessage(fd, msg_txt(1032)); // You are not authorized to warp somenone to your current map. + return -1; + } + + if ((p = party_searchname(party_name)) == NULL && // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) == NULL) + { + clif_displaymessage(fd, msg_txt(96)); // Incorrect name or ID, or no one from the party is online. + return -1; + } + + count = 0; + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (sd->status.account_id != pl_sd->status.account_id && pl_sd->status.party_id == p->party.party_id) + { + if (pc_get_group_level(pl_sd) > pc_get_group_level(sd) || (pl_sd->bl.m == sd->bl.m && pl_sd->bl.x == sd->bl.x && pl_sd->bl.y == sd->bl.y)) + continue; // Skip GMs greater than you... or chars already on the cell + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) + count++; + else + pc_setpos(pl_sd, sd->mapindex, sd->bl.x, sd->bl.y, CLR_RESPAWN); + } + } + mapit_free(iter); + + sprintf(atcmd_output, msg_txt(95), p->party.name); // All online characters of the %s party have been recalled to your position. + clif_displaymessage(fd, atcmd_output); + if (count) { + sprintf(atcmd_output, msg_txt(1033), count); // Because you are not authorized to warp from some maps, %d player(s) have not been recalled. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(reloaditemdb) +{ + nullpo_retr(-1, sd); + itemdb_reload(); + clif_displaymessage(fd, msg_txt(97)); // Item database has been reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(reloadmobdb) +{ + nullpo_retr(-1, sd); + mob_reload(); + read_petdb(); + merc_reload(); + read_mercenarydb(); + read_mercenary_skilldb(); + reload_elementaldb(); + clif_displaymessage(fd, msg_txt(98)); // Monster database has been reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(reloadskilldb) +{ + nullpo_retr(-1, sd); + skill_reload(); + merc_skill_reload(); + reload_elemental_skilldb(); + read_mercenary_skilldb(); + clif_displaymessage(fd, msg_txt(99)); // Skill database has been reloaded. + + return 0; +} + +/*========================================== + * @reloadatcommand - reloads atcommand_athena.conf groups.conf + *------------------------------------------*/ +void atcommand_doload(); +ACMD_FUNC(reloadatcommand) { + config_t run_test; + + if (conf_read_file(&run_test, "conf/groups.conf")) { + clif_displaymessage(fd, msg_txt(1036)); // Error reading groups.conf, reload failed. + return -1; + } + + config_destroy(&run_test); + + if (conf_read_file(&run_test, ATCOMMAND_CONF_FILENAME)) { + clif_displaymessage(fd, msg_txt(1037)); // Error reading atcommand_athena.conf, reload failed. + return -1; + } + + config_destroy(&run_test); + + atcommand_doload(); + pc_groups_reload(); + clif_displaymessage(fd, msg_txt(254)); + return 0; +} +/*========================================== + * @reloadbattleconf - reloads battle_athena.conf + *------------------------------------------*/ +ACMD_FUNC(reloadbattleconf) +{ + struct Battle_Config prev_config; + memcpy(&prev_config, &battle_config, sizeof(prev_config)); + + battle_config_read(BATTLE_CONF_FILENAME); + + if( prev_config.item_rate_mvp != battle_config.item_rate_mvp + || prev_config.item_rate_common != battle_config.item_rate_common + || prev_config.item_rate_common_boss != battle_config.item_rate_common_boss + || prev_config.item_rate_card != battle_config.item_rate_card + || prev_config.item_rate_card_boss != battle_config.item_rate_card_boss + || prev_config.item_rate_equip != battle_config.item_rate_equip + || prev_config.item_rate_equip_boss != battle_config.item_rate_equip_boss + || prev_config.item_rate_heal != battle_config.item_rate_heal + || prev_config.item_rate_heal_boss != battle_config.item_rate_heal_boss + || prev_config.item_rate_use != battle_config.item_rate_use + || prev_config.item_rate_use_boss != battle_config.item_rate_use_boss + || prev_config.item_rate_treasure != battle_config.item_rate_treasure + || prev_config.item_rate_adddrop != battle_config.item_rate_adddrop + || prev_config.logarithmic_drops != battle_config.logarithmic_drops + || prev_config.item_drop_common_min != battle_config.item_drop_common_min + || prev_config.item_drop_common_max != battle_config.item_drop_common_max + || prev_config.item_drop_card_min != battle_config.item_drop_card_min + || prev_config.item_drop_card_max != battle_config.item_drop_card_max + || prev_config.item_drop_equip_min != battle_config.item_drop_equip_min + || prev_config.item_drop_equip_max != battle_config.item_drop_equip_max + || prev_config.item_drop_mvp_min != battle_config.item_drop_mvp_min + || prev_config.item_drop_mvp_max != battle_config.item_drop_mvp_max + || prev_config.item_drop_heal_min != battle_config.item_drop_heal_min + || prev_config.item_drop_heal_max != battle_config.item_drop_heal_max + || prev_config.item_drop_use_min != battle_config.item_drop_use_min + || prev_config.item_drop_use_max != battle_config.item_drop_use_max + || prev_config.item_drop_treasure_min != battle_config.item_drop_treasure_min + || prev_config.item_drop_treasure_max != battle_config.item_drop_treasure_max + || prev_config.base_exp_rate != battle_config.base_exp_rate + || prev_config.job_exp_rate != battle_config.job_exp_rate + ) + { // Exp or Drop rates changed. + mob_reload(); //Needed as well so rate changes take effect. + chrif_ragsrvinfo(battle_config.base_exp_rate, battle_config.job_exp_rate, battle_config.item_rate_common); + } + clif_displaymessage(fd, msg_txt(255)); + return 0; +} +/*========================================== + * @reloadstatusdb - reloads job_db1.txt job_db2.txt job_db2-2.txt refine_db.txt size_fix.txt + *------------------------------------------*/ +ACMD_FUNC(reloadstatusdb) +{ + status_readdb(); + clif_displaymessage(fd, msg_txt(256)); + return 0; +} +/*========================================== + * @reloadpcdb - reloads exp.txt skill_tree.txt attr_fix.txt statpoint.txt + *------------------------------------------*/ +ACMD_FUNC(reloadpcdb) +{ + pc_readdb(); + clif_displaymessage(fd, msg_txt(257)); + return 0; +} + +/*========================================== + * @reloadmotd - reloads motd.txt + *------------------------------------------*/ +ACMD_FUNC(reloadmotd) +{ + pc_read_motd(); + clif_displaymessage(fd, msg_txt(268)); + return 0; +} + +/*========================================== + * @reloadscript - reloads all scripts (npcs, warps, mob spawns, ...) + *------------------------------------------*/ +ACMD_FUNC(reloadscript) +{ + nullpo_retr(-1, sd); + //atcommand_broadcast( fd, sd, "@broadcast", "Server is reloading scripts..." ); + //atcommand_broadcast( fd, sd, "@broadcast", "You will feel a bit of lag at this point !" ); + + flush_fifos(); + map_reloadnpc(true); // reload config files seeking for npcs + script_reload(); + npc_reload(); + + clif_displaymessage(fd, msg_txt(100)); // Scripts have been reloaded. + + return 0; +} + +/*========================================== + * @mapinfo [0-3] <map name> by MC_Cameri + * => Shows information about the map [map name] + * 0 = no additional information + * 1 = Show users in that map and their location + * 2 = Shows NPCs in that map + * 3 = Shows the shops/chats in that map (not implemented) + *------------------------------------------*/ +ACMD_FUNC(mapinfo) +{ + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + struct npc_data *nd = NULL; + struct chat_data *cd = NULL; + char direction[12]; + int i, m_id, chat_num, list = 0; + unsigned short m_index; + char mapname[24]; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(mapname, '\0', sizeof(mapname)); + memset(direction, '\0', sizeof(direction)); + + sscanf(message, "%d %23[^\n]", &list, mapname); + + if (list < 0 || list > 3) { + clif_displaymessage(fd, msg_txt(1038)); // Please enter at least one valid list number (usage: @mapinfo <0-3> <map>). + return -1; + } + + if (mapname[0] == '\0') { + safestrncpy(mapname, mapindex_id2name(sd->mapindex), MAP_NAME_LENGTH); + m_id = map_mapindex2mapid(sd->mapindex); + } else { + m_id = map_mapname2mapid(mapname); + } + + if (m_id < 0) { + clif_displaymessage(fd, msg_txt(1)); // Map not found. + return -1; + } + m_index = mapindex_name2id(mapname); //This one shouldn't fail since the previous seek did not. + + clif_displaymessage(fd, msg_txt(1039)); // ------ Map Info ------ + + // count chats (for initial message) + chat_num = 0; + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + if( (cd = (struct chat_data*)map_id2bl(pl_sd->chatID)) != NULL && pl_sd->mapindex == m_index && cd->usersd[0] == pl_sd ) + chat_num++; + mapit_free(iter); + + sprintf(atcmd_output, msg_txt(1040), mapname, map[m_id].users, map[m_id].npc_num, chat_num); // Map Name: %s | Players In Map: %d | NPCs In Map: %d | Chats In Map: %d + clif_displaymessage(fd, atcmd_output); + clif_displaymessage(fd, msg_txt(1041)); // ------ Map Flags ------ + if (map[m_id].flag.town) + clif_displaymessage(fd, msg_txt(1042)); // Town Map + + if (battle_config.autotrade_mapflag == map[m_id].flag.autotrade) + clif_displaymessage(fd, msg_txt(1043)); // Autotrade Enabled + else + clif_displaymessage(fd, msg_txt(1044)); // Autotrade Disabled + + if (map[m_id].flag.battleground) + clif_displaymessage(fd, msg_txt(1045)); // Battlegrounds ON + + strcpy(atcmd_output,msg_txt(1046)); // PvP Flags: + if (map[m_id].flag.pvp) + strcat(atcmd_output, msg_txt(1047)); // Pvp ON | + if (map[m_id].flag.pvp_noguild) + strcat(atcmd_output, msg_txt(1048)); // NoGuild | + if (map[m_id].flag.pvp_noparty) + strcat(atcmd_output, msg_txt(1049)); // NoParty | + if (map[m_id].flag.pvp_nightmaredrop) + strcat(atcmd_output, msg_txt(1050)); // NightmareDrop | + if (map[m_id].flag.pvp_nocalcrank) + strcat(atcmd_output, msg_txt(1051)); // NoCalcRank | + clif_displaymessage(fd, atcmd_output); + + strcpy(atcmd_output,msg_txt(1052)); // GvG Flags: + if (map[m_id].flag.gvg) + strcat(atcmd_output, msg_txt(1053)); // GvG ON | + if (map[m_id].flag.gvg_dungeon) + strcat(atcmd_output, msg_txt(1054)); // GvG Dungeon | + if (map[m_id].flag.gvg_castle) + strcat(atcmd_output, msg_txt(1055)); // GvG Castle | + if (map[m_id].flag.gvg_noparty) + strcat(atcmd_output, msg_txt(1056)); // NoParty | + clif_displaymessage(fd, atcmd_output); + + strcpy(atcmd_output,msg_txt(1057)); // Teleport Flags: + if (map[m_id].flag.noteleport) + strcat(atcmd_output, msg_txt(1058)); // NoTeleport | + if (map[m_id].flag.monster_noteleport) + strcat(atcmd_output, msg_txt(1059)); // Monster NoTeleport | + if (map[m_id].flag.nowarp) + strcat(atcmd_output, msg_txt(1060)); // NoWarp | + if (map[m_id].flag.nowarpto) + strcat(atcmd_output, msg_txt(1061)); // NoWarpTo | + if (map[m_id].flag.noreturn) + strcat(atcmd_output, msg_txt(1062)); // NoReturn | + if (map[m_id].flag.nogo) + strcat(atcmd_output, msg_txt(1063)); // NoGo | + if (map[m_id].flag.nomemo) + strcat(atcmd_output, msg_txt(1064)); // NoMemo | + clif_displaymessage(fd, atcmd_output); + + sprintf(atcmd_output, msg_txt(1065), // No Exp Penalty: %s | No Zeny Penalty: %s + (map[m_id].flag.noexppenalty) ? msg_txt(1066) : msg_txt(1067), (map[m_id].flag.nozenypenalty) ? msg_txt(1066) : msg_txt(1067)); // On / Off + clif_displaymessage(fd, atcmd_output); + + if (map[m_id].flag.nosave) { + if (!map[m_id].save.map) + clif_displaymessage(fd, msg_txt(1068)); // No Save (Return to last Save Point) + else if (map[m_id].save.x == -1 || map[m_id].save.y == -1 ) { + sprintf(atcmd_output, msg_txt(1069), mapindex_id2name(map[m_id].save.map)); // No Save, Save Point: %s,Random + clif_displaymessage(fd, atcmd_output); + } + else { + sprintf(atcmd_output, msg_txt(1070), // No Save, Save Point: %s,%d,%d + mapindex_id2name(map[m_id].save.map),map[m_id].save.x,map[m_id].save.y); + clif_displaymessage(fd, atcmd_output); + } + } + + strcpy(atcmd_output,msg_txt(1071)); // Weather Flags: + if (map[m_id].flag.snow) + strcat(atcmd_output, msg_txt(1072)); // Snow | + if (map[m_id].flag.fog) + strcat(atcmd_output, msg_txt(1073)); // Fog | + if (map[m_id].flag.sakura) + strcat(atcmd_output, msg_txt(1074)); // Sakura | + if (map[m_id].flag.clouds) + strcat(atcmd_output, msg_txt(1075)); // Clouds | + if (map[m_id].flag.clouds2) + strcat(atcmd_output, msg_txt(1076)); // Clouds2 | + if (map[m_id].flag.fireworks) + strcat(atcmd_output, msg_txt(1077)); // Fireworks | + if (map[m_id].flag.leaves) + strcat(atcmd_output, msg_txt(1078)); // Leaves | + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //if (map[m_id].flag.rain) + // strcat(atcmd_output, msg_txt(1079)); // Rain | + if (map[m_id].flag.nightenabled) + strcat(atcmd_output, msg_txt(1080)); // Displays Night | + clif_displaymessage(fd, atcmd_output); + + strcpy(atcmd_output,msg_txt(1081)); // Other Flags: + if (map[m_id].flag.nobranch) + strcat(atcmd_output, msg_txt(1082)); // NoBranch | + if (map[m_id].flag.notrade) + strcat(atcmd_output, msg_txt(1083)); // NoTrade | + if (map[m_id].flag.novending) + strcat(atcmd_output, msg_txt(1084)); // NoVending | + if (map[m_id].flag.nodrop) + strcat(atcmd_output, msg_txt(1085)); // NoDrop | + if (map[m_id].flag.noskill) + strcat(atcmd_output, msg_txt(1086)); // NoSkill | + if (map[m_id].flag.noicewall) + strcat(atcmd_output, msg_txt(1087)); // NoIcewall | + if (map[m_id].flag.allowks) + strcat(atcmd_output, msg_txt(1088)); // AllowKS | + if (map[m_id].flag.reset) + strcat(atcmd_output, msg_txt(1089)); // Reset | + clif_displaymessage(fd, atcmd_output); + + strcpy(atcmd_output,msg_txt(1090)); // Other Flags: + if (map[m_id].nocommand) + strcat(atcmd_output, msg_txt(1091)); // NoCommand | + if (map[m_id].flag.nobaseexp) + strcat(atcmd_output, msg_txt(1092)); // NoBaseEXP | + if (map[m_id].flag.nojobexp) + strcat(atcmd_output, msg_txt(1093)); // NoJobEXP | + if (map[m_id].flag.nomobloot) + strcat(atcmd_output, msg_txt(1094)); // NoMobLoot | + if (map[m_id].flag.nomvploot) + strcat(atcmd_output, msg_txt(1095)); // NoMVPLoot | + if (map[m_id].flag.partylock) + strcat(atcmd_output, msg_txt(1096)); // PartyLock | + if (map[m_id].flag.guildlock) + strcat(atcmd_output, msg_txt(1097)); // GuildLock | + clif_displaymessage(fd, atcmd_output); + + switch (list) { + case 0: + // Do nothing. It's list 0, no additional display. + break; + case 1: + clif_displaymessage(fd, msg_txt(1098)); // ----- Players in Map ----- + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pl_sd->mapindex == m_index) { + sprintf(atcmd_output, msg_txt(1099), // Player '%s' (session #%d) | Location: %d,%d + pl_sd->status.name, pl_sd->fd, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + } + } + mapit_free(iter); + break; + case 2: + clif_displaymessage(fd, msg_txt(1100)); // ----- NPCs in Map ----- + for (i = 0; i < map[m_id].npc_num;) + { + nd = map[m_id].npc[i]; + switch(nd->ud.dir) { + case 0: strcpy(direction, msg_txt(1101)); break; // North + case 1: strcpy(direction, msg_txt(1102)); break; // North West + case 2: strcpy(direction, msg_txt(1103)); break; // West + case 3: strcpy(direction, msg_txt(1104)); break; // South West + case 4: strcpy(direction, msg_txt(1105)); break; // South + case 5: strcpy(direction, msg_txt(1106)); break; // South East + case 6: strcpy(direction, msg_txt(1107)); break; // East + case 7: strcpy(direction, msg_txt(1108)); break; // North East + case 9: strcpy(direction, msg_txt(1109)); break; // North + default: strcpy(direction, msg_txt(1110)); break; // Unknown + } + if(strcmp(nd->name,nd->exname) == 0) + sprintf(atcmd_output, msg_txt(1111), // NPC %d: %s | Direction: %s | Sprite: %d | Location: %d %d + ++i, nd->name, direction, nd->class_, nd->bl.x, nd->bl.y); + else + sprintf(atcmd_output, msg_txt(1112), // NPC %d: %s::%s | Direction: %s | Sprite: %d | Location: %d %d + ++i, nd->name, nd->exname, direction, nd->class_, nd->bl.x, nd->bl.y); + clif_displaymessage(fd, atcmd_output); + } + break; + case 3: + clif_displaymessage(fd, msg_txt(1113)); // ----- Chats in Map ----- + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if ((cd = (struct chat_data*)map_id2bl(pl_sd->chatID)) != NULL && + pl_sd->mapindex == m_index && + cd->usersd[0] == pl_sd) + { + sprintf(atcmd_output, msg_txt(1114), // Chat: %s | Player: %s | Location: %d %d + cd->title, pl_sd->status.name, cd->bl.x, cd->bl.y); + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1115), // Users: %d/%d | Password: %s | Public: %s + cd->users, cd->limit, cd->pass, (cd->pub) ? msg_txt(1116) : msg_txt(1117)); // Yes / No + clif_displaymessage(fd, atcmd_output); + } + } + mapit_free(iter); + break; + default: // normally impossible to arrive here + clif_displaymessage(fd, msg_txt(1118)); // Please enter at least one valid list number (usage: @mapinfo <0-3> <map>). + return -1; + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(mount_peco) +{ + nullpo_retr(-1, sd); + + if (sd->disguise) { + clif_displaymessage(fd, msg_txt(212)); // Cannot mount while in disguise. + return -1; + } + + if( (sd->class_&MAPID_THIRDMASK) == MAPID_RUNE_KNIGHT && pc_checkskill(sd,RK_DRAGONTRAINING) > 0 ) { + if( !(sd->sc.option&OPTION_DRAGON1) ) { + clif_displaymessage(sd->fd,msg_txt(1119)); // You have mounted your Dragon. + pc_setoption(sd, sd->sc.option|OPTION_DRAGON1); + } else { + clif_displaymessage(sd->fd,msg_txt(1120)); // You have released your Dragon. + pc_setoption(sd, sd->sc.option&~OPTION_DRAGON1); + } + return 0; + } + if( (sd->class_&MAPID_THIRDMASK) == MAPID_RANGER && pc_checkskill(sd,RA_WUGRIDER) > 0 ) { + if( !pc_isridingwug(sd) ) { + clif_displaymessage(sd->fd,msg_txt(1121)); // You have mounted your Warg. + pc_setoption(sd, sd->sc.option|OPTION_WUGRIDER); + } else { + clif_displaymessage(sd->fd,msg_txt(1122)); // You have released your Warg. + pc_setoption(sd, sd->sc.option&~OPTION_WUGRIDER); + } + return 0; + } + if( (sd->class_&MAPID_THIRDMASK) == MAPID_MECHANIC ) { + if( !pc_ismadogear(sd) ) { + clif_displaymessage(sd->fd,msg_txt(1123)); // You have mounted your Mado Gear. + pc_setoption(sd, sd->sc.option|OPTION_MADOGEAR); + } else { + clif_displaymessage(sd->fd,msg_txt(1124)); // You have released your Mado Gear. + pc_setoption(sd, sd->sc.option&~OPTION_MADOGEAR); + } + return 0; + } + if (!pc_isriding(sd)) { // if actually no peco + + if (!pc_checkskill(sd, KN_RIDING)) { + clif_displaymessage(fd, msg_txt(213)); // You can not mount a Peco Peco with your current job. + return -1; + } + + pc_setoption(sd, sd->sc.option | OPTION_RIDING); + clif_displaymessage(fd, msg_txt(102)); // You have mounted a Peco Peco. + } else {//Dismount + pc_setoption(sd, sd->sc.option & ~OPTION_RIDING); + clif_displaymessage(fd, msg_txt(214)); // You have released your Peco Peco. + } + + return 0; +} + +/*========================================== + *Spy Commands by Syrus22 + *------------------------------------------*/ +ACMD_FUNC(guildspy) +{ + char guild_name[NAME_LENGTH]; + struct guild *g; + nullpo_retr(-1, sd); + + memset(guild_name, '\0', sizeof(guild_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!enable_spy) + { + clif_displaymessage(fd, msg_txt(1125)); // The mapserver has spy command support disabled. + return -1; + } + if (!message || !*message || sscanf(message, "%23[^\n]", guild_name) < 1) { + clif_displaymessage(fd, msg_txt(1126)); // Please enter a guild name/ID (usage: @guildspy <guild_name/ID>). + return -1; + } + + if ((g = guild_searchname(guild_name)) != NULL || // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) != NULL) { + if (sd->guildspy == g->guild_id) { + sd->guildspy = 0; + sprintf(atcmd_output, msg_txt(103), g->name); // No longer spying on the %s guild. + clif_displaymessage(fd, atcmd_output); + } else { + sd->guildspy = g->guild_id; + sprintf(atcmd_output, msg_txt(104), g->name); // Spying on the %s guild. + clif_displaymessage(fd, atcmd_output); + } + } else { + clif_displaymessage(fd, msg_txt(94)); // Incorrect name/ID, or no one from the specified guild is online. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(partyspy) +{ + char party_name[NAME_LENGTH]; + struct party_data *p; + nullpo_retr(-1, sd); + + memset(party_name, '\0', sizeof(party_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!enable_spy) + { + clif_displaymessage(fd, msg_txt(1125)); // The mapserver has spy command support disabled. + return -1; + } + + if (!message || !*message || sscanf(message, "%23[^\n]", party_name) < 1) { + clif_displaymessage(fd, msg_txt(1127)); // Please enter a party name/ID (usage: @partyspy <party_name/ID>). + return -1; + } + + if ((p = party_searchname(party_name)) != NULL || // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) != NULL) { + if (sd->partyspy == p->party.party_id) { + sd->partyspy = 0; + sprintf(atcmd_output, msg_txt(105), p->party.name); // No longer spying on the %s party. + clif_displaymessage(fd, atcmd_output); + } else { + sd->partyspy = p->party.party_id; + sprintf(atcmd_output, msg_txt(106), p->party.name); // Spying on the %s party. + clif_displaymessage(fd, atcmd_output); + } + } else { + clif_displaymessage(fd, msg_txt(96)); // Incorrect name/ID, or no one from the specified party is online. + return -1; + } + + return 0; +} + +/*========================================== + * @repairall [Valaris] + *------------------------------------------*/ +ACMD_FUNC(repairall) +{ + int count, i; + nullpo_retr(-1, sd); + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && sd->status.inventory[i].attribute == 1) { + sd->status.inventory[i].attribute = 0; + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + count++; + } + } + + if (count > 0) { + clif_misceffect(&sd->bl, 3); + clif_equiplist(sd); + clif_displaymessage(fd, msg_txt(107)); // All items have been repaired. + } else { + clif_displaymessage(fd, msg_txt(108)); // No item need to be repaired. + return -1; + } + + return 0; +} + +/*========================================== + * @nuke [Valaris] + *------------------------------------------*/ +ACMD_FUNC(nuke) +{ + struct map_session_data *pl_sd; + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1128)); // Please enter a player name (usage: @nuke <char name>). + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) != NULL) { + if (pc_get_group_level(sd) >= pc_get_group_level(pl_sd)) { // you can kill only lower or same GM level + skill_castend_nodamage_id(&pl_sd->bl, &pl_sd->bl, NPC_SELFDESTRUCTION, 99, gettick(), 0); + clif_displaymessage(fd, msg_txt(109)); // Player has been nuked! + } else { + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @tonpc + *------------------------------------------*/ +ACMD_FUNC(tonpc) +{ + char npcname[NAME_LENGTH+1]; + struct npc_data *nd; + + nullpo_retr(-1, sd); + + memset(npcname, 0, sizeof(npcname)); + + if (!message || !*message || sscanf(message, "%23[^\n]", npcname) < 1) { + clif_displaymessage(fd, msg_txt(1129)); // Please enter a NPC name (usage: @tonpc <NPC_name>). + return -1; + } + + if ((nd = npc_name2id(npcname)) != NULL) { + if (pc_setpos(sd, map_id2index(nd->bl.m), nd->bl.x, nd->bl.y, CLR_TELEPORT) == 0) + clif_displaymessage(fd, msg_txt(0)); // Warped. + else + return -1; + } else { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(shownpc) +{ + char NPCname[NAME_LENGTH+1]; + nullpo_retr(-1, sd); + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%23[^\n]", NPCname) < 1) { + clif_displaymessage(fd, msg_txt(1130)); // Please enter a NPC name (usage: @enablenpc <NPC_name>). + return -1; + } + + if (npc_name2id(NPCname) != NULL) { + npc_enable(NPCname, 1); + clif_displaymessage(fd, msg_txt(110)); // Npc Enabled. + } else { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(hidenpc) +{ + char NPCname[NAME_LENGTH+1]; + nullpo_retr(-1, sd); + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%23[^\n]", NPCname) < 1) { + clif_displaymessage(fd, msg_txt(1131)); // Please enter a NPC name (usage: @hidenpc <NPC_name>). + return -1; + } + + if (npc_name2id(NPCname) == NULL) { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist. + return -1; + } + + npc_enable(NPCname, 0); + clif_displaymessage(fd, msg_txt(112)); // Npc Disabled. + return 0; +} + +ACMD_FUNC(loadnpc) +{ + FILE *fp; + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1132)); // Please enter a script file name (usage: @loadnpc <file name>). + return -1; + } + + // check if script file exists + if ((fp = fopen(message, "r")) == NULL) { + clif_displaymessage(fd, msg_txt(261)); + return -1; + } + fclose(fp); + + // add to list of script sources and run it + npc_addsrcfile(message); + npc_parsesrcfile(message,true); + npc_read_event_script(); + + clif_displaymessage(fd, msg_txt(262)); + + return 0; +} + +ACMD_FUNC(unloadnpc) +{ + struct npc_data *nd; + char NPCname[NAME_LENGTH+1]; + nullpo_retr(-1, sd); + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%24[^\n]", NPCname) < 1) { + clif_displaymessage(fd, msg_txt(1133)); // Please enter a NPC name (usage: @npcoff <NPC_name>). + return -1; + } + + if ((nd = npc_name2id(NPCname)) == NULL) { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist. + return -1; + } + + npc_unload_duplicates(nd); + npc_unload(nd,true); + npc_read_event_script(); + clif_displaymessage(fd, msg_txt(112)); // Npc Disabled. + return 0; +} + +/*========================================== + * time in txt for time command (by [Yor]) + *------------------------------------------*/ +char* txt_time(unsigned int duration) +{ + int days, hours, minutes, seconds; + char temp[CHAT_SIZE_MAX]; + static char temp1[CHAT_SIZE_MAX]; + + memset(temp, '\0', sizeof(temp)); + memset(temp1, '\0', sizeof(temp1)); + + days = duration / (60 * 60 * 24); + duration = duration - (60 * 60 * 24 * days); + hours = duration / (60 * 60); + duration = duration - (60 * 60 * hours); + minutes = duration / 60; + seconds = duration - (60 * minutes); + + if (days == 1) + sprintf(temp, msg_txt(219), days); // %d day + else if (days > 1) + sprintf(temp, msg_txt(220), days); // %d days + if (hours == 1) + sprintf(temp1, msg_txt(221), temp, hours); // %s %d hour + else if (hours > 1) + sprintf(temp1, msg_txt(222), temp, hours); // %s %d hours + if (minutes < 2) + sprintf(temp, msg_txt(223), temp1, minutes); // %s %d minute + else + sprintf(temp, msg_txt(224), temp1, minutes); // %s %d minutes + if (seconds == 1) + sprintf(temp1, msg_txt(225), temp, seconds); // %s and %d second + else if (seconds > 1) + sprintf(temp1, msg_txt(226), temp, seconds); // %s and %d seconds + + return temp1; +} + +/*========================================== + * @time/@date/@serverdate/@servertime: Display the date/time of the server (by [Yor] + * Calculation management of GM modification (@day/@night GM commands) is done + *------------------------------------------*/ +ACMD_FUNC(servertime) +{ + const struct TimerData * timer_data; + const struct TimerData * timer_data2; + time_t time_server; // variable for number of seconds (used with time() function) + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + char temp[CHAT_SIZE_MAX]; + nullpo_retr(-1, sd); + + memset(temp, '\0', sizeof(temp)); + + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + // like sprintf, but only for date/time (Sunday, November 02 2003 15:12:52) + strftime(temp, sizeof(temp)-1, msg_txt(230), datetime); // Server time (normal time): %A, %B %d %Y %X. + clif_displaymessage(fd, temp); + + if (battle_config.night_duration == 0 && battle_config.day_duration == 0) { + if (night_flag == 0) + clif_displaymessage(fd, msg_txt(231)); // Game time: The game is in permanent daylight. + else + clif_displaymessage(fd, msg_txt(232)); // Game time: The game is in permanent night. + } else if (battle_config.night_duration == 0) + if (night_flag == 1) { // we start with night + timer_data = get_timer(day_timer_tid); + sprintf(temp, msg_txt(233), txt_time(DIFF_TICK(timer_data->tick,gettick())/1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_txt(234)); // Game time: After, the game will be in permanent daylight. + } else + clif_displaymessage(fd, msg_txt(231)); // Game time: The game is in permanent daylight. + else if (battle_config.day_duration == 0) + if (night_flag == 0) { // we start with day + timer_data = get_timer(night_timer_tid); + sprintf(temp, msg_txt(235), txt_time(DIFF_TICK(timer_data->tick,gettick())/1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_txt(236)); // Game time: After, the game will be in permanent night. + } else + clif_displaymessage(fd, msg_txt(232)); // Game time: The game is in permanent night. + else { + if (night_flag == 0) { + timer_data = get_timer(night_timer_tid); + timer_data2 = get_timer(day_timer_tid); + sprintf(temp, msg_txt(235), txt_time(DIFF_TICK(timer_data->tick,gettick())/1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + if (DIFF_TICK(timer_data->tick, timer_data2->tick) > 0) + sprintf(temp, msg_txt(237), txt_time(DIFF_TICK(timer_data->interval,DIFF_TICK(timer_data->tick,timer_data2->tick)) / 1000)); // Game time: After, the game will be in night for %s. + else + sprintf(temp, msg_txt(237), txt_time(DIFF_TICK(timer_data2->tick,timer_data->tick)/1000)); // Game time: After, the game will be in night for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_txt(238), txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } else { + timer_data = get_timer(day_timer_tid); + timer_data2 = get_timer(night_timer_tid); + sprintf(temp, msg_txt(233), txt_time(DIFF_TICK(timer_data->tick,gettick()) / 1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + if (DIFF_TICK(timer_data->tick,timer_data2->tick) > 0) + sprintf(temp, msg_txt(239), txt_time((timer_data->interval - DIFF_TICK(timer_data->tick, timer_data2->tick)) / 1000)); // Game time: After, the game will be in daylight for %s. + else + sprintf(temp, msg_txt(239), txt_time(DIFF_TICK(timer_data2->tick, timer_data->tick) / 1000)); // Game time: After, the game will be in daylight for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_txt(238), txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } + } + + return 0; +} + +//Added by Coltaro +//We're using this function here instead of using time_t so that it only counts player's jail time when he/she's online (and since the idea is to reduce the amount of minutes one by one in status_change_timer...). +//Well, using time_t could still work but for some reason that looks like more coding x_x +static void get_jail_time(int jailtime, int* year, int* month, int* day, int* hour, int* minute) +{ + const int factor_year = 518400; //12*30*24*60 = 518400 + const int factor_month = 43200; //30*24*60 = 43200 + const int factor_day = 1440; //24*60 = 1440 + const int factor_hour = 60; + + *year = jailtime/factor_year; + jailtime -= *year*factor_year; + *month = jailtime/factor_month; + jailtime -= *month*factor_month; + *day = jailtime/factor_day; + jailtime -= *day*factor_day; + *hour = jailtime/factor_hour; + jailtime -= *hour*factor_hour; + *minute = jailtime; + + *year = *year > 0? *year : 0; + *month = *month > 0? *month : 0; + *day = *day > 0? *day : 0; + *hour = *hour > 0? *hour : 0; + *minute = *minute > 0? *minute : 0; + return; +} + +/*========================================== + * @jail <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------*/ +ACMD_FUNC(jail) +{ + struct map_session_data *pl_sd; + int x, y; + unsigned short m_index; + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1134)); // Please enter a player name (usage: @jail <char_name>). + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) == NULL) { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if (pc_get_group_level(sd) < pc_get_group_level(pl_sd)) + { // you can jail only lower or same GM + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + if (pl_sd->sc.data[SC_JAILED]) + { + clif_displaymessage(fd, msg_txt(118)); // Player warped in jails. + return -1; + } + + switch(rnd() % 2) { //Jail Locations + case 0: + m_index = mapindex_name2id(MAP_JAIL); + x = 24; + y = 75; + break; + default: + m_index = mapindex_name2id(MAP_JAIL); + x = 49; + y = 75; + break; + } + + //Duration of INT_MAX to specify infinity. + sc_start4(&pl_sd->bl,SC_JAILED,100,INT_MAX,m_index,x,y,1000); + clif_displaymessage(pl_sd->fd, msg_txt(117)); // GM has send you in jails. + clif_displaymessage(fd, msg_txt(118)); // Player warped in jails. + return 0; +} + +/*========================================== + * @unjail/@discharge <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------*/ +ACMD_FUNC(unjail) +{ + struct map_session_data *pl_sd; + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1135)); // Please enter a player name (usage: @unjail/@discharge <char_name>). + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) == NULL) { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if (pc_get_group_level(sd) < pc_get_group_level(pl_sd)) { // you can jail only lower or same GM + + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + if (!pl_sd->sc.data[SC_JAILED]) + { + clif_displaymessage(fd, msg_txt(119)); // This player is not in jails. + return -1; + } + + //Reset jail time to 1 sec. + sc_start(&pl_sd->bl,SC_JAILED,100,1,1000); + clif_displaymessage(pl_sd->fd, msg_txt(120)); // A GM has discharged you from jail. + clif_displaymessage(fd, msg_txt(121)); // Player unjailed. + return 0; +} + +ACMD_FUNC(jailfor) +{ + struct map_session_data *pl_sd = NULL; + int year, month, day, hour, minute, value; + char * modif_p; + int jailtime = 0,x,y; + short m_index = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%s %23[^\n]",atcmd_output,atcmd_player_name) < 2) { + clif_displaymessage(fd, msg_txt(400)); //Usage: @jailfor <time> <character name> + return -1; + } + + atcmd_output[sizeof(atcmd_output)-1] = '\0'; + + modif_p = atcmd_output; + year = month = day = hour = minute = 0; + while (modif_p[0] != '\0') { + value = atoi(modif_p); + if (value == 0) + modif_p++; + else { + if (modif_p[0] == '-' || modif_p[0] == '+') + modif_p++; + while (modif_p[0] >= '0' && modif_p[0] <= '9') + modif_p++; + if (modif_p[0] == 'n') { + minute = value; + modif_p++; + } else if (modif_p[0] == 'm' && modif_p[1] == 'n') { + minute = value; + modif_p = modif_p + 2; + } else if (modif_p[0] == 'h') { + hour = value; + modif_p++; + } else if (modif_p[0] == 'd' || modif_p[0] == 'j') { + day = value; + modif_p++; + } else if (modif_p[0] == 'm') { + month = value; + modif_p++; + } else if (modif_p[0] == 'y' || modif_p[0] == 'a') { + year = value; + modif_p++; + } else if (modif_p[0] != '\0') { + modif_p++; + } + } + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0) { + clif_displaymessage(fd, msg_txt(1136)); // Invalid time for jail command. + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) == NULL) { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if (pc_get_group_level(pl_sd) > pc_get_group_level(sd)) { + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + jailtime = year*12*30*24*60 + month*30*24*60 + day*24*60 + hour*60 + minute; //In minutes + + if(jailtime==0) { + clif_displaymessage(fd, msg_txt(1136)); // Invalid time for jail command. + return -1; + } + + //Added by Coltaro + if(pl_sd->sc.data[SC_JAILED] && + pl_sd->sc.data[SC_JAILED]->val1 != INT_MAX) + { //Update the player's jail time + jailtime += pl_sd->sc.data[SC_JAILED]->val1; + if (jailtime <= 0) { + jailtime = 0; + clif_displaymessage(pl_sd->fd, msg_txt(120)); // GM has discharge you. + clif_displaymessage(fd, msg_txt(121)); // Player unjailed + } else { + get_jail_time(jailtime,&year,&month,&day,&hour,&minute); + sprintf(atcmd_output,msg_txt(402),msg_txt(1137),year,month,day,hour,minute); //%s in jail for %d years, %d months, %d days, %d hours and %d minutes + clif_displaymessage(pl_sd->fd, atcmd_output); + sprintf(atcmd_output,msg_txt(402),msg_txt(1138),year,month,day,hour,minute); //This player is now in jail for %d years, %d months, %d days, %d hours and %d minutes + clif_displaymessage(fd, atcmd_output); + } + } else if (jailtime < 0) { + clif_displaymessage(fd, msg_txt(1136)); + return -1; + } + + //Jail locations, add more as you wish. + switch(rnd()%2) + { + case 1: //Jail #1 + m_index = mapindex_name2id(MAP_JAIL); + x = 49; y = 75; + break; + default: //Default Jail + m_index = mapindex_name2id(MAP_JAIL); + x = 24; y = 75; + break; + } + + sc_start4(&pl_sd->bl,SC_JAILED,100,jailtime,m_index,x,y,jailtime?60000:1000); //jailtime = 0: Time was reset to 0. Wait 1 second to warp player out (since it's done in status_change_timer). + return 0; +} + + +//By Coltaro +ACMD_FUNC(jailtime) +{ + int year, month, day, hour, minute; + + nullpo_retr(-1, sd); + + if (!sd->sc.data[SC_JAILED]) { + clif_displaymessage(fd, msg_txt(1139)); // You are not in jail. + return -1; + } + + if (sd->sc.data[SC_JAILED]->val1 == INT_MAX) { + clif_displaymessage(fd, msg_txt(1140)); // You have been jailed indefinitely. + return 0; + } + + if (sd->sc.data[SC_JAILED]->val1 <= 0) { // Was not jailed with @jailfor (maybe @jail? or warped there? or got recalled?) + clif_displaymessage(fd, msg_txt(1141)); // You have been jailed for an unknown amount of time. + return -1; + } + + //Get remaining jail time + get_jail_time(sd->sc.data[SC_JAILED]->val1,&year,&month,&day,&hour,&minute); + sprintf(atcmd_output,msg_txt(402),msg_txt(1142),year,month,day,hour,minute); // You will remain in jail for %d years, %d months, %d days, %d hours and %d minutes + + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * @disguise <mob_id> by [Valaris] (simplified by [Yor]) + *------------------------------------------*/ +ACMD_FUNC(disguise) +{ + int id = 0; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1143)); // Please enter a Monster/NPC name/ID (usage: @disguise <name/ID>). + return -1; + } + + if ((id = atoi(message)) > 0) + { //Acquired an ID + if (!mobdb_checkid(id) && !npcdb_checkid(id)) + id = 0; //Invalid id for either mobs or npcs. + } else { //Acquired a Name + if ((id = mobdb_searchname(message)) == 0) + { + struct npc_data* nd = npc_name2id(message); + if (nd != NULL) + id = nd->class_; + } + } + + if (id == 0) + { + clif_displaymessage(fd, msg_txt(123)); // Invalid Monster/NPC name/ID specified. + return -1; + } + + if(pc_isriding(sd)) + { + clif_displaymessage(fd, msg_txt(1144)); // Character cannot be disguised while mounted. + return -1; + } + + pc_disguise(sd, id); + clif_displaymessage(fd, msg_txt(122)); // Disguise applied. + + return 0; +} + +/*========================================== + * DisguiseAll + *------------------------------------------*/ +ACMD_FUNC(disguiseall) +{ + int mob_id=0; + struct map_session_data *pl_sd; + struct s_mapiterator* iter; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1145)); // Please enter a Monster/NPC name/ID (usage: @disguiseall <name/ID>). + return -1; + } + + if ((mob_id = mobdb_searchname(message)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(message); + + if (!mobdb_checkid(mob_id) && !npcdb_checkid(mob_id)) { //if mob or npc... + clif_displaymessage(fd, msg_txt(123)); // Monster/NPC name/id not found. + return -1; + } + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + pc_disguise(pl_sd, mob_id); + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(122)); // Disguise applied. + return 0; +} + +/*========================================== + * DisguiseGuild + *------------------------------------------*/ +ACMD_FUNC(disguiseguild) +{ + int id = 0, i; + char monster[NAME_LENGTH], guild[NAME_LENGTH]; + struct map_session_data *pl_sd; + struct guild *g; + + memset(monster, '\0', sizeof(monster)); + memset(guild, '\0', sizeof(guild)); + + if( !message || !*message || sscanf(message, "%23[^,], %23[^\r\n]", monster, guild) < 2 ) { + clif_displaymessage(fd, msg_txt(1146)); // Please enter a mob name/ID and guild name/ID (usage: @disguiseguild <mob name/ID>, <guild name/ID>). + return -1; + } + + if( (id = atoi(monster)) > 0 ) { + if( !mobdb_checkid(id) && !npcdb_checkid(id) ) + id = 0; + } else { + if( (id = mobdb_searchname(monster)) == 0 ) { + struct npc_data* nd = npc_name2id(monster); + if( nd != NULL ) + id = nd->class_; + } + } + + if( id == 0 ) { + clif_displaymessage(fd, msg_txt(123)); // Monster/NPC name/id hasn't been found. + return -1; + } + + if( (g = guild_searchname(guild)) == NULL && (g = guild_search(atoi(guild))) == NULL ) { + clif_displaymessage(fd, msg_txt(94)); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + for( i = 0; i < g->max_member; i++ ) + if( (pl_sd = g->member[i].sd) && !pc_isriding(pl_sd) ) + pc_disguise(pl_sd, id); + + clif_displaymessage(fd, msg_txt(122)); // Disguise applied. + return 0; +} + + +/*========================================== + * @undisguise by [Yor] + *------------------------------------------*/ +ACMD_FUNC(undisguise) +{ + nullpo_retr(-1, sd); + if (sd->disguise) { + pc_disguise(sd, 0); + clif_displaymessage(fd, msg_txt(124)); // Undisguise applied. + } else { + clif_displaymessage(fd, msg_txt(125)); // You're not disguised. + return -1; + } + + return 0; +} + +/*========================================== + * UndisguiseAll + *------------------------------------------*/ +ACMD_FUNC(undisguiseall) +{ + struct map_session_data *pl_sd; + struct s_mapiterator* iter; + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + if( pl_sd->disguise ) + pc_disguise(pl_sd, 0); + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(124)); // Undisguise applied. + + return 0; +} + +/*========================================== + * UndisguiseGuild + *------------------------------------------*/ +ACMD_FUNC(undisguiseguild) +{ + char guild_name[NAME_LENGTH]; + struct map_session_data *pl_sd; + struct guild *g; + int i; + nullpo_retr(-1, sd); + + memset(guild_name, '\0', sizeof(guild_name)); + + if(!message || !*message || sscanf(message, "%23[^\n]", guild_name) < 1) { + clif_displaymessage(fd, msg_txt(1147)); // Please enter guild name/ID (usage: @undisguiseguild <guild name/ID>). + return -1; + } + + if( (g = guild_searchname(guild_name)) == NULL && (g = guild_search(atoi(message))) == NULL ) { + clif_displaymessage(fd, msg_txt(94)); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + for(i = 0; i < g->max_member; i++) + if( (pl_sd = g->member[i].sd) && pl_sd->disguise ) + pc_disguise(pl_sd, 0); + + clif_displaymessage(fd, msg_txt(124)); // Undisguise applied. + + return 0; +} + +/*========================================== + * @exp by [Skotlex] + *------------------------------------------*/ +ACMD_FUNC(exp) +{ + char output[CHAT_SIZE_MAX]; + double nextb, nextj; + nullpo_retr(-1, sd); + memset(output, '\0', sizeof(output)); + + nextb = pc_nextbaseexp(sd); + if (nextb) + nextb = sd->status.base_exp*100.0/nextb; + + nextj = pc_nextjobexp(sd); + if (nextj) + nextj = sd->status.job_exp*100.0/nextj; + + sprintf(output, msg_txt(1148), sd->status.base_level, nextb, sd->status.job_level, nextj); // Base Level: %d (%.3f%%) | Job Level: %d (%.3f%%) + clif_displaymessage(fd, output); + return 0; +} + + +/*========================================== + * @broadcast by [Valaris] + *------------------------------------------*/ +ACMD_FUNC(broadcast) +{ + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1149)); // Please enter a message (usage: @broadcast <message>). + return -1; + } + + sprintf(atcmd_output, "%s: %s", sd->status.name, message); + intif_broadcast(atcmd_output, strlen(atcmd_output) + 1, 0); + + return 0; +} + +/*========================================== + * @localbroadcast by [Valaris] + *------------------------------------------*/ +ACMD_FUNC(localbroadcast) +{ + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1150)); // Please enter a message (usage: @localbroadcast <message>). + return -1; + } + + sprintf(atcmd_output, "%s: %s", sd->status.name, message); + + clif_broadcast(&sd->bl, atcmd_output, strlen(atcmd_output) + 1, 0, ALL_SAMEMAP); + + return 0; +} + +/*========================================== + * @email <actual@email> <new@email> by [Yor] + *------------------------------------------*/ +ACMD_FUNC(email) +{ + char actual_email[100]; + char new_email[100]; + nullpo_retr(-1, sd); + + memset(actual_email, '\0', sizeof(actual_email)); + memset(new_email, '\0', sizeof(new_email)); + + if (!message || !*message || sscanf(message, "%99s %99s", actual_email, new_email) < 2) { + clif_displaymessage(fd, msg_txt(1151)); // Please enter 2 emails (usage: @email <actual@email> <new@email>). + return -1; + } + + if (e_mail_check(actual_email) == 0) { + clif_displaymessage(fd, msg_txt(144)); // Invalid actual email. If you have default e-mail, give a@a.com. + return -1; + } else if (e_mail_check(new_email) == 0) { + clif_displaymessage(fd, msg_txt(145)); // Invalid new email. Please enter a real e-mail. + return -1; + } else if (strcmpi(new_email, "a@a.com") == 0) { + clif_displaymessage(fd, msg_txt(146)); // New email must be a real e-mail. + return -1; + } else if (strcmpi(actual_email, new_email) == 0) { + clif_displaymessage(fd, msg_txt(147)); // New email must be different of the actual e-mail. + return -1; + } + + chrif_changeemail(sd->status.account_id, actual_email, new_email); + clif_displaymessage(fd, msg_txt(148)); // Information sended to login-server via char-server. + return 0; +} + +/*========================================== + *@effect + *------------------------------------------*/ +ACMD_FUNC(effect) +{ + int type = 0, flag = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d", &type) < 1) { + clif_displaymessage(fd, msg_txt(1152)); // Please enter an effect number (usage: @effect <effect number>). + return -1; + } + + clif_specialeffect(&sd->bl, type, (send_target)flag); + clif_displaymessage(fd, msg_txt(229)); // Your effect has changed. + return 0; +} + +/*========================================== + * @killer by MouseJstr + * enable killing players even when not in pvp + *------------------------------------------*/ +ACMD_FUNC(killer) +{ + nullpo_retr(-1, sd); + sd->state.killer = !sd->state.killer; + + if(sd->state.killer) + clif_displaymessage(fd, msg_txt(241)); + else { + clif_displaymessage(fd, msg_txt(292)); + pc_stop_attack(sd); + } + return 0; +} + +/*========================================== + * @killable by MouseJstr + * enable other people killing you + *------------------------------------------*/ +ACMD_FUNC(killable) +{ + nullpo_retr(-1, sd); + sd->state.killable = !sd->state.killable; + + if(sd->state.killable) + clif_displaymessage(fd, msg_txt(242)); + else { + clif_displaymessage(fd, msg_txt(288)); + map_foreachinrange(atcommand_stopattack,&sd->bl, AREA_SIZE, BL_CHAR, sd->bl.id); + } + return 0; +} + +/*========================================== + * @skillon by MouseJstr + * turn skills on for the map + *------------------------------------------*/ +ACMD_FUNC(skillon) +{ + nullpo_retr(-1, sd); + map[sd->bl.m].flag.noskill = 0; + clif_displaymessage(fd, msg_txt(244)); + return 0; +} + +/*========================================== + * @skilloff by MouseJstr + * Turn skills off on the map + *------------------------------------------*/ +ACMD_FUNC(skilloff) +{ + nullpo_retr(-1, sd); + map[sd->bl.m].flag.noskill = 1; + clif_displaymessage(fd, msg_txt(243)); + return 0; +} + +/*========================================== + * @npcmove by MouseJstr + * move a npc + *------------------------------------------*/ +ACMD_FUNC(npcmove) +{ + int x = 0, y = 0, m; + struct npc_data *nd = 0; + nullpo_retr(-1, sd); + memset(atcmd_player_name, '\0', sizeof atcmd_player_name); + + if (!message || !*message || sscanf(message, "%d %d %23[^\n]", &x, &y, atcmd_player_name) < 3) { + clif_displaymessage(fd, msg_txt(1153)); // Usage: @npcmove <X> <Y> <npc_name> + return -1; + } + + if ((nd = npc_name2id(atcmd_player_name)) == NULL) + { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist. + return -1; + } + + if ((m=nd->bl.m) < 0 || nd->bl.prev == NULL) + { + clif_displaymessage(fd, msg_txt(1154)); // NPC is not on this map. + return -1; //Not on a map. + } + + x = cap_value(x, 0, map[m].xs-1); + y = cap_value(y, 0, map[m].ys-1); + map_foreachinrange(clif_outsight, &nd->bl, AREA_SIZE, BL_PC, &nd->bl); + map_moveblock(&nd->bl, x, y, gettick()); + map_foreachinrange(clif_insight, &nd->bl, AREA_SIZE, BL_PC, &nd->bl); + clif_displaymessage(fd, msg_txt(1155)); // NPC moved. + + return 0; +} + +/*========================================== + * @addwarp by MouseJstr + * Create a new static warp point. + *------------------------------------------*/ +ACMD_FUNC(addwarp) +{ + char mapname[32], warpname[NAME_LENGTH+1]; + int x,y; + unsigned short m; + struct npc_data* nd; + + nullpo_retr(-1, sd); + memset(warpname, '\0', sizeof(warpname)); + + if (!message || !*message || sscanf(message, "%31s %d %d %23[^\n]", mapname, &x, &y, warpname) < 4) { + clif_displaymessage(fd, msg_txt(1156)); // Usage: @addwarp <mapname> <X> <Y> <npc name> + return -1; + } + + m = mapindex_name2id(mapname); + if( m == 0 ) + { + sprintf(atcmd_output, msg_txt(1157), mapname); // Unknown map '%s'. + clif_displaymessage(fd, atcmd_output); + return -1; + } + + nd = npc_add_warp(warpname, sd->bl.m, sd->bl.x, sd->bl.y, 2, 2, m, x, y); + if( nd == NULL ) + return -1; + + sprintf(atcmd_output, msg_txt(1158), nd->exname); // New warp NPC '%s' created. + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * @follow by [MouseJstr] + * Follow a player .. staying no more then 5 spaces away + *------------------------------------------*/ +ACMD_FUNC(follow) +{ + struct map_session_data *pl_sd = NULL; + nullpo_retr(-1, sd); + + if (!message || !*message) { + if (sd->followtarget == -1) + return -1; + + pc_stop_following (sd); + clif_displaymessage(fd, msg_txt(1159)); // Follow mode OFF. + return 0; + } + + if ( (pl_sd = map_nick2sd((char *)message)) == NULL ) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if (sd->followtarget == pl_sd->bl.id) { + pc_stop_following (sd); + clif_displaymessage(fd, msg_txt(1159)); // Follow mode OFF. + } else { + pc_follow(sd, pl_sd->bl.id); + clif_displaymessage(fd, msg_txt(1160)); // Follow mode ON. + } + + return 0; +} + + +/*========================================== + * @dropall by [MouseJstr] + * Drop all your possession on the ground + *------------------------------------------*/ +ACMD_FUNC(dropall) +{ + int i; + nullpo_retr(-1, sd); + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 3); + pc_dropitem(sd, i, sd->status.inventory[i].amount); + } + } + return 0; +} + +/*========================================== + * @storeall by [MouseJstr] + * Put everything into storage + *------------------------------------------*/ +ACMD_FUNC(storeall) +{ + int i; + nullpo_retr(-1, sd); + + if (sd->state.storage_flag != 1) + { //Open storage. + if( storage_storageopen(sd) == 1 ) { + clif_displaymessage(fd, msg_txt(1161)); // You currently cannot open your storage. + return -1; + } + } + + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 3); + storage_storageadd(sd, i, sd->status.inventory[i].amount); + } + } + storage_storageclose(sd); + + clif_displaymessage(fd, msg_txt(1162)); // All items stored. + return 0; +} + +ACMD_FUNC(clearstorage) +{ + int i, j; + nullpo_retr(-1, sd); + + if (sd->state.storage_flag == 1) { + clif_displaymessage(fd, msg_txt(250)); + return -1; + } + + j = sd->status.storage.storage_amount; + for (i = 0; i < j; ++i) { + storage_delitem(sd, i, sd->status.storage.items[i].amount); + } + storage_storageclose(sd); + + clif_displaymessage(fd, msg_txt(1394)); // Your storage was cleaned. + return 0; +} + +ACMD_FUNC(cleargstorage) +{ + int i, j; + struct guild *g; + struct guild_storage *gstorage; + nullpo_retr(-1, sd); + + g = guild_search(sd->status.guild_id); + + if (g == NULL) { + clif_displaymessage(fd, msg_txt(43)); + return -1; + } + + if (sd->state.storage_flag == 1) { + clif_displaymessage(fd, msg_txt(250)); + return -1; + } + + if (sd->state.storage_flag == 2) { + clif_displaymessage(fd, msg_txt(251)); + return -1; + } + + gstorage = guild2storage2(sd->status.guild_id); + if (gstorage == NULL) {// Doesn't have opened @gstorage yet, so we skip the deletion since *shouldn't* have any item there. + return -1; + } + + j = gstorage->storage_amount; + gstorage->lock = 1; // Lock @gstorage: do not allow any item to be retrieved or stored from any guild member + for (i = 0; i < j; ++i) { + guild_storage_delitem(sd, gstorage, i, gstorage->items[i].amount); + } + storage_guild_storageclose(sd); + gstorage->lock = 0; // Cleaning done, release lock + + clif_displaymessage(fd, msg_txt(1395)); // Your guild storage was cleaned. + return 0; +} + +ACMD_FUNC(clearcart) +{ + int i; + nullpo_retr(-1, sd); + + if (pc_iscarton(sd) == 0) { + clif_displaymessage(fd, msg_txt(1396)); // You do not have a cart to be cleaned. + return -1; + } + + if (sd->state.vending == 1) { //Somehow... + return -1; + } + + for( i = 0; i < MAX_CART; i++ ) + if(sd->status.cart[i].nameid > 0) + pc_cart_delitem(sd, i, sd->status.cart[i].amount, 1, LOG_TYPE_OTHER); + + clif_clearcart(fd); + clif_updatestatus(sd,SP_CARTINFO); + + clif_displaymessage(fd, msg_txt(1397)); // Your cart was cleaned. + return 0; +} + +/*========================================== + * @skillid by [MouseJstr] + * lookup a skill by name + *------------------------------------------*/ +ACMD_FUNC(skillid) +{ + int skillen, idx; + nullpo_retr(-1, sd); + + if (!message || !*message) + { + clif_displaymessage(fd, msg_txt(1163)); // Please enter a skill name to look up (usage: @skillid <skill name>). + return -1; + } + + skillen = strlen(message); + + for (idx = 0; idx < MAX_SKILL_DB; idx++) { + if (strnicmp(skill_db[idx].name, message, skillen) == 0 || strnicmp(skill_db[idx].desc, message, skillen) == 0) + { + sprintf(atcmd_output, msg_txt(1164), idx, skill_db[idx].desc); // skill %d: %s + clif_displaymessage(fd, atcmd_output); + } + } + + return 0; +} + +/*========================================== + * @useskill by [MouseJstr] + * A way of using skills without having to find them in the skills menu + *------------------------------------------*/ +ACMD_FUNC(useskill) +{ + struct map_session_data *pl_sd = NULL; + struct block_list *bl; + uint16 skill_id; + uint16 skill_lv; + char target[100]; + nullpo_retr(-1, sd); + + if(!message || !*message || sscanf(message, "%hu %hu %23[^\n]", &skill_id, &skill_lv, target) != 3) { + clif_displaymessage(fd, msg_txt(1165)); // Usage: @useskill <skill ID> <skill level> <target> + return -1; + } + + if(!strcmp(target,"self")) pl_sd = sd; //quick keyword + else if ( (pl_sd = map_nick2sd(target)) == NULL ){ + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if ( pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + if (skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE+MAX_HOMUNSKILL + && sd->hd && merc_is_hom_active(sd->hd)) // (If used with @useskill, put the homunc as dest) + bl = &sd->hd->bl; + else + bl = &sd->bl; + + if (skill_get_inf(skill_id)&INF_GROUND_SKILL) + unit_skilluse_pos(bl, pl_sd->bl.x, pl_sd->bl.y, skill_id, skill_lv); + else + unit_skilluse_id(bl, pl_sd->bl.id, skill_id, skill_lv); + + return 0; +} + +/*========================================== + * @displayskill by [Skotlex] + * Debug command to locate new skill IDs. It sends the + * three possible skill-effect packets to the area. + *------------------------------------------*/ +ACMD_FUNC(displayskill) +{ + struct status_data * status; + unsigned int tick; + uint16 skill_id; + uint16 skill_lv = 1; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%hu %hu", &skill_id, &skill_lv) < 1) + { + clif_displaymessage(fd, msg_txt(1166)); // Usage: @displayskill <skill ID> {<skill level>} + return -1; + } + status = status_get_status_data(&sd->bl); + tick = gettick(); + clif_skill_damage(&sd->bl,&sd->bl, tick, status->amotion, status->dmotion, 1, 1, skill_id, skill_lv, 5); + clif_skill_nodamage(&sd->bl, &sd->bl, skill_id, skill_lv, 1); + clif_skill_poseffect(&sd->bl, skill_id, skill_lv, sd->bl.x, sd->bl.y, tick); + return 0; +} + +/*========================================== + * @skilltree by [MouseJstr] + * prints the skill tree for a player required to get to a skill + *------------------------------------------*/ +ACMD_FUNC(skilltree) +{ + struct map_session_data *pl_sd = NULL; + uint16 skill_id; + int meets, j, c=0; + char target[NAME_LENGTH]; + struct skill_tree_entry *ent; + nullpo_retr(-1, sd); + + if(!message || !*message || sscanf(message, "%hu %23[^\r\n]", &skill_id, target) != 2) { + clif_displaymessage(fd, msg_txt(1167)); // Usage: @skilltree <skill ID> <target> + return -1; + } + + if ( (pl_sd = map_nick2sd(target)) == NULL ) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + c = pc_calc_skilltree_normalize_job(pl_sd); + c = pc_mapid2jobid(c, pl_sd->status.sex); + + sprintf(atcmd_output, msg_txt(1168), job_name(c), pc_checkskill(pl_sd, NV_BASIC)); // Player is using %s skill tree (%d basic points). + clif_displaymessage(fd, atcmd_output); + + ARR_FIND( 0, MAX_SKILL_TREE, j, skill_tree[c][j].id == 0 || skill_tree[c][j].id == skill_id ); + if( j == MAX_SKILL_TREE || skill_tree[c][j].id == 0 ) + { + clif_displaymessage(fd, msg_txt(1169)); // The player cannot use that skill. + return 0; + } + + ent = &skill_tree[c][j]; + + meets = 1; + for(j=0;j<MAX_PC_SKILL_REQUIRE;j++) + { + if( ent->need[j].id && pc_checkskill(sd,ent->need[j].id) < ent->need[j].lv) + { + sprintf(atcmd_output, msg_txt(1170), ent->need[j].lv, skill_db[ent->need[j].id].desc); // Player requires level %d of skill %s. + clif_displaymessage(fd, atcmd_output); + meets = 0; + } + } + if (meets == 1) { + clif_displaymessage(fd, msg_txt(1171)); // The player meets all the requirements for that skill. + } + + return 0; +} + +// Hand a ring with partners name on it to this char +void getring (struct map_session_data* sd) +{ + int flag, item_id; + struct item item_tmp; + item_id = (sd->status.sex) ? WEDDING_RING_M : WEDDING_RING_F; + + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = 1; + item_tmp.card[0] = 255; + item_tmp.card[2] = sd->status.partner_id; + item_tmp.card[3] = sd->status.partner_id >> 16; + + if((flag = pc_additem(sd,&item_tmp,1,LOG_TYPE_COMMAND))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } +} + +/*========================================== + * @marry by [MouseJstr], fixed by Lupus + * Marry two players + *------------------------------------------*/ +ACMD_FUNC(marry) +{ + struct map_session_data *pl_sd = NULL; + char player_name[NAME_LENGTH] = ""; + + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%23s", player_name) != 1) { + clif_displaymessage(fd, msg_txt(1172)); // Usage: @marry <char name> + return -1; + } + + if ((pl_sd = map_nick2sd(player_name)) == NULL) { + clif_displaymessage(fd, msg_txt(3)); + return -1; + } + + if (pc_marriage(sd, pl_sd) == 0) { + clif_displaymessage(fd, msg_txt(1173)); // They are married... wish them well. + clif_wedding_effect(&pl_sd->bl); //wedding effect and music [Lupus] + getring(sd); // Auto-give named rings (Aru) + getring(pl_sd); + return 0; + } + + clif_displaymessage(fd, msg_txt(1174)); // The two cannot wed because one is either a baby or already married. + return -1; +} + +/*========================================== + * @divorce by [MouseJstr], fixed by [Lupus] + * divorce two players + *------------------------------------------*/ +ACMD_FUNC(divorce) +{ + nullpo_retr(-1, sd); + + if (pc_divorce(sd) != 0) { + sprintf(atcmd_output, msg_txt(1175), sd->status.name); // '%s' is not married. + clif_displaymessage(fd, atcmd_output); + return -1; + } + + sprintf(atcmd_output, msg_txt(1176), sd->status.name); // '%s' and his/her partner are now divorced. + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * @changelook by [Celest] + *------------------------------------------*/ +ACMD_FUNC(changelook) +{ + int i, j = 0, k = 0; + int pos[7] = { LOOK_HEAD_TOP,LOOK_HEAD_MID,LOOK_HEAD_BOTTOM,LOOK_WEAPON,LOOK_SHIELD,LOOK_SHOES,LOOK_ROBE }; + + if((i = sscanf(message, "%d %d", &j, &k)) < 1) { + clif_displaymessage(fd, msg_txt(1177)); // Usage: @changelook {<position>} <view id> + clif_displaymessage(fd, msg_txt(1178)); // Position: 1-Top 2-Middle 3-Bottom 4-Weapon 5-Shield 6-Shoes 7-Robe + return -1; + } else if ( i == 2 ) { + if (j < 1 || j > 7) + j = 1; + j = pos[j - 1]; + } else if( i == 1 ) { // position not defined, use HEAD_TOP as default + k = j; // swap + j = LOOK_HEAD_TOP; + } + + clif_changelook(&sd->bl,j,k); + + return 0; +} + +/*========================================== + * @autotrade by durf [Lupus] [Paradox924X] + * Turns on/off Autotrade for a specific player + *------------------------------------------*/ +ACMD_FUNC(autotrade) +{ + nullpo_retr(-1, sd); + + if( map[sd->bl.m].flag.autotrade != battle_config.autotrade_mapflag ) { + clif_displaymessage(fd, msg_txt(1179)); // Autotrade is not allowed on this map. + return -1; + } + + if( pc_isdead(sd) ) { + clif_displaymessage(fd, msg_txt(1180)); // You cannot autotrade when dead. + return -1; + } + + if( !sd->state.vending && !sd->state.buyingstore ) { //check if player is vending or buying + clif_displaymessage(fd, msg_txt(549)); // "You should have a shop open to use @autotrade." + return -1; + } + + sd->state.autotrade = 1; + if( battle_config.at_timeout ) + { + int timeout = atoi(message); + status_change_start(&sd->bl, SC_AUTOTRADE, 10000, 0, 0, 0, 0, ((timeout > 0) ? min(timeout,battle_config.at_timeout) : battle_config.at_timeout) * 60000, 0); + } + clif_authfail_fd(fd, 15); + + return 0; +} + +/*========================================== + * @changegm by durf (changed by Lupus) + * Changes Master of your Guild to a specified guild member + *------------------------------------------*/ +ACMD_FUNC(changegm) +{ + struct guild *g; + struct map_session_data *pl_sd; + nullpo_retr(-1, sd); + + if (sd->status.guild_id == 0 || (g = guild_search(sd->status.guild_id)) == NULL || strcmp(g->master,sd->status.name)) { + clif_displaymessage(fd, msg_txt(1181)); // You need to be a Guild Master to use this command. + return -1; + } + + if( map[sd->bl.m].flag.guildlock || map[sd->bl.m].flag.gvg_castle ) { + clif_displaymessage(fd, msg_txt(1182)); // You cannot change guild leaders on this map. + return -1; + } + + if( !message[0] ) { + clif_displaymessage(fd, msg_txt(1183)); // Usage: @changegm <guild_member_name> + return -1; + } + + if((pl_sd=map_nick2sd((char *) message)) == NULL || pl_sd->status.guild_id != sd->status.guild_id) { + clif_displaymessage(fd, msg_txt(1184)); // Target character must be online and be a guild member. + return -1; + } + + guild_gm_change(sd->status.guild_id, pl_sd); + return 0; +} + +/*========================================== + * @changeleader by Skotlex + * Changes the leader of a party. + *------------------------------------------*/ +ACMD_FUNC(changeleader) +{ + nullpo_retr(-1, sd); + + if( !message[0] ) + { + clif_displaymessage(fd, msg_txt(1185)); // Usage: @changeleader <party_member_name> + return -1; + } + + if (party_changeleader(sd, map_nick2sd((char *) message))) + return 0; + return -1; +} + +/*========================================== + * @partyoption by Skotlex + * Used to change the item share setting of a party. + *------------------------------------------*/ +ACMD_FUNC(partyoption) +{ + struct party_data *p; + int mi, option; + char w1[16], w2[16]; + nullpo_retr(-1, sd); + + if (sd->status.party_id == 0 || (p = party_search(sd->status.party_id)) == NULL) + { + clif_displaymessage(fd, msg_txt(282)); + return -1; + } + + ARR_FIND( 0, MAX_PARTY, mi, p->data[mi].sd == sd ); + if (mi == MAX_PARTY) + return -1; //Shouldn't happen + + if (!p->party.member[mi].leader) + { + clif_displaymessage(fd, msg_txt(282)); + return -1; + } + + if(!message || !*message || sscanf(message, "%15s %15s", w1, w2) < 2) + { + clif_displaymessage(fd, msg_txt(1186)); // Usage: @partyoption <pickup share: yes/no> <item distribution: yes/no> + return -1; + } + + option = (config_switch(w1)?1:0)|(config_switch(w2)?2:0); + + //Change item share type. + if (option != p->party.item) + party_changeoption(sd, p->party.exp, option); + else + clif_displaymessage(fd, msg_txt(286)); + + return 0; +} + +/*========================================== + * @autoloot by Upa-Kun + * Turns on/off AutoLoot for a specific player + *------------------------------------------*/ +ACMD_FUNC(autoloot) +{ + int rate; + double drate; + nullpo_retr(-1, sd); + // autoloot command without value + if(!message || !*message) + { + if (sd->state.autoloot) + rate = 0; + else + rate = 10000; + } else { + drate = atof(message); + rate = (int)(drate*100); + } + if (rate < 0) rate = 0; + if (rate > 10000) rate = 10000; + + sd->state.autoloot = rate; + if (sd->state.autoloot) { + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1187),((double)sd->state.autoloot)/100.); // Autolooting items with drop rates of %0.02f%% and below. + clif_displaymessage(fd, atcmd_output); + }else + clif_displaymessage(fd, msg_txt(1188)); // Autoloot is now off. + + return 0; +} + +/*========================================== + * @alootid + *------------------------------------------*/ +ACMD_FUNC(autolootitem) +{ + struct item_data *item_data = NULL; + int i; + int action = 3; // 1=add, 2=remove, 3=help+list (default), 4=reset + + if (message && *message) { + if (message[0] == '+') { + message++; + action = 1; + } + else if (message[0] == '-') { + message++; + action = 2; + } + else if (!strcmp(message,"reset")) + action = 4; + } + + if (action < 3) // add or remove + { + if ((item_data = itemdb_exists(atoi(message))) == NULL) + item_data = itemdb_searchname(message); + if (!item_data) { + // No items founds in the DB with Id or Name + clif_displaymessage(fd, msg_txt(1189)); // Item not found. + return -1; + } + } + + switch(action) { + case 1: + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] == item_data->nameid); + if (i != AUTOLOOTITEM_SIZE) { + clif_displaymessage(fd, msg_txt(1190)); // You're already autolooting this item. + return -1; + } + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] == 0); + if (i == AUTOLOOTITEM_SIZE) { + clif_displaymessage(fd, msg_txt(1191)); // Your autolootitem list is full. Remove some items first with @autolootid -<item name or ID>. + return -1; + } + sd->state.autolootid[i] = item_data->nameid; // Autoloot Activated + sprintf(atcmd_output, msg_txt(1192), item_data->name, item_data->jname, item_data->nameid); // Autolooting item: '%s'/'%s' {%d} + clif_displaymessage(fd, atcmd_output); + sd->state.autolooting = 1; + break; + case 2: + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] == item_data->nameid); + if (i == AUTOLOOTITEM_SIZE) { + clif_displaymessage(fd, msg_txt(1193)); // You're currently not autolooting this item. + return -1; + } + sd->state.autolootid[i] = 0; + sprintf(atcmd_output, msg_txt(1194), item_data->name, item_data->jname, item_data->nameid); // Removed item: '%s'/'%s' {%d} from your autolootitem list. + clif_displaymessage(fd, atcmd_output); + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] != 0); + if (i == AUTOLOOTITEM_SIZE) { + sd->state.autolooting = 0; + } + break; + case 3: + sprintf(atcmd_output, msg_txt(1195), AUTOLOOTITEM_SIZE); // You can have %d items on your autolootitem list. + clif_displaymessage(fd, atcmd_output); + clif_displaymessage(fd, msg_txt(1196)); // To add an item to the list, use "@alootid +<item name or ID>". To remove an item, use "@alootid -<item name or ID>". + clif_displaymessage(fd, msg_txt(1197)); // "@alootid reset" will clear your autolootitem list. + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] != 0); + if (i == AUTOLOOTITEM_SIZE) { + clif_displaymessage(fd, msg_txt(1198)); // Your autolootitem list is empty. + } else { + clif_displaymessage(fd, msg_txt(1199)); // Items on your autolootitem list: + for(i = 0; i < AUTOLOOTITEM_SIZE; i++) + { + if (sd->state.autolootid[i] == 0) + continue; + if (!(item_data = itemdb_exists(sd->state.autolootid[i]))) { + ShowDebug("Non-existant item %d on autolootitem list (account_id: %d, char_id: %d)", sd->state.autolootid[i], sd->status.account_id, sd->status.char_id); + continue; + } + sprintf(atcmd_output, "'%s'/'%s' {%d}", item_data->name, item_data->jname, item_data->nameid); + clif_displaymessage(fd, atcmd_output); + } + } + break; + case 4: + memset(sd->state.autolootid, 0, sizeof(sd->state.autolootid)); + clif_displaymessage(fd, msg_txt(1200)); // Your autolootitem list has been reset. + sd->state.autolooting = 0; + break; + } + return 0; +} +/** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ +/*========================================== + * It is made to rain. + *------------------------------------------*/ +//ACMD_FUNC(rain) +//{ +// nullpo_retr(-1, sd); +// if (map[sd->bl.m].flag.rain) { +// map[sd->bl.m].flag.rain=0; +// clif_weather(sd->bl.m); +// clif_displaymessage(fd, msg_txt(1201)); // The rain has stopped. +// } else { +// map[sd->bl.m].flag.rain=1; +// clif_weather(sd->bl.m); +// clif_displaymessage(fd, msg_txt(1202)); // It has started to rain. +// } +// return 0; +//} + +/*========================================== + * It is made to snow. + *------------------------------------------*/ +ACMD_FUNC(snow) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.snow) { + map[sd->bl.m].flag.snow=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1203)); // Snow has stopped falling. + } else { + map[sd->bl.m].flag.snow=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1204)); // It has started to snow. + } + + return 0; +} + +/*========================================== + * Cherry tree snowstorm is made to fall. (Sakura) + *------------------------------------------*/ +ACMD_FUNC(sakura) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.sakura) { + map[sd->bl.m].flag.sakura=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1205)); // Cherry tree leaves no longer fall. + } else { + map[sd->bl.m].flag.sakura=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1206)); // Cherry tree leaves have begun to fall. + } + return 0; +} + +/*========================================== + * Clouds appear. + *------------------------------------------*/ +ACMD_FUNC(clouds) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.clouds) { + map[sd->bl.m].flag.clouds=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1207)); // The clouds has disappear. + } else { + map[sd->bl.m].flag.clouds=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1208)); // Clouds appear. + } + + return 0; +} + +/*========================================== + * Different type of clouds using effect 516 + *------------------------------------------*/ +ACMD_FUNC(clouds2) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.clouds2) { + map[sd->bl.m].flag.clouds2=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1209)); // The alternative clouds disappear. + } else { + map[sd->bl.m].flag.clouds2=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1210)); // Alternative clouds appear. + } + + return 0; +} + +/*========================================== + * Fog hangs over. + *------------------------------------------*/ +ACMD_FUNC(fog) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.fog) { + map[sd->bl.m].flag.fog=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1211)); // The fog has gone. + } else { + map[sd->bl.m].flag.fog=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1212)); // Fog hangs over. + } + return 0; +} + +/*========================================== + * Fallen leaves fall. + *------------------------------------------*/ +ACMD_FUNC(leaves) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.leaves) { + map[sd->bl.m].flag.leaves=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1213)); // Leaves no longer fall. + } else { + map[sd->bl.m].flag.leaves=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1214)); // Fallen leaves fall. + } + + return 0; +} + +/*========================================== + * Fireworks appear. + *------------------------------------------*/ +ACMD_FUNC(fireworks) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.fireworks) { + map[sd->bl.m].flag.fireworks=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1215)); // Fireworks have ended. + } else { + map[sd->bl.m].flag.fireworks=1; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(1216)); // Fireworks have launched. + } + + return 0; +} + +/*========================================== + * Clearing Weather Effects by Dexity + *------------------------------------------*/ +ACMD_FUNC(clearweather) +{ + nullpo_retr(-1, sd); + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //map[sd->bl.m].flag.rain=0; + map[sd->bl.m].flag.snow=0; + map[sd->bl.m].flag.sakura=0; + map[sd->bl.m].flag.clouds=0; + map[sd->bl.m].flag.clouds2=0; + map[sd->bl.m].flag.fog=0; + map[sd->bl.m].flag.fireworks=0; + map[sd->bl.m].flag.leaves=0; + clif_weather(sd->bl.m); + clif_displaymessage(fd, msg_txt(291)); + + return 0; +} + +/*=============================================================== + * Sound Command - plays a sound for everyone around! [Codemaster] + *---------------------------------------------------------------*/ +ACMD_FUNC(sound) +{ + char sound_file[100]; + + memset(sound_file, '\0', sizeof(sound_file)); + + if(!message || !*message || sscanf(message, "%99[^\n]", sound_file) < 1) { + clif_displaymessage(fd, msg_txt(1217)); // Please enter a sound filename (usage: @sound <filename>). + return -1; + } + + if(strstr(sound_file, ".wav") == NULL) + strcat(sound_file, ".wav"); + + clif_soundeffectall(&sd->bl, sound_file, 0, AREA); + + return 0; +} + +/*========================================== + * MOB Search + *------------------------------------------*/ +ACMD_FUNC(mobsearch) +{ + char mob_name[100]; + int mob_id; + int number = 0; + struct s_mapiterator* it; + + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%99[^\n]", mob_name) < 1) { + clif_displaymessage(fd, msg_txt(1218)); // Please enter a monster name (usage: @mobsearch <monster name>). + return -1; + } + + if ((mob_id = atoi(mob_name)) == 0) + mob_id = mobdb_searchname(mob_name); + if(mob_id > 0 && mobdb_checkid(mob_id) == 0){ + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1219),mob_name); // Invalid mob ID %s! + clif_displaymessage(fd, atcmd_output); + return -1; + } + if(mob_id == atoi(mob_name) && mob_db(mob_id)->jname) + strcpy(mob_name,mob_db(mob_id)->jname); // --ja-- +// strcpy(mob_name,mob_db(mob_id)->name); // --en-- + + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1220), mob_name, mapindex_id2name(sd->mapindex)); // Mob Search... %s %s + clif_displaymessage(fd, atcmd_output); + + it = mapit_geteachmob(); + for(;;) + { + TBL_MOB* md = (TBL_MOB*)mapit_next(it); + if( md == NULL ) + break;// no more mobs + + if( md->bl.m != sd->bl.m ) + continue; + if( mob_id != -1 && md->class_ != mob_id ) + continue; + + ++number; + if( md->spawn_timer == INVALID_TIMER ) + snprintf(atcmd_output, sizeof(atcmd_output), "%2d[%3d:%3d] %s", number, md->bl.x, md->bl.y, md->name); + else + snprintf(atcmd_output, sizeof(atcmd_output), "%2d[%s] %s", number, "dead", md->name); + clif_displaymessage(fd, atcmd_output); + } + mapit_free(it); + + return 0; +} + +/*========================================== + * @cleanmap - cleans items on the ground + * @cleanarea - cleans items on the ground within an specified area + *------------------------------------------*/ +static int atcommand_cleanfloor_sub(struct block_list *bl, va_list ap) +{ + nullpo_ret(bl); + map_clearflooritem(bl); + + return 0; +} + +ACMD_FUNC(cleanmap) +{ + map_foreachinmap(atcommand_cleanfloor_sub, sd->bl.m, BL_ITEM); + clif_displaymessage(fd, msg_txt(1221)); // All dropped items have been cleaned up. + return 0; +} + +ACMD_FUNC(cleanarea) +{ + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + + if (!message || !*message || sscanf(message, "%d %d %d %d", &x0, &y0, &x1, &y1) < 1) { + map_foreachinarea(atcommand_cleanfloor_sub, sd->bl.m, sd->bl.x - (AREA_SIZE * 2), sd->bl.y - (AREA_SIZE * 2), sd->bl.x + (AREA_SIZE * 2), sd->bl.y + (AREA_SIZE * 2), BL_ITEM); + } + else if (sscanf(message, "%d %d %d %d", &x0, &y0, &x1, &y1) == 1) { + map_foreachinarea(atcommand_cleanfloor_sub, sd->bl.m, sd->bl.x - x0, sd->bl.y - x0, sd->bl.x + x0, sd->bl.y + x0, BL_ITEM); + } + else if (sscanf(message, "%d %d %d %d", &x0, &y0, &x1, &y1) == 4) { + map_foreachinarea(atcommand_cleanfloor_sub, sd->bl.m, x0, y0, x1, y1, BL_ITEM); + } + + clif_displaymessage(fd, msg_txt(1221)); // All dropped items have been cleaned up. + return 0; +} + +/*========================================== + * make a NPC/PET talk + * @npctalkc [SnakeDrak] + *------------------------------------------*/ +ACMD_FUNC(npctalk) +{ + char name[NAME_LENGTH],mes[100],temp[100]; + struct npc_data *nd; + bool ifcolor=(*(command + 8) != 'c' && *(command + 8) != 'C')?0:1; + unsigned long color=0; + + if (sd->sc.count && //no "chatting" while muted. + (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT))) + return -1; + + if(!ifcolor) { + if (!message || !*message || sscanf(message, "%23[^,], %99[^\n]", name, mes) < 2) { + clif_displaymessage(fd, msg_txt(1222)); // Please enter the correct parameters (usage: @npctalk <npc name>, <message>). + return -1; + } + } + else { + if (!message || !*message || sscanf(message, "%lx %23[^,], %99[^\n]", &color, name, mes) < 3) { + clif_displaymessage(fd, msg_txt(1223)); // Please enter the correct parameters (usage: @npctalkc <color> <npc name>, <message>). + return -1; + } + } + + if (!(nd = npc_name2id(name))) { + clif_displaymessage(fd, msg_txt(111)); // This NPC doesn't exist + return -1; + } + + strtok(name, "#"); // discard extra name identifier if present + snprintf(temp, sizeof(temp), "%s : %s", name, mes); + + if(ifcolor) clif_messagecolor(&nd->bl,color,temp); + else clif_message(&nd->bl, temp); + + return 0; +} + +ACMD_FUNC(pettalk) +{ + char mes[100],temp[100]; + struct pet_data *pd; + + nullpo_retr(-1, sd); + + if ( battle_config.min_chat_delay ) { + if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) + return 0; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + if(!sd->status.pet_id || !(pd=sd->pd)) + { + clif_displaymessage(fd, msg_txt(184)); + return -1; + } + + if (sd->sc.count && //no "chatting" while muted. + (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT))) + return -1; + + if (!message || !*message || sscanf(message, "%99[^\n]", mes) < 1) { + clif_displaymessage(fd, msg_txt(1224)); // Please enter a message (usage: @pettalk <message>). + return -1; + } + + if (message[0] == '/') + {// pet emotion processing + const char* emo[] = { + "/!", "/?", "/ho", "/lv", "/swt", "/ic", "/an", "/ag", "/$", "/...", + "/scissors", "/rock", "/paper", "/korea", "/lv2", "/thx", "/wah", "/sry", "/heh", "/swt2", + "/hmm", "/no1", "/??", "/omg", "/O", "/X", "/hlp", "/go", "/sob", "/gg", + "/kis", "/kis2", "/pif", "/ok", "-?-", "/indonesia", "/bzz", "/rice", "/awsm", "/meh", + "/shy", "/pat", "/mp", "/slur", "/com", "/yawn", "/grat", "/hp", "/philippines", "/malaysia", + "/singapore", "/brazil", "/fsh", "/spin", "/sigh", "/dum", "/crwd", "/desp", "/dice", "-dice2", + "-dice3", "-dice4", "-dice5", "-dice6", "/india", "/love", "/russia", "-?-", "/mobile", "/mail", + "/chinese", "/antenna1", "/antenna2", "/antenna3", "/hum", "/abs", "/oops", "/spit", "/ene", "/panic", + "/whisp" + }; + int i; + ARR_FIND( 0, ARRAYLENGTH(emo), i, stricmp(message, emo[i]) == 0 ); + if( i == E_DICE1 ) i = rnd()%6 + E_DICE1; // randomize /dice + if( i < ARRAYLENGTH(emo) ) + { + if (sd->emotionlasttime + 1 >= time(NULL)) { // not more than 1 per second + sd->emotionlasttime = time(NULL); + return 0; + } + sd->emotionlasttime = time(NULL); + + clif_emotion(&pd->bl, i); + return 0; + } + } + + snprintf(temp, sizeof temp ,"%s : %s", pd->pet.name, mes); + clif_message(&pd->bl, temp); + + return 0; +} + +/// @users - displays the number of players present on each map (and percentage) +/// #users displays on the target user instead of self +ACMD_FUNC(users) +{ + char buf[CHAT_SIZE_MAX]; + int i; + int users[MAX_MAPINDEX]; + int users_all; + struct s_mapiterator* iter; + + memset(users, 0, sizeof(users)); + users_all = 0; + + // count users on each map + iter = mapit_getallusers(); + for(;;) + { + struct map_session_data* sd2 = (struct map_session_data*)mapit_next(iter); + if( sd2 == NULL ) + break;// no more users + + if( sd2->mapindex >= MAX_MAPINDEX ) + continue;// invalid mapindex + + if( users[sd2->mapindex] < INT_MAX ) ++users[sd2->mapindex]; + if( users_all < INT_MAX ) ++users_all; + } + mapit_free(iter); + + // display results for each map + for( i = 0; i < MAX_MAPINDEX; ++i ) + { + if( users[i] == 0 ) + continue;// empty + + safesnprintf(buf, sizeof(buf), "%s: %d (%.2f%%)", mapindex_id2name(i), users[i], (float)(100.0f*users[i]/users_all)); + clif_displaymessage(sd->fd, buf); + } + + // display overall count + safesnprintf(buf, sizeof(buf), "all: %d", users_all); + clif_displaymessage(sd->fd, buf); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(reset) +{ + pc_resetstate(sd); + pc_resetskill(sd,1); + sprintf(atcmd_output, msg_txt(208), sd->status.name); // '%s' skill and stats points reseted! + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD_FUNC(summon) +{ + char name[NAME_LENGTH]; + int mob_id = 0; + int duration = 0; + struct mob_data *md; + unsigned int tick=gettick(); + + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%23s %d", name, &duration) < 1) + { + clif_displaymessage(fd, msg_txt(1225)); // Please enter a monster name (usage: @summon <monster name> {duration}). + return -1; + } + + if (duration < 1) + duration =1; + else if (duration > 60) + duration =60; + + if ((mob_id = atoi(name)) == 0) + mob_id = mobdb_searchname(name); + if(mob_id == 0 || mobdb_checkid(mob_id) == 0) + { + clif_displaymessage(fd, msg_txt(40)); // Invalid monster ID or name. + return -1; + } + + md = mob_once_spawn_sub(&sd->bl, sd->bl.m, -1, -1, "--ja--", mob_id, "", SZ_SMALL, AI_NONE); + + if(!md) + return -1; + + md->master_id=sd->bl.id; + md->special_state.ai=1; + md->deletetimer=add_timer(tick+(duration*60000),mob_timer_delete,md->bl.id,0); + clif_specialeffect(&md->bl,344,AREA); + mob_spawn(md); + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000); + clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,md->bl.x,md->bl.y,tick); + clif_displaymessage(fd, msg_txt(39)); // All monster summoned! + + return 0; +} + +/*========================================== + * @adjgroup + * Temporarily move player to another group + * Useful during beta testing to allow players to use GM commands for short periods of time + *------------------------------------------*/ +ACMD_FUNC(adjgroup) +{ + int new_group = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d", &new_group) != 1) { + clif_displaymessage(fd, msg_txt(1226)); // Usage: @adjgroup <group_id> + return -1; + } + + if (!pc_group_exists(new_group)) { + clif_displaymessage(fd, msg_txt(1227)); // Specified group does not exist. + return -1; + } + + sd->group_id = new_group; + pc_group_pc_load(sd);/* update cache */ + clif_displaymessage(fd, msg_txt(1228)); // Group changed successfully. + clif_displaymessage(sd->fd, msg_txt(1229)); // Your group has changed. + return 0; +} + +/*========================================== + * @trade by [MouseJstr] + * Open a trade window with a remote player + *------------------------------------------*/ +ACMD_FUNC(trade) +{ + struct map_session_data *pl_sd = NULL; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1230)); // Please enter a player name (usage: @trade <char name>). + return -1; + } + + if ( (pl_sd = map_nick2sd((char *)message)) == NULL ) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + trade_traderequest(sd, pl_sd); + return 0; +} + +/*========================================== + * @setbattleflag by [MouseJstr] + * set a battle_config flag without having to reboot + *------------------------------------------*/ +ACMD_FUNC(setbattleflag) +{ + char flag[128], value[128]; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%127s %127s", flag, value) != 2) { + clif_displaymessage(fd, msg_txt(1231)); // Usage: @setbattleflag <flag> <value> + return -1; + } + + if (battle_set_value(flag, value) == 0) + { + clif_displaymessage(fd, msg_txt(1232)); // Unknown battle_config flag. + return -1; + } + + clif_displaymessage(fd, msg_txt(1233)); // Set battle_config as requested. + + return 0; +} + +/*========================================== + * @unmute [Valaris] + *------------------------------------------*/ +ACMD_FUNC(unmute) +{ + struct map_session_data *pl_sd = NULL; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1234)); // Please enter a player name (usage: @unmute <char name>). + return -1; + } + + if ( (pl_sd = map_nick2sd((char *)message)) == NULL ) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if(!pl_sd->sc.data[SC_NOCHAT]) { + clif_displaymessage(sd->fd,msg_txt(1235)); // Player is not muted. + return -1; + } + + pl_sd->status.manner = 0; + status_change_end(&pl_sd->bl, SC_NOCHAT, INVALID_TIMER); + clif_displaymessage(sd->fd,msg_txt(1236)); // Player unmuted. + + return 0; +} + +/*========================================== + * @uptime by MC Cameri + *------------------------------------------*/ +ACMD_FUNC(uptime) +{ + unsigned long seconds = 0, day = 24*60*60, hour = 60*60, + minute = 60, days = 0, hours = 0, minutes = 0; + nullpo_retr(-1, sd); + + seconds = get_uptime(); + days = seconds/day; + seconds -= (seconds/day>0)?(seconds/day)*day:0; + hours = seconds/hour; + seconds -= (seconds/hour>0)?(seconds/hour)*hour:0; + minutes = seconds/minute; + seconds -= (seconds/minute>0)?(seconds/minute)*minute:0; + + snprintf(atcmd_output, sizeof(atcmd_output), msg_txt(245), days, hours, minutes, seconds); + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * @changesex <sex> + * => Changes one's sex. Argument sex can be 0 or 1, m or f, male or female. + *------------------------------------------*/ +ACMD_FUNC(changesex) +{ + int i; + nullpo_retr(-1, sd); + pc_resetskill(sd,4); + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for( i=0; i<EQI_MAX; i++ ) + if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3); + chrif_changesex(sd); + return 0; +} + +/*================================================ + * @mute - Mutes a player for a set amount of time + *------------------------------------------------*/ +ACMD_FUNC(mute) +{ + struct map_session_data *pl_sd = NULL; + int manner; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d %23[^\n]", &manner, atcmd_player_name) < 1) { + clif_displaymessage(fd, msg_txt(1237)); // Usage: @mute <time> <char name> + return -1; + } + + if ( (pl_sd = map_nick2sd(atcmd_player_name)) == NULL ) + { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return -1; + } + + if ( pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + clif_displaymessage(fd, msg_txt(81)); // Your GM level don't authorise you to do this action on this player. + return -1; + } + + clif_manner_message(sd, 0); + clif_manner_message(pl_sd, 5); + + if( pl_sd->status.manner < manner ) { + pl_sd->status.manner -= manner; + sc_start(&pl_sd->bl,SC_NOCHAT,100,0,0); + } else { + pl_sd->status.manner = 0; + status_change_end(&pl_sd->bl, SC_NOCHAT, INVALID_TIMER); + } + + clif_GM_silence(sd, pl_sd, (manner > 0 ? 1 : 0)); + + return 0; +} + +/*========================================== + * @refresh (like @jumpto <<yourself>>) + *------------------------------------------*/ +ACMD_FUNC(refresh) +{ + nullpo_retr(-1, sd); + clif_refresh(sd); + return 0; +} + +ACMD_FUNC(refreshall) +{ + struct map_session_data* iter_sd; + struct s_mapiterator* iter; + nullpo_retr(-1, sd); + + iter = mapit_getallusers(); + for (iter_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); iter_sd = (TBL_PC*)mapit_next(iter)) + clif_refresh(iter_sd); + mapit_free(iter); + return 0; +} + +/*========================================== + * @identify + * => GM's magnifier. + *------------------------------------------*/ +ACMD_FUNC(identify) +{ + int i,num; + + nullpo_retr(-1, sd); + + for(i=num=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].identify!=1){ + num++; + } + } + if (num > 0) { + clif_item_identify_list(sd); + } else { + clif_displaymessage(fd,msg_txt(1238)); // There are no items to appraise. + } + return 0; +} + +/*========================================== + * @gmotd (Global MOTD) + * by davidsiaw :P + *------------------------------------------*/ +ACMD_FUNC(gmotd) +{ + char buf[CHAT_SIZE_MAX]; + size_t len; + FILE* fp; + + if( ( fp = fopen(motd_txt, "r") ) != NULL ) + { + while( fgets(buf, sizeof(buf), fp) ) + { + if( buf[0] == '/' && buf[1] == '/' ) + { + continue; + } + + len = strlen(buf); + + while( len && ( buf[len-1] == '\r' || buf[len-1] == '\n' ) ) + {// strip trailing EOL characters + len--; + } + + if( len ) + { + buf[len] = 0; + + intif_broadcast(buf, len+1, 0); + } + } + fclose(fp); + } + return 0; +} + +ACMD_FUNC(misceffect) +{ + int effect = 0; + nullpo_retr(-1, sd); + if (!message || !*message) + return -1; + if (sscanf(message, "%d", &effect) < 1) + return -1; + clif_misceffect(&sd->bl,effect); + + return 0; +} + +/*========================================== + * MAIL SYSTEM + *------------------------------------------*/ +ACMD_FUNC(mail) +{ + nullpo_ret(sd); + mail_openmail(sd); + return 0; +} + +/*========================================== + * Show Monster DB Info v 1.0 + * originally by [Lupus] + *------------------------------------------*/ +ACMD_FUNC(mobinfo) +{ + unsigned char msize[3][7] = {"Small", "Medium", "Large"}; + unsigned char mrace[12][11] = {"Formless", "Undead", "Beast", "Plant", "Insect", "Fish", "Demon", "Demi-Human", "Angel", "Dragon", "Boss", "Non-Boss"}; + unsigned char melement[10][8] = {"Neutral", "Water", "Earth", "Fire", "Wind", "Poison", "Holy", "Dark", "Ghost", "Undead"}; + char atcmd_output2[CHAT_SIZE_MAX]; + struct item_data *item_data; + struct mob_db *mob, *mob_array[MAX_SEARCH]; + int count; + int i, j, k; + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(atcmd_output2, '\0', sizeof(atcmd_output2)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1239)); // Please enter a monster name/ID (usage: @mobinfo <monster_name_or_monster_ID>). + return -1; + } + + // If monster identifier/name argument is a name + if ((i = mobdb_checkid(atoi(message)))) + { + mob_array[0] = mob_db(i); + count = 1; + } else + count = mobdb_searchname_array(mob_array, MAX_SEARCH, message); + + if (!count) { + clif_displaymessage(fd, msg_txt(40)); // Invalid monster ID or name. + return -1; + } + + if (count > MAX_SEARCH) { + sprintf(atcmd_output, msg_txt(269), MAX_SEARCH, count); + clif_displaymessage(fd, atcmd_output); + count = MAX_SEARCH; + } + for (k = 0; k < count; k++) { + mob = mob_array[k]; + + // stats + if (mob->mexp) + sprintf(atcmd_output, msg_txt(1240), mob->name, mob->jname, mob->sprite, mob->vd.class_); // MVP Monster: '%s'/'%s'/'%s' (%d) + else + sprintf(atcmd_output, msg_txt(1241), mob->name, mob->jname, mob->sprite, mob->vd.class_); // Monster: '%s'/'%s'/'%s' (%d) + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1242), mob->lv, mob->status.max_hp, mob->base_exp, mob->job_exp,MOB_HIT(mob), MOB_FLEE(mob)); // Lv:%d HP:%d Base EXP:%u Job EXP:%u HIT:%d FLEE:%d + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, msg_txt(1243), // DEF:%d MDEF:%d STR:%d AGI:%d VIT:%d INT:%d DEX:%d LUK:%d + mob->status.def, mob->status.mdef,mob->status.str, mob->status.agi, + mob->status.vit, mob->status.int_, mob->status.dex, mob->status.luk); + clif_displaymessage(fd, atcmd_output); + + sprintf(atcmd_output, msg_txt(1244), // ATK:%d~%d Range:%d~%d~%d Size:%s Race: %s Element: %s (Lv:%d) + mob->status.rhw.atk, mob->status.rhw.atk2, mob->status.rhw.range, + mob->range2 , mob->range3, msize[mob->status.size], + mrace[mob->status.race], melement[mob->status.def_ele], mob->status.ele_lv); + clif_displaymessage(fd, atcmd_output); + // drops + clif_displaymessage(fd, msg_txt(1245)); // Drops: + strcpy(atcmd_output, " "); + j = 0; + for (i = 0; i < MAX_MOB_DROP; i++) { + int droprate; + if (mob->dropitem[i].nameid <= 0 || mob->dropitem[i].p < 1 || (item_data = itemdb_exists(mob->dropitem[i].nameid)) == NULL) + continue; + droprate = mob->dropitem[i].p; + + if (item_data->slot) + sprintf(atcmd_output2, " - %s[%d] %02.02f%%", item_data->jname, item_data->slot, (float)droprate / 100); + else + sprintf(atcmd_output2, " - %s %02.02f%%", item_data->jname, (float)droprate / 100); + strcat(atcmd_output, atcmd_output2); + if (++j % 3 == 0) { + clif_displaymessage(fd, atcmd_output); + strcpy(atcmd_output, " "); + } + } + if (j == 0) + clif_displaymessage(fd, msg_txt(1246)); // This monster has no drops. + else if (j % 3 != 0) + clif_displaymessage(fd, atcmd_output); + // mvp + if (mob->mexp) { + sprintf(atcmd_output, msg_txt(1247), mob->mexp); // MVP Bonus EXP:%u + clif_displaymessage(fd, atcmd_output); + strcpy(atcmd_output, msg_txt(1248)); // MVP Items: + j = 0; + for (i = 0; i < MAX_MVP_DROP; i++) { + if (mob->mvpitem[i].nameid <= 0 || (item_data = itemdb_exists(mob->mvpitem[i].nameid)) == NULL) + continue; + if (mob->mvpitem[i].p > 0) { + j++; + if (j == 1) + sprintf(atcmd_output2, " %s %02.02f%%", item_data->jname, (float)mob->mvpitem[i].p / 100); + else + sprintf(atcmd_output2, " - %s %02.02f%%", item_data->jname, (float)mob->mvpitem[i].p / 100); + strcat(atcmd_output, atcmd_output2); + } + } + if (j == 0) + clif_displaymessage(fd, msg_txt(1249)); // This monster has no MVP prizes. + else + clif_displaymessage(fd, atcmd_output); + } + } + return 0; +} + +/*========================================= +* @showmobs by KarLaeda +* => For 15 sec displays the mobs on minimap +*------------------------------------------*/ +ACMD_FUNC(showmobs) +{ + char mob_name[100]; + int mob_id; + int number = 0; + struct s_mapiterator* it; + + nullpo_retr(-1, sd); + + if(sscanf(message, "%99[^\n]", mob_name) < 0) + return -1; + + if((mob_id = atoi(mob_name)) == 0) + mob_id = mobdb_searchname(mob_name); + if(mob_id > 0 && mobdb_checkid(mob_id) == 0){ + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1250),mob_name); // Invalid mob id %s! + clif_displaymessage(fd, atcmd_output); + return 0; + } + + if(mob_db(mob_id)->status.mode&MD_BOSS && !pc_has_permission(sd, PC_PERM_SHOW_BOSS)){ // If player group does not have access to boss mobs. + clif_displaymessage(fd, msg_txt(1251)); // Can't show boss mobs! + return 0; + } + + if(mob_id == atoi(mob_name) && mob_db(mob_id)->jname) + strcpy(mob_name,mob_db(mob_id)->jname); // --ja-- + //strcpy(mob_name,mob_db(mob_id)->name); // --en-- + + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1252), // Mob Search... %s %s + mob_name, mapindex_id2name(sd->mapindex)); + clif_displaymessage(fd, atcmd_output); + + it = mapit_geteachmob(); + for(;;) + { + TBL_MOB* md = (TBL_MOB*)mapit_next(it); + if( md == NULL ) + break;// no more mobs + + if( md->bl.m != sd->bl.m ) + continue; + if( mob_id != -1 && md->class_ != mob_id ) + continue; + if( md->special_state.ai || md->master_id ) + continue; // hide slaves and player summoned mobs + if( md->spawn_timer != INVALID_TIMER ) + continue; // hide mobs waiting for respawn + + ++number; + clif_viewpoint(sd, 1, 0, md->bl.x, md->bl.y, number, 0xFFFFFF); + } + mapit_free(it); + + return 0; +} + +/*========================================== + * homunculus level up [orn] + *------------------------------------------*/ +ACMD_FUNC(homlevel) +{ + TBL_HOM * hd; + int level = 0, i = 0; + + nullpo_retr(-1, sd); + + if ( !message || !*message || ( level = atoi(message) ) < 1 ) { + clif_displaymessage(fd, msg_txt(1253)); // Please enter a level adjustment (usage: @homlevel <number of levels>). + return -1; + } + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + hd = sd->hd; + + for (i = 1; i <= level && hd->exp_next; i++){ + hd->homunculus.exp += hd->exp_next; + merc_hom_levelup(hd); + } + status_calc_homunculus(hd,0); + status_percent_heal(&hd->bl, 100, 100); + clif_specialeffect(&hd->bl,568,AREA); + return 0; +} + +/*========================================== + * homunculus evolution H [orn] + *------------------------------------------*/ +ACMD_FUNC(homevolution) +{ + nullpo_retr(-1, sd); + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + if ( !merc_hom_evolution(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1255)); // Your homunculus doesn't evolve. + return -1; + } + clif_homskillinfoblock(sd); + return 0; +} + +ACMD_FUNC(hommutate) +{ + int homun_id, m_class = 0, m_id; + nullpo_retr(-1, sd); + + if (!merc_is_hom_active(sd->hd)) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + if (!message || !*message) { + homun_id = 6048 + (rnd() % 4); + } else { + homun_id = atoi(message); + } + + m_class = hom_class2mapid(sd->hd->homunculus.class_); + m_id = hom_class2mapid(homun_id); + + if (m_class != -1 && m_id != -1 && m_class&HOM_EVO && m_id&HOM_S && sd->hd->homunculus.level >= 99) { + hom_mutate(sd->hd, homun_id); + } else { + clif_emotion(&sd->hd->bl, E_SWT); + } + return 0; +} + +/*========================================== + * call choosen homunculus [orn] + *------------------------------------------*/ +ACMD_FUNC(makehomun) +{ + int homunid; + nullpo_retr(-1, sd); + + if ( sd->status.hom_id ) { + clif_displaymessage(fd, msg_txt(450)); + return -1; + } + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1256)); // Please enter a homunculus ID (usage: @makehomun <homunculus id>). + return -1; + } + + homunid = atoi(message); + if( homunid < HM_CLASS_BASE || homunid > HM_CLASS_BASE + MAX_HOMUNCULUS_CLASS - 1 ) + { + clif_displaymessage(fd, msg_txt(1257)); // Invalid Homunculus ID. + return -1; + } + + merc_create_homunculus_request(sd,homunid); + return 0; +} + +/*========================================== + * modify homunculus intimacy [orn] + *------------------------------------------*/ +ACMD_FUNC(homfriendly) +{ + int friendly = 0; + + nullpo_retr(-1, sd); + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1258)); // Please enter a friendly value (usage: @homfriendly <friendly value [0-1000]>). + return -1; + } + + friendly = atoi(message); + friendly = cap_value(friendly, 0, 1000); + + sd->hd->homunculus.intimacy = friendly * 100 ; + clif_send_homdata(sd,SP_INTIMATE,friendly); + return 0; +} + +/*========================================== + * modify homunculus hunger [orn] + *------------------------------------------*/ +ACMD_FUNC(homhungry) +{ + int hungry = 0; + + nullpo_retr(-1, sd); + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1259)); // Please enter a hunger value (usage: @homhungry <hunger value [0-100]>). + return -1; + } + + hungry = atoi(message); + hungry = cap_value(hungry, 0, 100); + + sd->hd->homunculus.hunger = hungry; + clif_send_homdata(sd,SP_HUNGRY,hungry); + return 0; +} + +/*========================================== + * make the homunculus speak [orn] + *------------------------------------------*/ +ACMD_FUNC(homtalk) +{ + char mes[100],temp[100]; + + nullpo_retr(-1, sd); + + if ( battle_config.min_chat_delay ) { + if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) + return 0; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + if (sd->sc.count && //no "chatting" while muted. + (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT))) + return -1; + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + if (!message || !*message || sscanf(message, "%99[^\n]", mes) < 1) { + clif_displaymessage(fd, msg_txt(1260)); // Please enter a message (usage: @homtalk <message>). + return -1; + } + + snprintf(temp, sizeof temp ,"%s : %s", sd->hd->homunculus.name, mes); + clif_message(&sd->hd->bl, temp); + + return 0; +} + +/*========================================== + * Show homunculus stats + *------------------------------------------*/ +ACMD_FUNC(hominfo) +{ + struct homun_data *hd; + struct status_data *status; + nullpo_retr(-1, sd); + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + hd = sd->hd; + status = status_get_status_data(&hd->bl); + clif_displaymessage(fd, msg_txt(1261)); // Homunculus stats: + + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1262), // HP: %d/%d - SP: %d/%d + status->hp, status->max_hp, status->sp, status->max_sp); + clif_displaymessage(fd, atcmd_output); + + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1263), // ATK: %d - MATK: %d~%d + status->rhw.atk2 +status->batk, status->matk_min, status->matk_max); + clif_displaymessage(fd, atcmd_output); + + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1264), // Hungry: %d - Intimacy: %u + hd->homunculus.hunger, hd->homunculus.intimacy/100); + clif_displaymessage(fd, atcmd_output); + + snprintf(atcmd_output, sizeof(atcmd_output) , + msg_txt(1265), // Stats: Str %d / Agi %d / Vit %d / Int %d / Dex %d / Luk %d + status->str, status->agi, status->vit, + status->int_, status->dex, status->luk); + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +ACMD_FUNC(homstats) +{ + struct homun_data *hd; + struct s_homunculus_db *db; + struct s_homunculus *hom; + int lv, min, max, evo; + + nullpo_retr(-1, sd); + + if ( !merc_is_hom_active(sd->hd) ) { + clif_displaymessage(fd, msg_txt(1254)); // You do not have a homunculus. + return -1; + } + + hd = sd->hd; + + hom = &hd->homunculus; + db = hd->homunculusDB; + lv = hom->level; + + snprintf(atcmd_output, sizeof(atcmd_output) , + msg_txt(1266), lv, db->name); // Homunculus growth stats (Lv %d %s): + clif_displaymessage(fd, atcmd_output); + lv--; //Since the first increase is at level 2. + + evo = (hom->class_ == db->evo_class); + min = db->base.HP +lv*db->gmin.HP +(evo?db->emin.HP:0); + max = db->base.HP +lv*db->gmax.HP +(evo?db->emax.HP:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1267), hom->max_hp, min, max); // Max HP: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.SP +lv*db->gmin.SP +(evo?db->emin.SP:0); + max = db->base.SP +lv*db->gmax.SP +(evo?db->emax.SP:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1268), hom->max_sp, min, max); // Max SP: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.str +lv*(db->gmin.str/10) +(evo?db->emin.str:0); + max = db->base.str +lv*(db->gmax.str/10) +(evo?db->emax.str:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1269), hom->str/10, min, max); // Str: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.agi +lv*(db->gmin.agi/10) +(evo?db->emin.agi:0); + max = db->base.agi +lv*(db->gmax.agi/10) +(evo?db->emax.agi:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1270), hom->agi/10, min, max); // Agi: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.vit +lv*(db->gmin.vit/10) +(evo?db->emin.vit:0); + max = db->base.vit +lv*(db->gmax.vit/10) +(evo?db->emax.vit:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1271), hom->vit/10, min, max); // Vit: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.int_ +lv*(db->gmin.int_/10) +(evo?db->emin.int_:0); + max = db->base.int_ +lv*(db->gmax.int_/10) +(evo?db->emax.int_:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1272), hom->int_/10, min, max); // Int: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.dex +lv*(db->gmin.dex/10) +(evo?db->emin.dex:0); + max = db->base.dex +lv*(db->gmax.dex/10) +(evo?db->emax.dex:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1273), hom->dex/10, min, max); // Dex: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + min = db->base.luk +lv*(db->gmin.luk/10) +(evo?db->emin.luk:0); + max = db->base.luk +lv*(db->gmax.luk/10) +(evo?db->emax.luk:0);; + snprintf(atcmd_output, sizeof(atcmd_output) ,msg_txt(1274), hom->luk/10, min, max); // Luk: %d (%d~%d) + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +ACMD_FUNC(homshuffle) +{ + nullpo_retr(-1, sd); + + if(!sd->hd) + return -1; // nothing to do + + if(!merc_hom_shuffle(sd->hd)) + return -1; + + clif_displaymessage(sd->fd, msg_txt(1275)); // Homunculus stats altered. + atcommand_homstats(fd, sd, command, message); //Print out the new stats + return 0; +} + +/*========================================== + * Show Items DB Info v 1.0 + * originally by [Lupus] + *------------------------------------------*/ +ACMD_FUNC(iteminfo) +{ + struct item_data *item_data, *item_array[MAX_SEARCH]; + int i, count = 1; + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1276)); // Please enter an item name/ID (usage: @ii/@iteminfo <item name/ID>). + return -1; + } + if ((item_array[0] = itemdb_exists(atoi(message))) == NULL) + count = itemdb_searchname_array(item_array, MAX_SEARCH, message); + + if (!count) { + clif_displaymessage(fd, msg_txt(19)); // Invalid item ID or name. + return -1; + } + + if (count > MAX_SEARCH) { + sprintf(atcmd_output, msg_txt(269), MAX_SEARCH, count); // Displaying first %d out of %d matches + clif_displaymessage(fd, atcmd_output); + count = MAX_SEARCH; + } + for (i = 0; i < count; i++) { + item_data = item_array[i]; + sprintf(atcmd_output, msg_txt(1277), // Item: '%s'/'%s'[%d] (%d) Type: %s | Extra Effect: %s + item_data->name,item_data->jname,item_data->slot,item_data->nameid, + itemdb_typename(item_data->type), + (item_data->script==NULL)? msg_txt(1278) : msg_txt(1279) // None / With script + ); + clif_displaymessage(fd, atcmd_output); + + sprintf(atcmd_output, msg_txt(1280), item_data->value_buy, item_data->value_sell, item_data->weight/10. ); // NPC Buy:%dz, Sell:%dz | Weight: %.1f + clif_displaymessage(fd, atcmd_output); + + if (item_data->maxchance == -1) + strcpy(atcmd_output, msg_txt(1281)); // - Available in the shops only. + else if (!battle_config.atcommand_mobinfo_type && item_data->maxchance) + sprintf(atcmd_output, msg_txt(1282), (float)item_data->maxchance / 100 ); // - Maximal monsters drop chance: %02.02f%% + else + strcpy(atcmd_output, msg_txt(1283)); // - Monsters don't drop this item. + clif_displaymessage(fd, atcmd_output); + + } + return 0; +} + +/*========================================== + * Show who drops the item. + *------------------------------------------*/ +ACMD_FUNC(whodrops) +{ + struct item_data *item_data, *item_array[MAX_SEARCH]; + int i,j, count = 1; + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1284)); // Please enter item name/ID (usage: @whodrops <item name/ID>). + return -1; + } + if ((item_array[0] = itemdb_exists(atoi(message))) == NULL) + count = itemdb_searchname_array(item_array, MAX_SEARCH, message); + + if (!count) { + clif_displaymessage(fd, msg_txt(19)); // Invalid item ID or name. + return -1; + } + + if (count > MAX_SEARCH) { + sprintf(atcmd_output, msg_txt(269), MAX_SEARCH, count); // Displaying first %d out of %d matches + clif_displaymessage(fd, atcmd_output); + count = MAX_SEARCH; + } + for (i = 0; i < count; i++) { + item_data = item_array[i]; + sprintf(atcmd_output, msg_txt(1285), item_data->jname,item_data->slot); // Item: '%s'[%d] + clif_displaymessage(fd, atcmd_output); + + if (item_data->mob[0].chance == 0) { + strcpy(atcmd_output, msg_txt(1286)); // - Item is not dropped by mobs. + clif_displaymessage(fd, atcmd_output); + } else { + sprintf(atcmd_output, msg_txt(1287), MAX_SEARCH); // - Common mobs with highest drop chance (only max %d are listed): + clif_displaymessage(fd, atcmd_output); + + for (j=0; j < MAX_SEARCH && item_data->mob[j].chance > 0; j++) + { + sprintf(atcmd_output, "- %s (%02.02f%%)", mob_db(item_data->mob[j].id)->jname, item_data->mob[j].chance/100.); + clif_displaymessage(fd, atcmd_output); + } + } + } + return 0; +} + +ACMD_FUNC(whereis) +{ + struct mob_db *mob, *mob_array[MAX_SEARCH]; + int count; + int i, j, k; + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1288)); // Please enter a monster name/ID (usage: @whereis <monster_name_or_monster_ID>). + return -1; + } + + // If monster identifier/name argument is a name + if ((i = mobdb_checkid(atoi(message)))) + { + mob_array[0] = mob_db(i); + count = 1; + } else + count = mobdb_searchname_array(mob_array, MAX_SEARCH, message); + + if (!count) { + clif_displaymessage(fd, msg_txt(40)); // Invalid monster ID or name. + return -1; + } + + if (count > MAX_SEARCH) { + sprintf(atcmd_output, msg_txt(269), MAX_SEARCH, count); + clif_displaymessage(fd, atcmd_output); + count = MAX_SEARCH; + } + for (k = 0; k < count; k++) { + mob = mob_array[k]; + snprintf(atcmd_output, sizeof atcmd_output, msg_txt(1289), mob->jname); // %s spawns in: + clif_displaymessage(fd, atcmd_output); + + for (i = 0; i < ARRAYLENGTH(mob->spawn) && mob->spawn[i].qty; i++) + { + j = map_mapindex2mapid(mob->spawn[i].mapindex); + if (j < 0) continue; + snprintf(atcmd_output, sizeof atcmd_output, "%s (%d)", map[j].name, mob->spawn[i].qty); + clif_displaymessage(fd, atcmd_output); + } + if (i == 0) + clif_displaymessage(fd, msg_txt(1290)); // This monster does not spawn normally. + } + + return 0; +} + +ACMD_FUNC(version) +{ + const char * revision; + + if ((revision = get_svn_revision()) != 0) { + sprintf(atcmd_output,msg_txt(1295),revision); // rAthena Version SVN r%s + clif_displaymessage(fd,atcmd_output); + } else + clif_displaymessage(fd,msg_txt(1296)); // Cannot determine SVN revision. + + return 0; +} + +/*========================================== + * @mutearea by MouseJstr + *------------------------------------------*/ +static int atcommand_mutearea_sub(struct block_list *bl,va_list ap) +{ + + int time, id; + struct map_session_data *pl_sd = (struct map_session_data *)bl; + if (pl_sd == NULL) + return 0; + + id = va_arg(ap, int); + time = va_arg(ap, int); + + if (id != bl->id && !pc_get_group_level(pl_sd)) { + pl_sd->status.manner -= time; + if (pl_sd->status.manner < 0) + sc_start(&pl_sd->bl,SC_NOCHAT,100,0,0); + else + status_change_end(&pl_sd->bl, SC_NOCHAT, INVALID_TIMER); + } + return 0; +} + +ACMD_FUNC(mutearea) +{ + int time; + nullpo_ret(sd); + + if (!message || !*message) { + clif_displaymessage(fd, msg_txt(1297)); // Please enter a time in minutes (usage: @mutearea/@stfu <time in minutes>). + return -1; + } + + time = atoi(message); + + map_foreachinarea(atcommand_mutearea_sub,sd->bl.m, + sd->bl.x-AREA_SIZE, sd->bl.y-AREA_SIZE, + sd->bl.x+AREA_SIZE, sd->bl.y+AREA_SIZE, BL_PC, sd->bl.id, time); + + return 0; +} + + +ACMD_FUNC(rates) +{ + char buf[CHAT_SIZE_MAX]; + + nullpo_ret(sd); + memset(buf, '\0', sizeof(buf)); + + snprintf(buf, CHAT_SIZE_MAX, msg_txt(1298), // Experience rates: Base %.2fx / Job %.2fx + battle_config.base_exp_rate/100., battle_config.job_exp_rate/100.); + clif_displaymessage(fd, buf); + snprintf(buf, CHAT_SIZE_MAX, msg_txt(1299), // Normal Drop Rates: Common %.2fx / Healing %.2fx / Usable %.2fx / Equipment %.2fx / Card %.2fx + battle_config.item_rate_common/100., battle_config.item_rate_heal/100., battle_config.item_rate_use/100., battle_config.item_rate_equip/100., battle_config.item_rate_card/100.); + clif_displaymessage(fd, buf); + snprintf(buf, CHAT_SIZE_MAX, msg_txt(1300), // Boss Drop Rates: Common %.2fx / Healing %.2fx / Usable %.2fx / Equipment %.2fx / Card %.2fx + battle_config.item_rate_common_boss/100., battle_config.item_rate_heal_boss/100., battle_config.item_rate_use_boss/100., battle_config.item_rate_equip_boss/100., battle_config.item_rate_card_boss/100.); + clif_displaymessage(fd, buf); + snprintf(buf, CHAT_SIZE_MAX, msg_txt(1301), // Other Drop Rates: MvP %.2fx / Card-Based %.2fx / Treasure %.2fx + battle_config.item_rate_mvp/100., battle_config.item_rate_adddrop/100., battle_config.item_rate_treasure/100.); + clif_displaymessage(fd, buf); + + return 0; +} + +/*========================================== + * @me by lordalfa + * => Displays the OUTPUT string on top of the Visible players Heads. + *------------------------------------------*/ +ACMD_FUNC(me) +{ + char tempmes[CHAT_SIZE_MAX]; + nullpo_retr(-1, sd); + + memset(tempmes, '\0', sizeof(tempmes)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (sd->sc.count && //no "chatting" while muted. + (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT))) + return -1; + + if (!message || !*message || sscanf(message, "%199[^\n]", tempmes) < 0) { + clif_displaymessage(fd, msg_txt(1302)); // Please enter a message (usage: @me <message>). + return -1; + } + + sprintf(atcmd_output, msg_txt(270), sd->status.name, tempmes); // *%s %s* + clif_disp_overhead(sd, atcmd_output); + + return 0; + +} + +/*========================================== + * @size + * => Resize your character sprite. [Valaris] + *------------------------------------------*/ +ACMD_FUNC(size) +{ + int size = 0; + nullpo_retr(-1, sd); + + size = cap_value(atoi(message),SZ_SMALL,SZ_BIG); + + if(sd->state.size) { + sd->state.size = SZ_SMALL; + pc_setpos(sd, sd->mapindex, sd->bl.x, sd->bl.y, CLR_TELEPORT); + } + + sd->state.size = size; + if( size == SZ_MEDIUM ) + clif_specialeffect(&sd->bl,420,AREA); + else if( size == SZ_BIG ) + clif_specialeffect(&sd->bl,422,AREA); + + clif_displaymessage(fd, msg_txt(1303)); // Size change applied. + return 0; +} + +ACMD_FUNC(sizeall) +{ + int size; + struct map_session_data *pl_sd; + struct s_mapiterator* iter; + + size = atoi(message); + size = cap_value(size,0,2); + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) { + if( pl_sd->state.size != size ) { + if( pl_sd->state.size ) { + pl_sd->state.size = SZ_SMALL; + pc_setpos(pl_sd, pl_sd->mapindex, pl_sd->bl.x, pl_sd->bl.y, CLR_TELEPORT); + } + + pl_sd->state.size = size; + if( size == SZ_MEDIUM ) + clif_specialeffect(&pl_sd->bl,420,AREA); + else if( size == SZ_BIG ) + clif_specialeffect(&pl_sd->bl,422,AREA); + } + } + mapit_free(iter); + + clif_displaymessage(fd, msg_txt(1303)); // Size change applied. + return 0; +} + +ACMD_FUNC(sizeguild) +{ + int size = 0, i; + char guild[NAME_LENGTH]; + struct map_session_data *pl_sd; + struct guild *g; + nullpo_retr(-1, sd); + + memset(guild, '\0', sizeof(guild)); + + if( !message || !*message || sscanf(message, "%d %23[^\n]", &size, guild) < 2 ) { + clif_displaymessage(fd, msg_txt(1304)); // Please enter guild name/ID (usage: @sizeguild <size> <guild name/ID>). + return -1; + } + + if( (g = guild_searchname(guild)) == NULL && (g = guild_search(atoi(guild))) == NULL ) { + clif_displaymessage(fd, msg_txt(94)); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + size = cap_value(size,SZ_SMALL,SZ_BIG); + + for( i = 0; i < g->max_member; i++ ) { + if( (pl_sd = g->member[i].sd) && pl_sd->state.size != size ) { + if( pl_sd->state.size ) { + pl_sd->state.size = SZ_SMALL; + pc_setpos(pl_sd, pl_sd->mapindex, pl_sd->bl.x, pl_sd->bl.y, CLR_TELEPORT); + } + + pl_sd->state.size = size; + if( size == SZ_MEDIUM ) + clif_specialeffect(&pl_sd->bl,420,AREA); + else if( size == SZ_BIG ) + clif_specialeffect(&pl_sd->bl,422,AREA); + } + } + + clif_displaymessage(fd, msg_txt(1303)); // Size change applied. + return 0; +} + +/*========================================== + * @monsterignore + * => Makes monsters ignore you. [Valaris] + *------------------------------------------*/ +ACMD_FUNC(monsterignore) +{ + nullpo_retr(-1, sd); + + if (!sd->state.monster_ignore) { + sd->state.monster_ignore = 1; + clif_displaymessage(sd->fd, msg_txt(1305)); // You are now immune to attacks. + } else { + sd->state.monster_ignore = 0; + clif_displaymessage(sd->fd, msg_txt(1306)); // Returned to normal state. + } + + return 0; +} +/*========================================== + * @fakename + * => Gives your character a fake name. [Valaris] + *------------------------------------------*/ +ACMD_FUNC(fakename) +{ + nullpo_retr(-1, sd); + + if( !message || !*message ) + { + if( sd->fakename[0] ) + { + sd->fakename[0] = '\0'; + clif_charnameack(0, &sd->bl); + clif_displaymessage(sd->fd, msg_txt(1307)); // Returned to real name. + return 0; + } + + clif_displaymessage(sd->fd, msg_txt(1308)); // You must enter a name. + return -1; + } + + if( strlen(message) < 2 ) + { + clif_displaymessage(sd->fd, msg_txt(1309)); // Fake name must be at least two characters. + return -1; + } + + safestrncpy(sd->fakename, message, sizeof(sd->fakename)); + clif_charnameack(0, &sd->bl); + clif_displaymessage(sd->fd, msg_txt(1310)); // Fake name enabled. + + return 0; +} + +/*========================================== + * Ragnarok Resources + *------------------------------------------*/ +ACMD_FUNC(mapflag) { +#define checkflag( cmd ) if ( map[ sd->bl.m ].flag.cmd ) clif_displaymessage(sd->fd,#cmd) +#define setflag( cmd ) \ + if ( strcmp( flag_name , #cmd ) == 0 ){\ + map[ sd->bl.m ].flag.cmd = flag;\ + sprintf(atcmd_output,"[ @mapflag ] %s flag has been set to %s value = %hd",#cmd,flag?"On":"Off",flag);\ + clif_displaymessage(sd->fd,atcmd_output);\ + return 0;\ + } + char flag_name[100]; + short flag=0,i; + nullpo_retr(-1, sd); + memset(flag_name, '\0', sizeof(flag_name)); + + if (!message || !*message || (sscanf(message, "%99s %hd", flag_name, &flag) < 1)) { + clif_displaymessage(sd->fd,msg_txt(1311)); // Enabled Mapflags in this map: + clif_displaymessage(sd->fd,"----------------------------------"); + checkflag(autotrade); checkflag(allowks); checkflag(nomemo); checkflag(noteleport); + checkflag(noreturn); checkflag(monster_noteleport); checkflag(nosave); checkflag(nobranch); + checkflag(noexppenalty); checkflag(pvp); checkflag(pvp_noparty); checkflag(pvp_noguild); + checkflag(pvp_nightmaredrop); checkflag(pvp_nocalcrank); checkflag(gvg_castle); checkflag(gvg); + checkflag(gvg_dungeon); checkflag(gvg_noparty); checkflag(battleground);checkflag(nozenypenalty); + checkflag(notrade); checkflag(noskill); checkflag(nowarp); checkflag(nowarpto); + checkflag(noicewall); checkflag(snow); checkflag(clouds); checkflag(clouds2); + checkflag(fog); checkflag(fireworks); checkflag(sakura); checkflag(leaves); + checkflag(nogo); checkflag(nobaseexp); + checkflag(nojobexp); checkflag(nomobloot); checkflag(nomvploot); checkflag(nightenabled); + checkflag(restricted); checkflag(nodrop); checkflag(novending); checkflag(loadevent); + checkflag(nochat); checkflag(partylock); checkflag(guildlock); checkflag(src4instance); + clif_displaymessage(sd->fd," "); + clif_displaymessage(sd->fd,msg_txt(1312)); // Usage: "@mapflag monster_noteleport 1" (0=Off | 1=On) + clif_displaymessage(sd->fd,msg_txt(1313)); // Type "@mapflag available" to list the available mapflags. + return 1; + } + for (i = 0; flag_name[i]; i++) flag_name[i] = (char)tolower(flag_name[i]); //lowercase + + setflag(autotrade); setflag(allowks); setflag(nomemo); setflag(noteleport); + setflag(noreturn); setflag(monster_noteleport);setflag(nosave); setflag(nobranch); + setflag(noexppenalty); setflag(pvp); setflag(pvp_noparty); setflag(pvp_noguild); + setflag(pvp_nightmaredrop); setflag(pvp_nocalcrank); setflag(gvg_castle); setflag(gvg); + setflag(gvg_dungeon); setflag(gvg_noparty); setflag(battleground); setflag(nozenypenalty); + setflag(notrade); setflag(noskill); setflag(nowarp); setflag(nowarpto); + setflag(noicewall); setflag(snow); setflag(clouds); setflag(clouds2); + setflag(fog); setflag(fireworks); setflag(sakura); setflag(leaves); + setflag(nogo); setflag(nobaseexp); + setflag(nojobexp); setflag(nomobloot); setflag(nomvploot); setflag(nightenabled); + setflag(restricted); setflag(nodrop); setflag(novending); setflag(loadevent); + setflag(nochat); setflag(partylock); setflag(guildlock); setflag(src4instance); + + clif_displaymessage(sd->fd,msg_txt(1314)); // Invalid flag name or flag. + clif_displaymessage(sd->fd,msg_txt(1312)); // Usage: "@mapflag monster_noteleport 1" (0=Off | 1=On) + clif_displaymessage(sd->fd,msg_txt(1315)); // Available Flags: + clif_displaymessage(sd->fd,"----------------------------------"); + clif_displaymessage(sd->fd,"town, autotrade, allowks, nomemo, noteleport, noreturn, monster_noteleport, nosave,"); + clif_displaymessage(sd->fd,"nobranch, noexppenalty, pvp, pvp_noparty, pvp_noguild, pvp_nightmaredrop,"); + clif_displaymessage(sd->fd,"pvp_nocalcrank, gvg_castle, gvg, gvg_dungeon, gvg_noparty, battleground,"); + clif_displaymessage(sd->fd,"nozenypenalty, notrade, noskill, nowarp, nowarpto, noicewall, snow, clouds, clouds2,"); + clif_displaymessage(sd->fd,"fog, fireworks, sakura, leaves, nogo, nobaseexp, nojobexp, nomobloot,"); + clif_displaymessage(sd->fd,"nomvploot, nightenabled, restricted, nodrop, novending, loadevent, nochat, partylock,"); + clif_displaymessage(sd->fd,"guildlock, src4instance"); + +#undef checkflag +#undef setflag + + return 0; +} + +/*=================================== + * Remove some messages + *-----------------------------------*/ +ACMD_FUNC(showexp) +{ + if (sd->state.showexp) { + sd->state.showexp = 0; + clif_displaymessage(fd, msg_txt(1316)); // Gained exp will not be shown. + return 0; + } + + sd->state.showexp = 1; + clif_displaymessage(fd, msg_txt(1317)); // Gained exp is now shown. + return 0; +} + +ACMD_FUNC(showzeny) +{ + if (sd->state.showzeny) { + sd->state.showzeny = 0; + clif_displaymessage(fd, msg_txt(1318)); // Gained zeny will not be shown. + return 0; + } + + sd->state.showzeny = 1; + clif_displaymessage(fd, msg_txt(1319)); // Gained zeny is now shown. + return 0; +} + +ACMD_FUNC(showdelay) +{ + if (sd->state.showdelay) { + sd->state.showdelay = 0; + clif_displaymessage(fd, msg_txt(1320)); // Skill delay failures will not be shown. + return 0; + } + + sd->state.showdelay = 1; + clif_displaymessage(fd, msg_txt(1321)); // Skill delay failures are now shown. + return 0; +} + +/*========================================== + * Duel organizing functions [LuzZza] + * + * @duel [limit|nick] - create a duel + * @invite <nick> - invite player + * @accept - accept invitation + * @reject - reject invitation + * @leave - leave duel + *------------------------------------------*/ +ACMD_FUNC(invite) +{ + unsigned int did = sd->duel_group; + struct map_session_data *target_sd = map_nick2sd((char *)message); + + if(did <= 0) { + // "Duel: @invite without @duel." + clif_displaymessage(fd, msg_txt(350)); + return 0; + } + + if(duel_list[did].max_players_limit > 0 && + duel_list[did].members_count >= duel_list[did].max_players_limit) { + + // "Duel: Limit of players is reached." + clif_displaymessage(fd, msg_txt(351)); + return 0; + } + + if(target_sd == NULL) { + // "Duel: Player not found." + clif_displaymessage(fd, msg_txt(352)); + return 0; + } + + if(target_sd->duel_group > 0 || target_sd->duel_invite > 0) { + // "Duel: Player already in duel." + clif_displaymessage(fd, msg_txt(353)); + return 0; + } + + if(battle_config.duel_only_on_same_map && target_sd->bl.m != sd->bl.m) + { + sprintf(atcmd_output, msg_txt(364), message); + clif_displaymessage(fd, atcmd_output); + return 0; + } + + duel_invite(did, sd, target_sd); + // "Duel: Invitation has been sent." + clif_displaymessage(fd, msg_txt(354)); + return 0; +} + +ACMD_FUNC(duel) +{ + char output[CHAT_SIZE_MAX]; + unsigned int maxpl=0, newduel; + struct map_session_data *target_sd; + + if(sd->duel_group > 0) { + duel_showinfo(sd->duel_group, sd); + return 0; + } + + if(sd->duel_invite > 0) { + // "Duel: @duel without @reject." + clif_displaymessage(fd, msg_txt(355)); + return 0; + } + + if(!duel_checktime(sd)) { + // "Duel: You can take part in duel only one time per %d minutes." + sprintf(output, msg_txt(356), battle_config.duel_time_interval); + clif_displaymessage(fd, output); + return 0; + } + + if( message[0] ) { + if(sscanf(message, "%d", &maxpl) >= 1) { + if(maxpl < 2 || maxpl > 65535) { + clif_displaymessage(fd, msg_txt(357)); // "Duel: Invalid value." + return 0; + } + duel_create(sd, maxpl); + } else { + target_sd = map_nick2sd((char *)message); + if(target_sd != NULL) { + if((newduel = duel_create(sd, 2)) != -1) { + if(target_sd->duel_group > 0 || target_sd->duel_invite > 0) { + clif_displaymessage(fd, msg_txt(353)); // "Duel: Player already in duel." + return 0; + } + duel_invite(newduel, sd, target_sd); + clif_displaymessage(fd, msg_txt(354)); // "Duel: Invitation has been sent." + } + } else { + // "Duel: Player not found." + clif_displaymessage(fd, msg_txt(352)); + return 0; + } + } + } else + duel_create(sd, 0); + + return 0; +} + + +ACMD_FUNC(leave) +{ + if(sd->duel_group <= 0) { + // "Duel: @leave without @duel." + clif_displaymessage(fd, msg_txt(358)); + return 0; + } + + duel_leave(sd->duel_group, sd); + clif_displaymessage(fd, msg_txt(359)); // "Duel: You left the duel." + return 0; +} + +ACMD_FUNC(accept) +{ + char output[CHAT_SIZE_MAX]; + + if(!duel_checktime(sd)) { + // "Duel: You can take part in duel only one time per %d minutes." + sprintf(output, msg_txt(356), battle_config.duel_time_interval); + clif_displaymessage(fd, output); + return 0; + } + + if(sd->duel_invite <= 0) { + // "Duel: @accept without invititation." + clif_displaymessage(fd, msg_txt(360)); + return 0; + } + + if( duel_list[sd->duel_invite].max_players_limit > 0 && duel_list[sd->duel_invite].members_count >= duel_list[sd->duel_invite].max_players_limit ) + { + // "Duel: Limit of players is reached." + clif_displaymessage(fd, msg_txt(351)); + return 0; + } + + duel_accept(sd->duel_invite, sd); + // "Duel: Invitation has been accepted." + clif_displaymessage(fd, msg_txt(361)); + return 0; +} + +ACMD_FUNC(reject) +{ + if(sd->duel_invite <= 0) { + // "Duel: @reject without invititation." + clif_displaymessage(fd, msg_txt(362)); + return 0; + } + + duel_reject(sd->duel_invite, sd); + // "Duel: Invitation has been rejected." + clif_displaymessage(fd, msg_txt(363)); + return 0; +} + +/*=================================== + * Cash Points + *-----------------------------------*/ +ACMD_FUNC(cash) +{ + char output[128]; + int value; + int ret=0; + nullpo_retr(-1, sd); + + if( !message || !*message || (value = atoi(message)) == 0 ) { + clif_displaymessage(fd, msg_txt(1322)); // Please enter an amount. + return -1; + } + + if( !strcmpi(command+1,"cash") ) + { + if( value > 0 ) { + if( (ret=pc_getcash(sd, value, 0)) >= 0){ + sprintf(output, msg_txt(505), ret, sd->cashPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + else clif_displaymessage(fd, msg_txt(149)); // Unable to decrease the number/value. + } else { + if( (ret=pc_paycash(sd, -value, 0)) >= 0){ + sprintf(output, msg_txt(410), ret, sd->cashPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + else clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + } + } + else + { // @points + if( value > 0 ) { + if( (ret=pc_getcash(sd, 0, value)) >= 0){ + sprintf(output, msg_txt(506), ret, sd->kafraPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + else clif_displaymessage(fd, msg_txt(149)); // Unable to decrease the number/value. + } else { + if( (ret=pc_paycash(sd, -value, -value)) >= 0){ + sprintf(output, msg_txt(411), ret, sd->kafraPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + else clif_displaymessage(fd, msg_txt(41)); // Unable to decrease the number/value. + } + } + + return 0; +} + +// @clone/@slaveclone/@evilclone <playername> [Valaris] +ACMD_FUNC(clone) +{ + int x=0,y=0,flag=0,master=0,i=0; + struct map_session_data *pl_sd=NULL; + + if (!message || !*message) { + clif_displaymessage(sd->fd,msg_txt(1323)); // You must enter a player name or ID. + return 0; + } + + if((pl_sd=map_nick2sd((char *)message)) == NULL && (pl_sd=map_charid2sd(atoi(message))) == NULL) { + clif_displaymessage(fd, msg_txt(3)); // Character not found. + return 0; + } + + if(pc_get_group_level(pl_sd) > pc_get_group_level(sd)) { + clif_displaymessage(fd, msg_txt(126)); // Cannot clone a player of higher GM level than yourself. + return 0; + } + + if (strcmpi(command+1, "clone") == 0) + flag = 1; + else if (strcmpi(command+1, "slaveclone") == 0) { + flag = 2; + if(pc_isdead(sd)){ + clif_displaymessage(fd, msg_txt(129+flag*2)); + return 0; + } + master = sd->bl.id; + if (battle_config.atc_slave_clone_limit + && mob_countslave(&sd->bl) >= battle_config.atc_slave_clone_limit) { + clif_displaymessage(fd, msg_txt(127)); // You've reached your slave clones limit. + return 0; + } + } + + do { + x = sd->bl.x + (rnd() % 10 - 5); + y = sd->bl.y + (rnd() % 10 - 5); + } while (map_getcell(sd->bl.m,x,y,CELL_CHKNOPASS) && i++ < 10); + + if (i >= 10) { + x = sd->bl.x; + y = sd->bl.y; + } + + if((x = mob_clone_spawn(pl_sd, sd->bl.m, x, y, "", master, 0, flag?1:0, 0)) > 0) { + clif_displaymessage(fd, msg_txt(128+flag*2)); // Evil Clone spawned. Clone spawned. Slave clone spawned. + return 0; + } + clif_displaymessage(fd, msg_txt(129+flag*2)); // Unable to spawn evil clone. Unable to spawn clone. Unable to spawn slave clone. + return 0; +} + +/*=================================== + * Main chat [LuzZza] + * Usage: @main <on|off|message> + *-----------------------------------*/ +ACMD_FUNC(main) +{ + if( message[0] ) { + + if(strcmpi(message, "on") == 0) { + if(!sd->state.mainchat) { + sd->state.mainchat = 1; + clif_displaymessage(fd, msg_txt(380)); // Main chat has been activated. + } else { + clif_displaymessage(fd, msg_txt(381)); // Main chat already activated. + } + } else if(strcmpi(message, "off") == 0) { + if(sd->state.mainchat) { + sd->state.mainchat = 0; + clif_displaymessage(fd, msg_txt(382)); // Main chat has been disabled. + } else { + clif_displaymessage(fd, msg_txt(383)); // Main chat already disabled. + } + } else { + if(!sd->state.mainchat) { + sd->state.mainchat = 1; + clif_displaymessage(fd, msg_txt(380)); // Main chat has been activated. + } + if (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) { + clif_displaymessage(fd, msg_txt(387)); + return -1; + } + + if ( battle_config.min_chat_delay ) { + if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) + return 0; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + // send the message using inter-server system + intif_main_message( sd, message ); + } + + } else { + + if(sd->state.mainchat) + clif_displaymessage(fd, msg_txt(384)); // Main chat currently enabled. Usage: @main <on|off>, @main <message>. + else + clif_displaymessage(fd, msg_txt(385)); // Main chat currently disabled. Usage: @main <on|off>, @main <message>. + } + return 0; +} + +/*===================================== + * Autorejecting Invites/Deals [LuzZza] + * Usage: @noask + *-------------------------------------*/ +ACMD_FUNC(noask) +{ + if(sd->state.noask) { + clif_displaymessage(fd, msg_txt(391)); // Autorejecting is deactivated. + sd->state.noask = 0; + } else { + clif_displaymessage(fd, msg_txt(390)); // Autorejecting is activated. + sd->state.noask = 1; + } + + return 0; +} + +/*===================================== + * Send a @request message to all GMs of lowest_gm_level. + * Usage: @request <petition> + *-------------------------------------*/ +ACMD_FUNC(request) +{ + if (!message || !*message) { + clif_displaymessage(sd->fd,msg_txt(277)); // Usage: @request <petition/message to online GMs>. + return -1; + } + + sprintf(atcmd_output, msg_txt(278), message); // (@request): %s + intif_wis_message_to_gm(sd->status.name, PC_PERM_RECEIVE_REQUESTS, atcmd_output); + clif_disp_onlyself(sd, atcmd_output, strlen(atcmd_output)); + clif_displaymessage(sd->fd,msg_txt(279)); // @request sent. + return 0; +} + +/*========================================== + * Feel (SG save map) Reset [HiddenDragon] + *------------------------------------------*/ +ACMD_FUNC(feelreset) +{ + pc_resetfeel(sd); + clif_displaymessage(fd, msg_txt(1324)); // Reset 'Feeling' maps. + + return 0; +} + +/*========================================== + * AUCTION SYSTEM + *------------------------------------------*/ +ACMD_FUNC(auction) +{ + nullpo_ret(sd); + + clif_Auction_openwindow(sd); + + return 0; +} + +/*========================================== + * Kill Steal Protection + *------------------------------------------*/ +ACMD_FUNC(ksprotection) +{ + nullpo_retr(-1,sd); + + if( sd->state.noks ) { + sd->state.noks = 0; + clif_displaymessage(fd, msg_txt(1325)); // [ K.S Protection Inactive ] + } + else + { + if( !message || !*message || !strcmpi(message, "party") ) + { // Default is Party + sd->state.noks = 2; + clif_displaymessage(fd, msg_txt(1326)); // [ K.S Protection Active - Option: Party ] + } + else if( !strcmpi(message, "self") ) + { + sd->state.noks = 1; + clif_displaymessage(fd, msg_txt(1327)); // [ K.S Protection Active - Option: Self ] + } + else if( !strcmpi(message, "guild") ) + { + sd->state.noks = 3; + clif_displaymessage(fd, msg_txt(1328)); // [ K.S Protection Active - Option: Guild ] + } + else + clif_displaymessage(fd, msg_txt(1329)); // Usage: @noks <self|party|guild> + } + return 0; +} +/*========================================== + * Map Kill Steal Protection Setting + *------------------------------------------*/ +ACMD_FUNC(allowks) +{ + nullpo_retr(-1,sd); + + if( map[sd->bl.m].flag.allowks ) { + map[sd->bl.m].flag.allowks = 0; + clif_displaymessage(fd, msg_txt(1330)); // [ Map K.S Protection Active ] + } else { + map[sd->bl.m].flag.allowks = 1; + clif_displaymessage(fd, msg_txt(1331)); // [ Map K.S Protection Inactive ] + } + return 0; +} + +ACMD_FUNC(resetstat) +{ + nullpo_retr(-1, sd); + + pc_resetstate(sd); + sprintf(atcmd_output, msg_txt(207), sd->status.name); + clif_displaymessage(fd, atcmd_output); + return 0; +} + +ACMD_FUNC(resetskill) +{ + nullpo_retr(-1,sd); + + pc_resetskill(sd,1); + sprintf(atcmd_output, msg_txt(206), sd->status.name); + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * #storagelist: Displays the items list of a player's storage. + * #cartlist: Displays contents of target's cart. + * #itemlist: Displays contents of target's inventory. + *------------------------------------------*/ +ACMD_FUNC(itemlist) +{ + int i, j, count, counter; + const char* location; + const struct item* items; + int size; + StringBuf buf; + + nullpo_retr(-1, sd); + + if( strcmp(command+1, "storagelist") == 0 ) + { + location = "storage"; + items = sd->status.storage.items; + size = MAX_STORAGE; + } + else + if( strcmp(command+1, "cartlist") == 0 ) + { + location = "cart"; + items = sd->status.cart; + size = MAX_CART; + } + else + if( strcmp(command+1, "itemlist") == 0 ) + { + location = "inventory"; + items = sd->status.inventory; + size = MAX_INVENTORY; + } + else + return 1; + + StringBuf_Init(&buf); + + count = 0; // total slots occupied + counter = 0; // total items found + for( i = 0; i < size; ++i ) + { + const struct item* it = &items[i]; + struct item_data* itd; + + if( it->nameid == 0 || (itd = itemdb_exists(it->nameid)) == NULL ) + continue; + + counter += it->amount; + count++; + + if( count == 1 ) + { + StringBuf_Printf(&buf, msg_txt(1332), location, sd->status.name); // ------ %s items list of '%s' ------ + clif_displaymessage(fd, StringBuf_Value(&buf)); + StringBuf_Clear(&buf); + } + + if( it->refine ) + StringBuf_Printf(&buf, "%d %s %+d (%s, id: %d)", it->amount, itd->jname, it->refine, itd->name, it->nameid); + else + StringBuf_Printf(&buf, "%d %s (%s, id: %d)", it->amount, itd->jname, itd->name, it->nameid); + + if( it->equip ) + { + char equipstr[CHAT_SIZE_MAX]; + strcpy(equipstr, msg_txt(1333)); // | equipped: + if( it->equip & EQP_GARMENT ) + strcat(equipstr, msg_txt(1334)); // garment, + if( it->equip & EQP_ACC_L ) + strcat(equipstr, msg_txt(1335)); // left accessory, + if( it->equip & EQP_ARMOR ) + strcat(equipstr, msg_txt(1336)); // body/armor, + if( (it->equip & EQP_ARMS) == EQP_HAND_R ) + strcat(equipstr, msg_txt(1337)); // right hand, + if( (it->equip & EQP_ARMS) == EQP_HAND_L ) + strcat(equipstr, msg_txt(1338)); // left hand, + if( (it->equip & EQP_ARMS) == EQP_ARMS ) + strcat(equipstr, msg_txt(1339)); // both hands, + if( it->equip & EQP_SHOES ) + strcat(equipstr, msg_txt(1340)); // feet, + if( it->equip & EQP_ACC_R ) + strcat(equipstr, msg_txt(1341)); // right accessory, + if( (it->equip & EQP_HELM) == EQP_HEAD_LOW ) + strcat(equipstr, msg_txt(1342)); // lower head, + if( (it->equip & EQP_HELM) == EQP_HEAD_TOP ) + strcat(equipstr, msg_txt(1343)); // top head, + if( (it->equip & EQP_HELM) == (EQP_HEAD_LOW|EQP_HEAD_TOP) ) + strcat(equipstr, msg_txt(1344)); // lower/top head, + if( (it->equip & EQP_HELM) == EQP_HEAD_MID ) + strcat(equipstr, msg_txt(1345)); // mid head, + if( (it->equip & EQP_HELM) == (EQP_HEAD_LOW|EQP_HEAD_MID) ) + strcat(equipstr, msg_txt(1346)); // lower/mid head, + if( (it->equip & EQP_HELM) == EQP_HELM ) + strcat(equipstr, msg_txt(1347)); // lower/mid/top head, + // remove final ', ' + equipstr[strlen(equipstr) - 2] = '\0'; + StringBuf_AppendStr(&buf, equipstr); + } + + clif_displaymessage(fd, StringBuf_Value(&buf)); + StringBuf_Clear(&buf); + + if( it->card[0] == CARD0_PET ) + {// pet egg + if (it->card[3]) + StringBuf_Printf(&buf, msg_txt(1348), (unsigned int)MakeDWord(it->card[1], it->card[2])); // -> (pet egg, pet id: %u, named) + else + StringBuf_Printf(&buf, msg_txt(1349), (unsigned int)MakeDWord(it->card[1], it->card[2])); // -> (pet egg, pet id: %u, unnamed) + } + else + if(it->card[0] == CARD0_FORGE) + {// forged item + StringBuf_Printf(&buf, msg_txt(1350), (unsigned int)MakeDWord(it->card[2], it->card[3]), it->card[1]>>8, it->card[1]&0x0f); // -> (crafted item, creator id: %u, star crumbs %d, element %d) + } + else + if(it->card[0] == CARD0_CREATE) + {// created item + StringBuf_Printf(&buf, msg_txt(1351), (unsigned int)MakeDWord(it->card[2], it->card[3])); // -> (produced item, creator id: %u) + } + else + {// normal item + int counter2 = 0; + + for( j = 0; j < itd->slot; ++j ) + { + struct item_data* card; + + if( it->card[j] == 0 || (card = itemdb_exists(it->card[j])) == NULL ) + continue; + + counter2++; + + if( counter2 == 1 ) + StringBuf_AppendStr(&buf, msg_txt(1352)); // -> (card(s): + + if( counter2 != 1 ) + StringBuf_AppendStr(&buf, ", "); + + StringBuf_Printf(&buf, "#%d %s (id: %d)", counter2, card->jname, card->nameid); + } + + if( counter2 > 0 ) + StringBuf_AppendStr(&buf, ")"); + } + + if( StringBuf_Length(&buf) > 0 ) + clif_displaymessage(fd, StringBuf_Value(&buf)); + + StringBuf_Clear(&buf); + } + + if( count == 0 ) + StringBuf_Printf(&buf, msg_txt(1353), location); // No item found in this player's %s. + else + StringBuf_Printf(&buf, msg_txt(1354), counter, count, location); // %d item(s) found in %d %s slots. + + clif_displaymessage(fd, StringBuf_Value(&buf)); + + StringBuf_Destroy(&buf); + + return 0; +} + +ACMD_FUNC(stats) +{ + char job_jobname[100]; + char output[CHAT_SIZE_MAX]; + int i; + struct { + const char* format; + int value; + } output_table[] = { + { "Base Level - %d", 0 }, + { NULL, 0 }, + { "Hp - %d", 0 }, + { "MaxHp - %d", 0 }, + { "Sp - %d", 0 }, + { "MaxSp - %d", 0 }, + { "Str - %3d", 0 }, + { "Agi - %3d", 0 }, + { "Vit - %3d", 0 }, + { "Int - %3d", 0 }, + { "Dex - %3d", 0 }, + { "Luk - %3d", 0 }, + { "Zeny - %d", 0 }, + { "Free SK Points - %d", 0 }, + { "JobChangeLvl (2nd) - %d", 0 }, + { "JobChangeLvl (3rd) - %d", 0 }, + { NULL, 0 } + }; + + memset(job_jobname, '\0', sizeof(job_jobname)); + memset(output, '\0', sizeof(output)); + + //direct array initialization with variables is not standard C compliant. + output_table[0].value = sd->status.base_level; + output_table[1].format = job_jobname; + output_table[1].value = sd->status.job_level; + output_table[2].value = sd->status.hp; + output_table[3].value = sd->status.max_hp; + output_table[4].value = sd->status.sp; + output_table[5].value = sd->status.max_sp; + output_table[6].value = sd->status.str; + output_table[7].value = sd->status.agi; + output_table[8].value = sd->status.vit; + output_table[9].value = sd->status.int_; + output_table[10].value = sd->status.dex; + output_table[11].value = sd->status.luk; + output_table[12].value = sd->status.zeny; + output_table[13].value = sd->status.skill_point; + output_table[14].value = sd->change_level_2nd; + output_table[15].value = sd->change_level_3rd; + + sprintf(job_jobname, "Job - %s %s", job_name(sd->status.class_), "(level %d)"); + sprintf(output, msg_txt(53), sd->status.name); // '%s' stats: + + clif_displaymessage(fd, output); + + for (i = 0; output_table[i].format != NULL; i++) { + sprintf(output, output_table[i].format, output_table[i].value); + clif_displaymessage(fd, output); + } + + return 0; +} + +ACMD_FUNC(delitem) +{ + char item_name[100]; + int nameid, amount = 0, total, idx; + struct item_data* id; + + nullpo_retr(-1, sd); + + if( !message || !*message || ( sscanf(message, "\"%99[^\"]\" %d", item_name, &amount) < 2 && sscanf(message, "%99s %d", item_name, &amount) < 2 ) || amount < 1 ) + { + clif_displaymessage(fd, msg_txt(1355)); // Please enter an item name/ID, a quantity, and a player name (usage: #delitem <player> <item_name_or_ID> <quantity>). + return -1; + } + + if( ( id = itemdb_searchname(item_name) ) != NULL || ( id = itemdb_exists(atoi(item_name)) ) != NULL ) + { + nameid = id->nameid; + } + else + { + clif_displaymessage(fd, msg_txt(19)); // Invalid item ID or name. + return -1; + } + + total = amount; + + // delete items + while( amount && ( idx = pc_search_inventory(sd, nameid) ) != -1 ) + { + int delamount = ( amount < sd->status.inventory[idx].amount ) ? amount : sd->status.inventory[idx].amount; + + if( sd->inventory_data[idx]->type == IT_PETEGG && sd->status.inventory[idx].card[0] == CARD0_PET ) + {// delete pet + intif_delete_petdata(MakeDWord(sd->status.inventory[idx].card[1], sd->status.inventory[idx].card[2])); + } + pc_delitem(sd, idx, delamount, 0, 0, LOG_TYPE_COMMAND); + + amount-= delamount; + } + + // notify target + sprintf(atcmd_output, msg_txt(113), total-amount); // %d item(s) removed by a GM. + clif_displaymessage(sd->fd, atcmd_output); + + // notify source + if( amount == total ) + { + clif_displaymessage(fd, msg_txt(116)); // Character does not have the item. + } + else if( amount ) + { + sprintf(atcmd_output, msg_txt(115), total-amount, total-amount, total); // %d item(s) removed. Player had only %d on %d items. + clif_displaymessage(fd, atcmd_output); + } + else + { + sprintf(atcmd_output, msg_txt(114), total); // %d item(s) removed from the player. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * Custom Fonts + *------------------------------------------*/ +ACMD_FUNC(font) +{ + int font_id; + nullpo_retr(-1,sd); + + font_id = atoi(message); + if( font_id == 0 ) + { + if( sd->user_font ) + { + sd->user_font = 0; + clif_displaymessage(fd, msg_txt(1356)); // Returning to normal font. + clif_font(sd); + } + else + { + clif_displaymessage(fd, msg_txt(1357)); // Use @font <1-9> to change your message font. + clif_displaymessage(fd, msg_txt(1358)); // Use 0 or no parameter to return to normal font. + } + } + else if( font_id < 0 || font_id > 9 ) + clif_displaymessage(fd, msg_txt(1359)); // Invalid font. Use a value from 0 to 9. + else if( font_id != sd->user_font ) + { + sd->user_font = font_id; + clif_font(sd); + clif_displaymessage(fd, msg_txt(1360)); // Font changed. + } + else + clif_displaymessage(fd, msg_txt(1361)); // Already using this font. + + return 0; +} + +/*========================================== + * type: 1 = commands (@), 2 = charcommands (#) + *------------------------------------------*/ +static void atcommand_commands_sub(struct map_session_data* sd, const int fd, AtCommandType type) +{ + char line_buff[CHATBOX_SIZE]; + char* cur = line_buff; + AtCommandInfo* cmd; + DBIterator *iter = db_iterator(atcommand_db); + int count = 0; + + memset(line_buff,' ',CHATBOX_SIZE); + line_buff[CHATBOX_SIZE-1] = 0; + + clif_displaymessage(fd, msg_txt(273)); // "Commands available:" + + for (cmd = dbi_first(iter); dbi_exists(iter); cmd = dbi_next(iter)) { + unsigned int slen = 0; + + switch( type ) { + case COMMAND_CHARCOMMAND: + if( cmd->char_groups[sd->group_pos] == 0 ) + continue; + break; + case COMMAND_ATCOMMAND: + if( cmd->at_groups[sd->group_pos] == 0 ) + continue; + break; + default: + continue; + } + + + slen = strlen(cmd->command); + + // flush the text buffer if this command won't fit into it + if ( slen + cur - line_buff >= CHATBOX_SIZE ) + { + clif_displaymessage(fd,line_buff); + cur = line_buff; + memset(line_buff,' ',CHATBOX_SIZE); + line_buff[CHATBOX_SIZE-1] = 0; + } + + memcpy(cur,cmd->command,slen); + cur += slen+(10-slen%10); + + count++; + } + dbi_destroy(iter); + clif_displaymessage(fd,line_buff); + + sprintf(atcmd_output, msg_txt(274), count); // "%d commands found." + clif_displaymessage(fd, atcmd_output); + + return; +} + +/*========================================== + * @commands Lists available @ commands to you + *------------------------------------------*/ +ACMD_FUNC(commands) +{ + atcommand_commands_sub(sd, fd, COMMAND_ATCOMMAND); + return 0; +} + +/*========================================== + * @charcommands Lists available # commands to you + *------------------------------------------*/ +ACMD_FUNC(charcommands) +{ + atcommand_commands_sub(sd, fd, COMMAND_CHARCOMMAND); + return 0; +} +/* for new mounts */ +ACMD_FUNC(mount2) { + + clif_displaymessage(sd->fd,msg_txt(1362)); // NOTICE: If you crash with mount your LUA is outdated. + if( !(sd->sc.option&OPTION_MOUNTING) ) { + clif_displaymessage(sd->fd,msg_txt(1363)); // You have mounted. + pc_setoption(sd, sd->sc.option|OPTION_MOUNTING); + } else { + clif_displaymessage(sd->fd,msg_txt(1364)); // You have released your mount. + pc_setoption(sd, sd->sc.option&~OPTION_MOUNTING); + } + return 0; +} + +ACMD_FUNC(accinfo) { + char query[NAME_LENGTH]; + + if (!message || !*message || strlen(message) > NAME_LENGTH ) { + clif_displaymessage(fd, msg_txt(1365)); // Usage: @accinfo/@accountinfo <account_id/char name> + clif_displaymessage(fd, msg_txt(1366)); // You may search partial name by making use of '%' in the search, ex. "@accinfo %Mario%" lists all characters whose name contains "Mario". + return -1; + } + + //remove const type + safestrncpy(query, message, NAME_LENGTH); + + intif_request_accinfo( sd->fd, sd->bl.id, sd->group_id, query ); + + return 0; +} + +/* [Ind] */ +ACMD_FUNC(set) { + char reg[32], val[128]; + struct script_data* data; + int toset = 0; + bool is_str = false; + + if( !message || !*message || (toset = sscanf(message, "%32s %128[^\n]s", reg, val)) < 1 ) { + clif_displaymessage(fd, msg_txt(1367)); // Usage: @set <variable name> <value> + clif_displaymessage(fd, msg_txt(1368)); // Usage: ex. "@set PoringCharVar 50" + clif_displaymessage(fd, msg_txt(1369)); // Usage: ex. "@set PoringCharVarSTR$ Super Duper String" + clif_displaymessage(fd, msg_txt(1370)); // Usage: ex. "@set PoringCharVarSTR$" outputs its value, Super Duper String. + return -1; + } + + /* disabled variable types (they require a proper script state to function, so allowing them would crash the server) */ + if( reg[0] == '.' ) { + clif_displaymessage(fd, msg_txt(1371)); // NPC variables may not be used with @set. + return -1; + } else if( reg[0] == '\'' ) { + clif_displaymessage(fd, msg_txt(1372)); // Instance variables may not be used with @set. + return -1; + } + + is_str = ( reg[strlen(reg) - 1] == '$' ) ? true : false; + + if( toset >= 2 ) {/* we only set the var if there is an val, otherwise we only output the value */ + if( is_str ) + set_var(sd, reg, (void*) val); + else + set_var(sd, reg, (void*)__64BPRTSIZE((atoi(val)))); + + } + + CREATE(data, struct script_data,1); + + + if( is_str ) {// string variable + + switch( reg[0] ) { + case '@': + data->u.str = pc_readregstr(sd, add_str(reg)); + break; + case '$': + data->u.str = mapreg_readregstr(add_str(reg)); + break; + case '#': + if( reg[1] == '#' ) + data->u.str = pc_readaccountreg2str(sd, reg);// global + else + data->u.str = pc_readaccountregstr(sd, reg);// local + break; + default: + data->u.str = pc_readglobalreg_str(sd, reg); + break; + } + + if( data->u.str == NULL || data->u.str[0] == '\0' ) {// empty string + data->type = C_CONSTSTR; + data->u.str = ""; + } else {// duplicate string + data->type = C_STR; + data->u.str = aStrdup(data->u.str); + } + + } else {// integer variable + + data->type = C_INT; + switch( reg[0] ) { + case '@': + data->u.num = pc_readreg(sd, add_str(reg)); + break; + case '$': + data->u.num = mapreg_readreg(add_str(reg)); + break; + case '#': + if( reg[1] == '#' ) + data->u.num = pc_readaccountreg2(sd, reg);// global + else + data->u.num = pc_readaccountreg(sd, reg);// local + break; + default: + data->u.num = pc_readglobalreg(sd, reg); + break; + } + + } + + + switch( data->type ) { + case C_INT: + sprintf(atcmd_output,msg_txt(1373),reg,data->u.num); // %s value is now :%d + break; + case C_STR: + sprintf(atcmd_output,msg_txt(1374),reg,data->u.str); // %s value is now :%s + break; + case C_CONSTSTR: + sprintf(atcmd_output,msg_txt(1375),reg); // %s is empty + break; + default: + sprintf(atcmd_output,msg_txt(1376),reg,data->type); // %s data type is not supported :%u + break; + } + + clif_displaymessage(fd, atcmd_output); + + aFree(data); + + return 0; +} +ACMD_FUNC(reloadquestdb) { + do_reload_quest(); + clif_displaymessage(fd, msg_txt(1377)); // Quest database has been reloaded. + return 0; +} +ACMD_FUNC(addperm) { + int perm_size = ARRAYLENGTH(pc_g_permission_name); + bool add = (strcmpi(command+1, "addperm") == 0) ? true : false; + int i; + + if( !message || !*message ) { + sprintf(atcmd_output, msg_txt(1378),command); // Usage: %s <permission_name> + clif_displaymessage(fd, atcmd_output); + clif_displaymessage(fd, msg_txt(1379)); // -- Permission List + for( i = 0; i < perm_size; i++ ) { + sprintf(atcmd_output,"- %s",pc_g_permission_name[i].name); + clif_displaymessage(fd, atcmd_output); + } + return -1; + } + + ARR_FIND(0, perm_size, i, strcmpi(pc_g_permission_name[i].name, message) == 0); + + if( i == perm_size ) { + sprintf(atcmd_output,msg_txt(1380),message); // '%s' is not a known permission. + clif_displaymessage(fd, atcmd_output); + clif_displaymessage(fd, msg_txt(1379)); // -- Permission List + for( i = 0; i < perm_size; i++ ) { + sprintf(atcmd_output,"- %s",pc_g_permission_name[i].name); + clif_displaymessage(fd, atcmd_output); + } + return -1; + } + + if( add && (sd->permissions&pc_g_permission_name[i].permission) ) { + sprintf(atcmd_output, msg_txt(1381),sd->status.name,pc_g_permission_name[i].name); // User '%s' already possesses the '%s' permission. + clif_displaymessage(fd, atcmd_output); + return -1; + } else if ( !add && !(sd->permissions&pc_g_permission_name[i].permission) ) { + sprintf(atcmd_output, msg_txt(1382),sd->status.name,pc_g_permission_name[i].name); // User '%s' doesn't possess the '%s' permission. + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output,msg_txt(1383),sd->status.name); // -- User '%s' Permissions + clif_displaymessage(fd, atcmd_output); + for( i = 0; i < perm_size; i++ ) { + if( sd->permissions&pc_g_permission_name[i].permission ) { + sprintf(atcmd_output,"- %s",pc_g_permission_name[i].name); + clif_displaymessage(fd, atcmd_output); + } + } + + return -1; + } + + if( add ) + sd->permissions |= pc_g_permission_name[i].permission; + else + sd->permissions &=~ pc_g_permission_name[i].permission; + + + sprintf(atcmd_output, msg_txt(1384),sd->status.name); // User '%s' permissions updated successfully. The changes are temporary. + clif_displaymessage(fd, atcmd_output); + + return 0; +} +ACMD_FUNC(unloadnpcfile) { + + if( !message || !*message ) { + clif_displaymessage(fd, msg_txt(1385)); // Usage: @unloadnpcfile <file name> + return -1; + } + + if( npc_unloadfile(message) ) + clif_displaymessage(fd, msg_txt(1386)); // File unloaded. Be aware that mapflags and monsters spawned directly are not removed. + else { + clif_displaymessage(fd, msg_txt(1387)); // File not found. + return -1; + } + return 0; +} +ACMD_FUNC(cart) { +#define MC_CART_MDFY(x) \ + sd->status.skill[MC_PUSHCART].id = x?MC_PUSHCART:0; \ + sd->status.skill[MC_PUSHCART].lv = x?1:0; \ + sd->status.skill[MC_PUSHCART].flag = x?1:0; + + int val = atoi(message); + bool need_skill = pc_checkskill(sd, MC_PUSHCART) ? false : true; + + if( !message || !*message || val < 0 || val > MAX_CARTS ) { + sprintf(atcmd_output, msg_txt(1390),command,MAX_CARTS); // Unknown Cart (usage: %s <0-%d>). + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if( val == 0 && !pc_iscarton(sd) ) { + clif_displaymessage(fd, msg_txt(1391)); // You do not possess a cart to be removed + return -1; + } + + if( need_skill ) { + MC_CART_MDFY(1); + } + + if( pc_setcart(sd, val) ) { + if( need_skill ) { + MC_CART_MDFY(0); + } + return -1;/* @cart failed */ + } + + if( need_skill ) { + MC_CART_MDFY(0); + } + + clif_displaymessage(fd, msg_txt(1392)); // Cart Added + + return 0; + #undef MC_CART_MDFY +} + +/** + * Fills the reference of available commands in atcommand DBMap + **/ +#define ACMD_DEF(x) { #x, atcommand_ ## x, NULL, NULL } +#define ACMD_DEF2(x2, x) { x2, atcommand_ ## x, NULL, NULL } +void atcommand_basecommands(void) { + /** + * Command reference list, place the base of your commands here + **/ + AtCommandInfo atcommand_base[] = { + ACMD_DEF2("warp", mapmove), + ACMD_DEF(where), + ACMD_DEF(jumpto), + ACMD_DEF(jump), + ACMD_DEF(who), + ACMD_DEF2("who2", who), + ACMD_DEF2("who3", who), + ACMD_DEF2("whomap", who), + ACMD_DEF2("whomap2", who), + ACMD_DEF2("whomap3", who), + ACMD_DEF(whogm), + ACMD_DEF(save), + ACMD_DEF(load), + ACMD_DEF(speed), + ACMD_DEF(storage), + ACMD_DEF(guildstorage), + ACMD_DEF(option), + ACMD_DEF(hide), // + /hide + ACMD_DEF(jobchange), + ACMD_DEF(kill), + ACMD_DEF(alive), + ACMD_DEF(kami), + ACMD_DEF2("kamib", kami), + ACMD_DEF2("kamic", kami), + ACMD_DEF2("lkami", kami), + ACMD_DEF(heal), + ACMD_DEF(item), + ACMD_DEF(item2), + ACMD_DEF(itemreset), + ACMD_DEF(clearstorage), + ACMD_DEF(cleargstorage), + ACMD_DEF(clearcart), + ACMD_DEF2("blvl", baselevelup), + ACMD_DEF2("jlvl", joblevelup), + ACMD_DEF(help), + ACMD_DEF(pvpoff), + ACMD_DEF(pvpon), + ACMD_DEF(gvgoff), + ACMD_DEF(gvgon), + ACMD_DEF(model), + ACMD_DEF(go), + ACMD_DEF(monster), + ACMD_DEF2("monstersmall", monster), + ACMD_DEF2("monsterbig", monster), + ACMD_DEF(killmonster), + ACMD_DEF2("killmonster2", killmonster), + ACMD_DEF(refine), + ACMD_DEF(produce), + ACMD_DEF(memo), + ACMD_DEF(gat), + ACMD_DEF(displaystatus), + ACMD_DEF2("stpoint", statuspoint), + ACMD_DEF2("skpoint", skillpoint), + ACMD_DEF(zeny), + ACMD_DEF2("str", param), + ACMD_DEF2("agi", param), + ACMD_DEF2("vit", param), + ACMD_DEF2("int", param), + ACMD_DEF2("dex", param), + ACMD_DEF2("luk", param), + ACMD_DEF2("glvl", guildlevelup), + ACMD_DEF(makeegg), + ACMD_DEF(hatch), + ACMD_DEF(petfriendly), + ACMD_DEF(pethungry), + ACMD_DEF(petrename), + ACMD_DEF(recall), // + /recall + ACMD_DEF(night), + ACMD_DEF(day), + ACMD_DEF(doom), + ACMD_DEF(doommap), + ACMD_DEF(raise), + ACMD_DEF(raisemap), + ACMD_DEF(kick), // + right click menu for GM "(name) force to quit" + ACMD_DEF(kickall), + ACMD_DEF(allskill), + ACMD_DEF(questskill), + ACMD_DEF(lostskill), + ACMD_DEF(spiritball), + ACMD_DEF(party), + ACMD_DEF(guild), + ACMD_DEF(breakguild), + ACMD_DEF(agitstart), + ACMD_DEF(agitend), + ACMD_DEF(mapexit), + ACMD_DEF(idsearch), + ACMD_DEF(broadcast), // + /b and /nb + ACMD_DEF(localbroadcast), // + /lb and /nlb + ACMD_DEF(recallall), + ACMD_DEF(reloaditemdb), + ACMD_DEF(reloadmobdb), + ACMD_DEF(reloadskilldb), + ACMD_DEF(reloadscript), + ACMD_DEF(reloadatcommand), + ACMD_DEF(reloadbattleconf), + ACMD_DEF(reloadstatusdb), + ACMD_DEF(reloadpcdb), + ACMD_DEF(reloadmotd), + ACMD_DEF(mapinfo), + ACMD_DEF(dye), + ACMD_DEF2("hairstyle", hair_style), + ACMD_DEF2("haircolor", hair_color), + ACMD_DEF2("allstats", stat_all), + ACMD_DEF2("block", char_block), + ACMD_DEF2("ban", char_ban), + ACMD_DEF2("unblock", char_unblock), + ACMD_DEF2("unban", char_unban), + ACMD_DEF2("mount", mount_peco), + ACMD_DEF(guildspy), + ACMD_DEF(partyspy), + ACMD_DEF(repairall), + ACMD_DEF(guildrecall), + ACMD_DEF(partyrecall), + ACMD_DEF(nuke), + ACMD_DEF(shownpc), + ACMD_DEF(hidenpc), + ACMD_DEF(loadnpc), + ACMD_DEF(unloadnpc), + ACMD_DEF2("time", servertime), + ACMD_DEF(jail), + ACMD_DEF(unjail), + ACMD_DEF(jailfor), + ACMD_DEF(jailtime), + ACMD_DEF(disguise), + ACMD_DEF(undisguise), + ACMD_DEF(email), + ACMD_DEF(effect), + ACMD_DEF(follow), + ACMD_DEF(addwarp), + ACMD_DEF(skillon), + ACMD_DEF(skilloff), + ACMD_DEF(killer), + ACMD_DEF(npcmove), + ACMD_DEF(killable), + ACMD_DEF(dropall), + ACMD_DEF(storeall), + ACMD_DEF(skillid), + ACMD_DEF(useskill), + ACMD_DEF(displayskill), + ACMD_DEF(snow), + ACMD_DEF(sakura), + ACMD_DEF(clouds), + ACMD_DEF(clouds2), + ACMD_DEF(fog), + ACMD_DEF(fireworks), + ACMD_DEF(leaves), + ACMD_DEF(summon), + ACMD_DEF(adjgroup), + ACMD_DEF(trade), + ACMD_DEF(send), + ACMD_DEF(setbattleflag), + ACMD_DEF(unmute), + ACMD_DEF(clearweather), + ACMD_DEF(uptime), + ACMD_DEF(changesex), + ACMD_DEF(mute), + ACMD_DEF(refresh), + ACMD_DEF(refreshall), + ACMD_DEF(identify), + ACMD_DEF(gmotd), + ACMD_DEF(misceffect), + ACMD_DEF(mobsearch), + ACMD_DEF(cleanmap), + ACMD_DEF(cleanarea), + ACMD_DEF(npctalk), + ACMD_DEF(pettalk), + ACMD_DEF(users), + ACMD_DEF(reset), + ACMD_DEF(skilltree), + ACMD_DEF(marry), + ACMD_DEF(divorce), + ACMD_DEF(sound), + ACMD_DEF(undisguiseall), + ACMD_DEF(disguiseall), + ACMD_DEF(changelook), + ACMD_DEF(autoloot), + ACMD_DEF2("alootid", autolootitem), + ACMD_DEF(mobinfo), + ACMD_DEF(exp), + ACMD_DEF(version), + ACMD_DEF(mutearea), + ACMD_DEF(rates), + ACMD_DEF(iteminfo), + ACMD_DEF(whodrops), + ACMD_DEF(whereis), + ACMD_DEF(mapflag), + ACMD_DEF(me), + ACMD_DEF(monsterignore), + ACMD_DEF(fakename), + ACMD_DEF(size), + ACMD_DEF(showexp), + ACMD_DEF(showzeny), + ACMD_DEF(showdelay), + ACMD_DEF(autotrade), + ACMD_DEF(changegm), + ACMD_DEF(changeleader), + ACMD_DEF(partyoption), + ACMD_DEF(invite), + ACMD_DEF(duel), + ACMD_DEF(leave), + ACMD_DEF(accept), + ACMD_DEF(reject), + ACMD_DEF(main), + ACMD_DEF(clone), + ACMD_DEF2("slaveclone", clone), + ACMD_DEF2("evilclone", clone), + ACMD_DEF(tonpc), + ACMD_DEF(commands), + ACMD_DEF(noask), + ACMD_DEF(request), + ACMD_DEF(homlevel), + ACMD_DEF(homevolution), + ACMD_DEF(hommutate), + ACMD_DEF(makehomun), + ACMD_DEF(homfriendly), + ACMD_DEF(homhungry), + ACMD_DEF(homtalk), + ACMD_DEF(hominfo), + ACMD_DEF(homstats), + ACMD_DEF(homshuffle), + ACMD_DEF(showmobs), + ACMD_DEF(feelreset), + ACMD_DEF(auction), + ACMD_DEF(mail), + ACMD_DEF2("noks", ksprotection), + ACMD_DEF(allowks), + ACMD_DEF(cash), + ACMD_DEF2("points", cash), + ACMD_DEF(agitstart2), + ACMD_DEF(agitend2), + ACMD_DEF2("skreset", resetskill), + ACMD_DEF2("streset", resetstat), + ACMD_DEF2("storagelist", itemlist), + ACMD_DEF2("cartlist", itemlist), + ACMD_DEF2("itemlist", itemlist), + ACMD_DEF(stats), + ACMD_DEF(delitem), + ACMD_DEF(charcommands), + ACMD_DEF(font), + ACMD_DEF(accinfo), + ACMD_DEF(set), + ACMD_DEF(reloadquestdb), + ACMD_DEF(undisguiseguild), + ACMD_DEF(disguiseguild), + ACMD_DEF(sizeall), + ACMD_DEF(sizeguild), + ACMD_DEF(addperm), + ACMD_DEF2("rmvperm", addperm), + ACMD_DEF(unloadnpcfile), + ACMD_DEF(cart), + ACMD_DEF(mount2) + }; + AtCommandInfo* atcommand; + int i; + + for( i = 0; i < ARRAYLENGTH(atcommand_base); i++ ) { + if(atcommand_exists(atcommand_base[i].command)) { // Should not happen if atcommand_base[] array is OK + ShowDebug("atcommand_basecommands: duplicate ACMD_DEF for '%s'.\n", atcommand_base[i].command); + continue; + } + CREATE(atcommand, AtCommandInfo, 1); + safestrncpy(atcommand->command, atcommand_base[i].command, sizeof(atcommand->command)); + atcommand->func = atcommand_base[i].func; + strdb_put(atcommand_db, atcommand->command, atcommand); + } + return; +} + +/*========================================== + * Command lookup functions + *------------------------------------------*/ +bool atcommand_exists(const char* name) +{ + return strdb_exists(atcommand_db, name); +} + +static AtCommandInfo* get_atcommandinfo_byname(const char *name) +{ + if (strdb_exists(atcommand_db, name)) + return (AtCommandInfo*)strdb_get(atcommand_db, name); + return NULL; +} + +static const char* atcommand_checkalias(const char *aliasname) +{ + AliasInfo *alias_info = NULL; + if ((alias_info = (AliasInfo*)strdb_get(atcommand_alias_db, aliasname)) != NULL) + return alias_info->command->command; + return aliasname; +} + +/// AtCommand suggestion +static void atcommand_get_suggestions(struct map_session_data* sd, const char *name, bool atcommand) { + DBIterator* atcommand_iter; + DBIterator* alias_iter; + AtCommandInfo* command_info = NULL; + AliasInfo* alias_info = NULL; + AtCommandType type = atcommand ? COMMAND_ATCOMMAND : COMMAND_CHARCOMMAND; + char* full_match[MAX_SUGGESTIONS]; + char* suggestions[MAX_SUGGESTIONS]; + char* match; + int prefix_count = 0, full_count = 0; + bool can_use; + + if (!battle_config.atcommand_suggestions_enabled) + return; + + atcommand_iter = db_iterator(atcommand_db); + alias_iter = db_iterator(atcommand_alias_db); + + // Build the matches + for (command_info = dbi_first(atcommand_iter); dbi_exists(atcommand_iter); command_info = dbi_next(atcommand_iter)) { + match = strstr(command_info->command, name); + can_use = pc_can_use_command(sd, command_info->command, type); + if ( prefix_count < MAX_SUGGESTIONS && match == command_info->command && can_use ) { + suggestions[prefix_count] = command_info->command; + ++prefix_count; + } + if ( full_count < MAX_SUGGESTIONS && match != NULL && match != command_info->command && can_use ) { + full_match[full_count] = command_info->command; + ++full_count; + } + } + + for (alias_info = dbi_first(alias_iter); dbi_exists(alias_iter); alias_info = dbi_next(alias_iter)) { + match = strstr(alias_info->alias, name); + can_use = pc_can_use_command(sd, alias_info->command->command, type); + if ( prefix_count < MAX_SUGGESTIONS && match == alias_info->alias && can_use) { + suggestions[prefix_count] = alias_info->alias; + ++prefix_count; + } + if ( full_count < MAX_SUGGESTIONS && match != NULL && match != alias_info->alias && can_use ) { + full_match[full_count] = alias_info->alias; + ++full_count; + } + } + + if ((full_count+prefix_count) > 0) { + char buffer[512]; + int i; + + // Merge full match and prefix match results + if (prefix_count < MAX_SUGGESTIONS) { + memmove(&suggestions[prefix_count], full_match, sizeof(char*) * (MAX_SUGGESTIONS-prefix_count)); + prefix_count = min(prefix_count+full_count, MAX_SUGGESTIONS); + } + + // Build the suggestion string + strcpy(buffer, msg_txt(205)); + strcat(buffer,"\n"); + + for(i=0; i < prefix_count; ++i) { + strcat(buffer,suggestions[i]); + strcat(buffer," "); + } + + clif_displaymessage(sd->fd, buffer); + } + + dbi_destroy(atcommand_iter); + dbi_destroy(alias_iter); +} + +/// Executes an at-command. +bool is_atcommand(const int fd, struct map_session_data* sd, const char* message, int type) +{ + char charname[NAME_LENGTH], params[100]; + char charname2[NAME_LENGTH], params2[100]; + char command[100]; + char output[CHAT_SIZE_MAX]; + + //Reconstructed message + char atcmd_msg[CHAT_SIZE_MAX]; + + TBL_PC * ssd = NULL; //sd for target + AtCommandInfo * info; + + nullpo_retr(false, sd); + + //Shouldn't happen + if ( !message || !*message ) + return false; + + //Block NOCHAT but do not display it as a normal message + if ( sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCOMMAND ) + return true; + + // skip 10/11-langtype's codepage indicator, if detected + if ( message[0] == '|' && strlen(message) >= 4 && (message[3] == atcommand_symbol || message[3] == charcommand_symbol) ) + message += 3; + + //Should display as a normal message + if ( *message != atcommand_symbol && *message != charcommand_symbol ) + return false; + + // type value 0 = server invoked: bypass restrictions + // 1 = player invoked + if ( type == 1) { + //Commands are disabled on maps flagged as 'nocommand' + if ( map[sd->bl.m].nocommand && pc_get_group_level(sd) < map[sd->bl.m].nocommand ) { + clif_displaymessage(fd, msg_txt(143)); + return false; + } + } + + if (*message == charcommand_symbol) { + do { + int x, y, z; + + //Checks to see if #command has a name or a name + parameters. + x = sscanf(message, "%99s \"%23[^\"]\" %99[^\n]", command, charname, params); + y = sscanf(message, "%99s %23s %99[^\n]", command, charname2, params2); + + //z always has the value of the scan that was successful + z = ( x > 1 ) ? x : y; + + //#command + name means the sufficient target was used and anything else after + //can be looked at by the actual command function since most scan to see if the + //right parameters are used. + if ( x > 2 ) { + sprintf(atcmd_msg, "%s %s", command, params); + break; + } + else if ( y > 2 ) { + sprintf(atcmd_msg, "%s %s", command, params2); + break; + } + //Regardless of what style the #command is used, if it's correct, it will always have + //this value if there is no parameter. Send it as just the #command + else if ( z == 2 ) { + sprintf(atcmd_msg, "%s", command); + break; + } + + if( !pc_get_group_level(sd) ) { + if( x >= 1 || y >= 1 ) { /* we have command */ + info = get_atcommandinfo_byname(atcommand_checkalias(command + 1)); + if( !info || info->char_groups[sd->group_pos] == 0 ) /* if we can't use or doesn't exist: don't even display the command failed message */ + return false; + } else + return false;/* display as normal message */ + } + + sprintf(output, msg_txt(1388), charcommand_symbol); // Charcommand failed (usage: %c<command> <char name> <parameters>). + clif_displaymessage(fd, output); + return true; + } while(0); + } + else if (*message == atcommand_symbol) { + //atcmd_msg is constructed above differently for charcommands + //it's copied from message if not a charcommand so it can + //pass through the rest of the code compatible with both symbols + sprintf(atcmd_msg, "%s", message); + } + + //Clearing these to be used once more. + memset(command, '\0', sizeof(command)); + memset(params, '\0', sizeof(params)); + + //check to see if any params exist within this command + if( sscanf(atcmd_msg, "%99s %99[^\n]", command, params) < 2 ) + params[0] = '\0'; + + // @commands (script based) + if(type == 1 && atcmd_binding_count > 0) { + struct atcmd_binding_data * binding; + + // Check if the command initiated is a character command + if (*message == charcommand_symbol && + (ssd = map_nick2sd(charname)) == NULL && (ssd = map_nick2sd(charname2)) == NULL ) { + sprintf(output, msg_txt(1389), command); // %s failed. Player not found. + clif_displaymessage(fd, output); + return true; + } + + // Get atcommand binding + binding = get_atcommandbind_byname(command); + + // Check if the binding isn't NULL and there is a NPC event, level of usage met, et cetera + if( binding != NULL && binding->npc_event[0] && + ((*atcmd_msg == atcommand_symbol && pc_get_group_level(sd) >= binding->level) || + (*atcmd_msg == charcommand_symbol && pc_get_group_level(sd) >= binding->level2))) + { + // Check if self or character invoking; if self == character invoked, then self invoke. + bool invokeFlag = ((*atcmd_msg == atcommand_symbol) ? 1 : 0); + npc_do_atcmd_event((invokeFlag ? sd : ssd), command, params, binding->npc_event); + return true; + } + } + + //Grab the command information and check for the proper GM level required to use it or if the command exists + info = get_atcommandinfo_byname(atcommand_checkalias(command + 1)); + if (info == NULL) { + if( pc_get_group_level(sd) ) { // TODO: remove or replace with proper permission + sprintf(output, msg_txt(153), command); // "%s is Unknown Command." + clif_displaymessage(fd, output); + atcommand_get_suggestions(sd, command + 1, *message == atcommand_symbol); + return true; + } else + return false; + } + + // type == 1 : player invoked + if (type == 1) { + if ((*command == atcommand_symbol && info->at_groups[sd->group_pos] == 0) || + (*command == charcommand_symbol && info->char_groups[sd->group_pos] == 0) ) { + return false; + } + if( pc_isdead(sd) && pc_has_permission(sd,PC_PERM_DISABLE_CMD_DEAD) ) { + clif_displaymessage(fd, msg_txt(1393)); // You can't use commands while dead + return true; + } + } + + // Check if target is valid only if confirmed that player can use command. + if (*message == charcommand_symbol && + (ssd = map_nick2sd(charname)) == NULL && (ssd = map_nick2sd(charname2)) == NULL ) { + sprintf(output, msg_txt(1389), command); // %s failed. Player not found. + clif_displaymessage(fd, output); + return true; + } + + //Attempt to use the command + if ( (info->func(fd, (*atcmd_msg == atcommand_symbol) ? sd : ssd, command, params) != 0) ) + { + sprintf(output,msg_txt(154), command); // %s failed. + clif_displaymessage(fd, output); + return true; + } + + //Log only if successful. + if ( *atcmd_msg == atcommand_symbol ) + log_atcommand(sd, atcmd_msg); + else if ( *atcmd_msg == charcommand_symbol ) + log_atcommand(sd, message); + + return true; +} + +/*========================================== + * + *------------------------------------------*/ +static void atcommand_config_read(const char* config_filename) +{ + config_setting_t *aliases = NULL, *help = NULL; + const char *symbol = NULL; + int num_aliases = 0; + + if (conf_read_file(&atcommand_config, config_filename)) + return; + + // Command symbols + if (config_lookup_string(&atcommand_config, "atcommand_symbol", &symbol)) { + if (ISPRINT(*symbol) && // no control characters + *symbol != '/' && // symbol of client commands + *symbol != '%' && // symbol of party chat + *symbol != '$' && // symbol of guild chat + *symbol != charcommand_symbol) + atcommand_symbol = *symbol; + } + + if (config_lookup_string(&atcommand_config, "charcommand_symbol", &symbol)) { + if (ISPRINT(*symbol) && // no control characters + *symbol != '/' && // symbol of client commands + *symbol != '%' && // symbol of party chat + *symbol != '$' && // symbol of guild chat + *symbol != atcommand_symbol) + charcommand_symbol = *symbol; + } + + // Command aliases + aliases = config_lookup(&atcommand_config, "aliases"); + if (aliases != NULL) { + int i = 0; + int count = config_setting_length(aliases); + + for (i = 0; i < count; ++i) { + config_setting_t *command = NULL; + const char *commandname = NULL; + int j = 0, alias_count = 0; + AtCommandInfo *commandinfo = NULL; + + command = config_setting_get_elem(aliases, i); + if (config_setting_type(command) != CONFIG_TYPE_ARRAY) + continue; + commandname = config_setting_name(command); + if (!atcommand_exists(commandname)) { + ShowConfigWarning(command, "atcommand_config_read: can not set alias for non-existent command %s", commandname); + continue; + } + commandinfo = get_atcommandinfo_byname(commandname); + alias_count = config_setting_length(command); + for (j = 0; j < alias_count; ++j) { + const char *alias = config_setting_get_string_elem(command, j); + if (alias != NULL) { + AliasInfo *alias_info; + if (strdb_exists(atcommand_alias_db, alias)) { + ShowConfigWarning(command, "atcommand_config_read: alias %s already exists", alias); + continue; + } + CREATE(alias_info, AliasInfo, 1); + alias_info->command = commandinfo; + safestrncpy(alias_info->alias, alias, sizeof(alias_info->alias)); + strdb_put(atcommand_alias_db, alias, alias_info); + ++num_aliases; + } + } + } + } + + // Commands help + // We only check if all commands exist + help = config_lookup(&atcommand_config, "help"); + if (help != NULL) { + int count = config_setting_length(help); + int i; + + for (i = 0; i < count; ++i) { + config_setting_t *command = NULL; + const char *commandname = NULL; + + command = config_setting_get_elem(help, i); + commandname = config_setting_name(command); + if (!atcommand_exists(commandname)) + ShowConfigWarning(command, "atcommand_config_read: command %s does not exist", commandname); + } + } + + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' command aliases in '"CL_WHITE"%s"CL_RESET"'.\n", num_aliases, config_filename); + return; +} +void atcommand_db_load_groups(int* group_ids) { + DBIterator *iter = db_iterator(atcommand_db); + AtCommandInfo* cmd; + int i; + + for (cmd = dbi_first(iter); dbi_exists(iter); cmd = dbi_next(iter)) { + cmd->at_groups = aMalloc( pc_group_max * sizeof(char) ); + cmd->char_groups = aMalloc( pc_group_max * sizeof(char) ); + for(i = 0; i < pc_group_max; i++) { + if( pc_group_can_use_command(group_ids[i], cmd->command, COMMAND_ATCOMMAND ) ) + cmd->at_groups[i] = 1; + else + cmd->at_groups[i] = 0; + if( pc_group_can_use_command(group_ids[i], cmd->command, COMMAND_CHARCOMMAND ) ) + cmd->char_groups[i] = 1; + else + cmd->char_groups[i] = 0; + } + } + + dbi_destroy(iter); + + return; +} +void atcommand_db_clear(void) { + + if (atcommand_db != NULL) { + DBIterator *iter = db_iterator(atcommand_db); + AtCommandInfo* cmd; + + for (cmd = dbi_first(iter); dbi_exists(iter); cmd = dbi_next(iter)) { + aFree(cmd->at_groups); + aFree(cmd->char_groups); + } + + dbi_destroy(iter); + + db_destroy(atcommand_db); + } + if (atcommand_alias_db != NULL) + db_destroy(atcommand_alias_db); + + config_destroy(&atcommand_config); +} + +void atcommand_doload(void) { + atcommand_db_clear(); + atcommand_db = stridb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, ATCOMMAND_LENGTH); + atcommand_alias_db = stridb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, ATCOMMAND_LENGTH); + atcommand_basecommands(); //fills initial atcommand_db with known commands + atcommand_config_read(ATCOMMAND_CONF_FILENAME); +} + +void do_init_atcommand(void) { + atcommand_doload(); +} + +void do_final_atcommand(void) { + atcommand_db_clear(); +} diff --git a/src/map/atcommand.h b/src/map/atcommand.h new file mode 100644 index 000000000..8affa4c26 --- /dev/null +++ b/src/map/atcommand.h @@ -0,0 +1,51 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _ATCOMMAND_H_ +#define _ATCOMMAND_H_ + +struct map_session_data; + +//This is the distance at which @autoloot works, +//if the item drops farther from the player than this, +//it will not be autolooted. [Skotlex] +//Note: The range is unlimited unless this define is set. +//#define AUTOLOOT_DISTANCE AREA_SIZE + +extern char atcommand_symbol; +extern char charcommand_symbol; + +typedef enum { + COMMAND_ATCOMMAND = 1, + COMMAND_CHARCOMMAND = 2, +} AtCommandType; + +typedef int (*AtCommandFunc)(const int fd, struct map_session_data* sd, const char* command, const char* message); + +bool is_atcommand(const int fd, struct map_session_data* sd, const char* message, int type); + +void do_init_atcommand(void); +void do_final_atcommand(void); +void atcommand_db_load_groups(int* group_ids); + +bool atcommand_exists(const char* name); + +const char* msg_txt(int msg_number); +int msg_config_read(const char* cfgName); +void do_final_msg(void); + +extern int atcmd_binding_count; + +// @commands (script based) +struct atcmd_binding_data { + char command[50]; + char npc_event[50]; + int level; + int level2; +}; + +struct atcmd_binding_data** atcmd_binding; + +struct atcmd_binding_data* get_atcommandbind_byname(const char* name); + +#endif /* _ATCOMMAND_H_ */ diff --git a/src/map/battle.c b/src/map/battle.c new file mode 100644 index 000000000..7b6bf5869 --- /dev/null +++ b/src/map/battle.c @@ -0,0 +1,6132 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.h" +#include "../common/random.h" +#include "../common/socket.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "map.h" +#include "path.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "mob.h" +#include "itemdb.h" +#include "clif.h" +#include "pet.h" +#include "guild.h" +#include "party.h" +#include "battle.h" +#include "battleground.h" +#include "chrif.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +int attr_fix_table[4][ELE_MAX][ELE_MAX]; + +struct Battle_Config battle_config; +static struct eri *delay_damage_ers; //For battle delay damage structures. + +#define DAMAGE_RATE(a){damage = (int64)damage * (a)/100;} +#define DAMAGE_SUBRATE(a){damage -= (int64)damage * (a)/100;} +#define DAMAGE_ADDRATE(a){damage += (int64)damage * (a)/100;} +int battle_getcurrentskill(struct block_list *bl) { //Returns the current/last skill in use by this bl. + struct unit_data *ud; + + if( bl->type == BL_SKILL ) { + struct skill_unit * su = (struct skill_unit*)bl; + return su->group?su->group->skill_id:0; + } + + ud = unit_bl2ud(bl); + + return ud?ud->skill_id:0; +} + +/*========================================== + * Get random targetting enemy + *------------------------------------------*/ +static int battle_gettargeted_sub(struct block_list *bl, va_list ap) { + struct block_list **bl_list; + struct unit_data *ud; + int target_id; + int *c; + + bl_list = va_arg(ap, struct block_list **); + c = va_arg(ap, int *); + target_id = va_arg(ap, int); + + if (bl->id == target_id) + return 0; + + if (*c >= 24) + return 0; + + if ( !(ud = unit_bl2ud(bl)) ) + return 0; + + if (ud->target == target_id || ud->skilltarget == target_id) { + bl_list[(*c)++] = bl; + return 1; + } + + return 0; +} + +struct block_list* battle_gettargeted(struct block_list *target) { + struct block_list *bl_list[24]; + int c = 0; + nullpo_retr(NULL, target); + + memset(bl_list, 0, sizeof(bl_list)); + map_foreachinrange(battle_gettargeted_sub, target, AREA_SIZE, BL_CHAR, bl_list, &c, target->id); + if ( c == 0 ) + return NULL; + if( c > 24 ) + c = 24; + return bl_list[rnd()%c]; +} + + +//Returns the id of the current targetted character of the passed bl. [Skotlex] +int battle_gettarget(struct block_list* bl) { + + switch (bl->type) { + case BL_PC: return ((struct map_session_data*)bl)->ud.target; + case BL_MOB: return ((struct mob_data*)bl)->target_id; + case BL_PET: return ((struct pet_data*)bl)->target_id; + case BL_HOM: return ((struct homun_data*)bl)->ud.target; + case BL_MER: return ((struct mercenary_data*)bl)->ud.target; + case BL_ELEM: return ((struct elemental_data*)bl)->ud.target; + } + + return 0; +} + +static int battle_getenemy_sub(struct block_list *bl, va_list ap) { + struct block_list **bl_list; + struct block_list *target; + int *c; + + bl_list = va_arg(ap, struct block_list **); + c = va_arg(ap, int *); + target = va_arg(ap, struct block_list *); + + if (bl->id == target->id) + return 0; + + if (*c >= 24) + return 0; + + if (status_isdead(bl)) + return 0; + + if (battle_check_target(target, bl, BCT_ENEMY) > 0) { + bl_list[(*c)++] = bl; + return 1; + } + + return 0; +} + +// Picks a random enemy of the given type (BL_PC, BL_CHAR, etc) within the range given. [Skotlex] +struct block_list* battle_getenemy(struct block_list *target, int type, int range) { + struct block_list *bl_list[24]; + int c = 0; + + memset(bl_list, 0, sizeof(bl_list)); + map_foreachinrange(battle_getenemy_sub, target, range, type, bl_list, &c, target); + + if ( c == 0 ) + return NULL; + + if( c > 24 ) + c = 24; + + return bl_list[rnd()%c]; +} +static int battle_getenemyarea_sub(struct block_list *bl, va_list ap) { + struct block_list **bl_list, *src; + int *c, ignore_id; + + bl_list = va_arg(ap, struct block_list **); + c = va_arg(ap, int *); + src = va_arg(ap, struct block_list *); + ignore_id = va_arg(ap, int); + + if( bl->id == src->id || bl->id == ignore_id ) + return 0; // Ignores Caster and a possible pre-target + + if( *c >= 23 ) + return 0; + + if( status_isdead(bl) ) + return 0; + + if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) {// Is Enemy!... + bl_list[(*c)++] = bl; + return 1; + } + + return 0; +} + +// Pick a random enemy +struct block_list* battle_getenemyarea(struct block_list *src, int x, int y, int range, int type, int ignore_id) { + struct block_list *bl_list[24]; + int c = 0; + + memset(bl_list, 0, sizeof(bl_list)); + map_foreachinarea(battle_getenemyarea_sub, src->m, x - range, y - range, x + range, y + range, type, bl_list, &c, src, ignore_id); + + if( c == 0 ) + return NULL; + if( c >= 24 ) + c = 23; + + return bl_list[rnd()%c]; +} + +// Dammage delayed info +struct delay_damage { + int src_id; + int target_id; + int damage; + int delay; + unsigned short distance; + uint16 skill_lv; + uint16 skill_id; + enum damage_lv dmg_lv; + unsigned short attack_type; +}; + +int battle_delay_damage_sub(int tid, unsigned int tick, int id, intptr_t data) { + struct delay_damage *dat = (struct delay_damage *)data; + + if ( dat ) { + struct block_list* src; + struct block_list* target = map_id2bl(dat->target_id); + + if( !target || status_isdead(target) ) {/* nothing we can do */ + ers_free(delay_damage_ers, dat); + return 0; + } + + src = map_id2bl(dat->src_id); + + if( src && target->m == src->m && + (target->type != BL_PC || ((TBL_PC*)target)->invincible_timer == INVALID_TIMER) && + check_distance_bl(src, target, dat->distance) ) //Check to see if you haven't teleported. [Skotlex] + { + map_freeblock_lock(); + status_fix_damage(src, target, dat->damage, dat->delay); + if( dat->attack_type && !status_isdead(target) ) + skill_additional_effect(src,target,dat->skill_id,dat->skill_lv,dat->attack_type,dat->dmg_lv,tick); + if( dat->dmg_lv > ATK_BLOCK && dat->attack_type ) + skill_counter_additional_effect(src,target,dat->skill_id,dat->skill_lv,dat->attack_type,tick); + map_freeblock_unlock(); + } else if( !src && dat->skill_id == CR_REFLECTSHIELD ) { + /** + * it was monster reflected damage, and the monster died, we pass the damage to the character as expected + **/ + map_freeblock_lock(); + status_fix_damage(target, target, dat->damage, dat->delay); + map_freeblock_unlock(); + } + } + ers_free(delay_damage_ers, dat); + return 0; +} + +int battle_delay_damage (unsigned int tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int damage, enum damage_lv dmg_lv, int ddelay) +{ + struct delay_damage *dat; + struct status_change *sc; + nullpo_ret(src); + nullpo_ret(target); + + sc = status_get_sc(target); + + if( sc && sc->data[SC_DEVOTION] && damage > 0 && skill_id != PA_PRESSURE && skill_id != CR_REFLECTSHIELD ) + damage = 0; + + if ( !battle_config.delay_battle_damage || amotion <= 1 ) { + map_freeblock_lock(); + status_fix_damage(src, target, damage, ddelay); // We have to seperate here between reflect damage and others [icescope] + if( attack_type && !status_isdead(target) ) + skill_additional_effect(src, target, skill_id, skill_lv, attack_type, dmg_lv, gettick()); + if( dmg_lv > ATK_BLOCK && attack_type ) + skill_counter_additional_effect(src, target, skill_id, skill_lv, attack_type, gettick()); + map_freeblock_unlock(); + return 0; + } + dat = ers_alloc(delay_damage_ers, struct delay_damage); + dat->src_id = src->id; + dat->target_id = target->id; + dat->skill_id = skill_id; + dat->skill_lv = skill_lv; + dat->attack_type = attack_type; + dat->damage = damage; + dat->dmg_lv = dmg_lv; + dat->delay = ddelay; + dat->distance = distance_bl(src, target)+10; //Attack should connect regardless unless you teleported. + if (src->type != BL_PC && amotion > 1000) + amotion = 1000; //Aegis places a damage-delay cap of 1 sec to non player attacks. [Skotlex] + + add_timer(tick+amotion, battle_delay_damage_sub, 0, (intptr_t)dat); + + return 0; +} +int battle_attr_ratio(int atk_elem,int def_type, int def_lv) +{ + + if (atk_elem < 0 || atk_elem >= ELE_MAX) + return 100; + + if (def_type < 0 || def_type > ELE_MAX || def_lv < 1 || def_lv > 4) + return 100; + + return attr_fix_table[def_lv-1][atk_elem][def_type]; +} + +/*========================================== + * Does attribute fix modifiers. + * Added passing of the chars so that the status changes can affect it. [Skotlex] + * Note: Passing src/target == NULL is perfectly valid, it skips SC_ checks. + *------------------------------------------*/ +int battle_attr_fix(struct block_list *src, struct block_list *target, int damage,int atk_elem,int def_type, int def_lv) +{ + struct status_change *sc=NULL, *tsc=NULL; + int ratio; + + if (src) sc = status_get_sc(src); + if (target) tsc = status_get_sc(target); + + if (atk_elem < 0 || atk_elem >= ELE_MAX) + atk_elem = rnd()%ELE_MAX; + + if (def_type < 0 || def_type > ELE_MAX || + def_lv < 1 || def_lv > 4) { + ShowError("battle_attr_fix: unknown attr type: atk=%d def_type=%d def_lv=%d\n",atk_elem,def_type,def_lv); + return damage; + } + + ratio = attr_fix_table[def_lv-1][atk_elem][def_type]; + if (sc && sc->count) { + if(sc->data[SC_VOLCANO] && atk_elem == ELE_FIRE) + ratio += enchant_eff[sc->data[SC_VOLCANO]->val1-1]; + if(sc->data[SC_VIOLENTGALE] && atk_elem == ELE_WIND) + ratio += enchant_eff[sc->data[SC_VIOLENTGALE]->val1-1]; + if(sc->data[SC_DELUGE] && atk_elem == ELE_WATER) + ratio += enchant_eff[sc->data[SC_DELUGE]->val1-1]; + } + if( target && target->type == BL_SKILL ) { + if( atk_elem == ELE_FIRE && battle_getcurrentskill(target) == GN_WALLOFTHORN ) { + struct skill_unit *su = (struct skill_unit*)target; + struct skill_unit_group *sg; + struct block_list *src; + int x,y; + + if( !su || !su->alive || (sg = su->group) == NULL || !sg || sg->val3 == -1 || + (src = map_id2bl(sg->src_id)) == NULL || status_isdead(src) ) + return 0; + + if( sg->unit_id != UNT_FIREWALL ) { + x = sg->val3 >> 16; + y = sg->val3 & 0xffff; + skill_unitsetting(src,su->group->skill_id,su->group->skill_lv,x,y,1); + sg->val3 = -1; + sg->limit = DIFF_TICK(gettick(),sg->tick)+300; + } + } + } + if( tsc && tsc->count ) { //since an atk can only have one type let's optimise this a bit + switch(atk_elem){ + case ELE_FIRE: + if( tsc->data[SC_SPIDERWEB]) { + tsc->data[SC_SPIDERWEB]->val1 = 0; // free to move now + if( tsc->data[SC_SPIDERWEB]->val2-- > 0 ) + damage <<= 1; // double damage + if( tsc->data[SC_SPIDERWEB]->val2 == 0 ) + status_change_end(target, SC_SPIDERWEB, INVALID_TIMER); + } + if( tsc->data[SC_THORNSTRAP]) + status_change_end(target, SC_THORNSTRAP, INVALID_TIMER); + if( tsc->data[SC_FIRE_CLOAK_OPTION]) + DAMAGE_SUBRATE(tsc->data[SC_FIRE_CLOAK_OPTION]->val2) + if( tsc->data[SC_CRYSTALIZE] && target->type != BL_MOB) + status_change_end(target, SC_CRYSTALIZE, INVALID_TIMER); + if( tsc->data[SC_EARTH_INSIGNIA]) damage += damage/2; + if( tsc->data[SC_ASH]) damage += damage/2; //150% + break; + case ELE_HOLY: + if( tsc->data[SC_ORATIO]) ratio += tsc->data[SC_ORATIO]->val1 * 2; + break; + case ELE_POISON: + if( tsc->data[SC_VENOMIMPRESS]) ratio += tsc->data[SC_VENOMIMPRESS]->val2; + break; + case ELE_WIND: + if( tsc->data[SC_CRYSTALIZE] && target->type != BL_MOB) damage += damage/2; + if( tsc->data[SC_WATER_INSIGNIA]) damage += damage/2; + break; + case ELE_WATER: + if( tsc->data[SC_FIRE_INSIGNIA]) damage += damage/2; + break; + case ELE_EARTH: + if( tsc->data[SC_WIND_INSIGNIA]) damage += damage/2; + break; + } + } //end tsc check + if( src && src->type == BL_PC ){ + struct map_session_data *sd = BL_CAST(BL_PC, src); + int s; + + ARR_FIND(1, 6, s, sd->talisman[s] > 0); + + if( s < 5 && atk_elem == s ) + ratio += sd->talisman[s] * 2; // +2% custom value + } + if( target && target->type == BL_PC ) { + struct map_session_data *tsd = BL_CAST(BL_PC, target); + int t; + + ARR_FIND(1, 6, t, tsd->talisman[t] > 0); + + if( t < 5 && atk_elem == t ) + DAMAGE_SUBRATE(tsd->talisman[t] * 3) // -3% custom value + } + return (int64)damage*ratio/100; +} + +/*========================================== + * Calculates card bonuses damage adjustments. + *------------------------------------------*/ +int battle_calc_cardfix(int attack_type, struct block_list *src, struct block_list *target, int nk, int s_ele, int s_ele_, int damage, int left, int flag){ + struct map_session_data *sd, *tsd; + short cardfix = 1000, t_class, s_class, s_race2, t_race2; + struct status_data *sstatus, *tstatus; + int i; + + if( !damage ) + return 0; + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + t_class = status_get_class(target); + s_class = status_get_class(src); + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(target); + s_race2 = status_get_race2(src); + +#define bccDAMAGE_RATE(a){ damage = (int64)damage * (a)/1000;} + switch(attack_type){ + case BF_MAGIC: + if ( sd && !(nk&NK_NO_CARDFIX_ATK) ) { + cardfix=cardfix*(100+sd->magic_addrace[tstatus->race])/100; + if (!(nk&NK_NO_ELEFIX)) + cardfix=cardfix*(100+sd->magic_addele[tstatus->def_ele])/100; + cardfix=cardfix*(100+sd->magic_addsize[tstatus->size])/100; + cardfix=cardfix*(100+sd->magic_addrace[is_boss(target)?RC_BOSS:RC_NONBOSS])/100; + cardfix=cardfix*(100+sd->magic_atk_ele[s_ele])/100; + for(i=0; i< ARRAYLENGTH(sd->add_mdmg) && sd->add_mdmg[i].rate;i++) { + if(sd->add_mdmg[i].class_ == t_class) { + cardfix=cardfix*(100+sd->add_mdmg[i].rate)/100; + break; + } + } + if (cardfix != 1000) + bccDAMAGE_RATE(cardfix) + } + + if( tsd && !(nk&NK_NO_CARDFIX_DEF) ) + { // Target cards. + if (!(nk&NK_NO_ELEFIX)) + { + int ele_fix = tsd->subele[s_ele]; + for (i = 0; ARRAYLENGTH(tsd->subele2) > i && tsd->subele2[i].rate != 0; i++) + { + if(tsd->subele2[i].ele != s_ele) continue; + if(!(tsd->subele2[i].flag&flag&BF_WEAPONMASK && + tsd->subele2[i].flag&flag&BF_RANGEMASK && + tsd->subele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += tsd->subele2[i].rate; + } + cardfix=cardfix*(100-ele_fix)/100; + } + cardfix=cardfix*(100-tsd->subsize[sstatus->size])/100; + cardfix=cardfix*(100-tsd->subrace2[s_race2])/100; + cardfix=cardfix*(100-tsd->subrace[sstatus->race])/100; + cardfix=cardfix*(100-tsd->subrace[is_boss(src)?RC_BOSS:RC_NONBOSS])/100; + if( sstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100-tsd->subrace[RC_NONDEMIHUMAN])/100; + + for(i=0; i < ARRAYLENGTH(tsd->add_mdef) && tsd->add_mdef[i].rate;i++) { + if(tsd->add_mdef[i].class_ == s_class) { + cardfix=cardfix*(100-tsd->add_mdef[i].rate)/100; + break; + } + } + //It was discovered that ranged defense also counts vs magic! [Skotlex] + if ( flag&BF_SHORT ) + cardfix = cardfix * ( 100 - tsd->bonus.near_attack_def_rate ) / 100; + else + cardfix = cardfix * ( 100 - tsd->bonus.long_attack_def_rate ) / 100; + + cardfix = cardfix * ( 100 - tsd->bonus.magic_def_rate ) / 100; + + if( tsd->sc.data[SC_MDEF_RATE] ) + cardfix = cardfix * ( 100 - tsd->sc.data[SC_MDEF_RATE]->val1 ) / 100; + + if (cardfix != 1000) + bccDAMAGE_RATE(cardfix) + } + break; + case BF_WEAPON: + t_race2 = status_get_race2(target); + if( sd && !(nk&NK_NO_CARDFIX_ATK) && (left&2) ) + { + short cardfix_ = 1000; + if(sd->state.arrow_atk) + { + cardfix=cardfix*(100+sd->right_weapon.addrace[tstatus->race]+sd->arrow_addrace[tstatus->race])/100; + if (!(nk&NK_NO_ELEFIX)) + { + int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->arrow_addele[tstatus->def_ele]; + for (i = 0; ARRAYLENGTH(sd->right_weapon.addele2) > i && sd->right_weapon.addele2[i].rate != 0; i++) { + if (sd->right_weapon.addele2[i].ele != tstatus->def_ele) continue; + if(!(sd->right_weapon.addele2[i].flag&flag&BF_WEAPONMASK && + sd->right_weapon.addele2[i].flag&flag&BF_RANGEMASK && + sd->right_weapon.addele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += sd->right_weapon.addele2[i].rate; + } + cardfix=cardfix*(100+ele_fix)/100; + } + cardfix=cardfix*(100+sd->right_weapon.addsize[tstatus->size]+sd->arrow_addsize[tstatus->size])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace2[t_race2])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace[is_boss(target)?RC_BOSS:RC_NONBOSS]+sd->arrow_addrace[is_boss(target)?RC_BOSS:RC_NONBOSS])/100; + if( tstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100+sd->right_weapon.addrace[RC_NONDEMIHUMAN]+sd->arrow_addrace[RC_NONDEMIHUMAN])/100; + } + else + { // Melee attack + if( !battle_config.left_cardfix_to_right ) + { + cardfix=cardfix*(100+sd->right_weapon.addrace[tstatus->race])/100; + if (!(nk&NK_NO_ELEFIX)) { + int ele_fix = sd->right_weapon.addele[tstatus->def_ele]; + for (i = 0; ARRAYLENGTH(sd->right_weapon.addele2) > i && sd->right_weapon.addele2[i].rate != 0; i++) { + if (sd->right_weapon.addele2[i].ele != tstatus->def_ele) continue; + if(!(sd->right_weapon.addele2[i].flag&flag&BF_WEAPONMASK && + sd->right_weapon.addele2[i].flag&flag&BF_RANGEMASK && + sd->right_weapon.addele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += sd->right_weapon.addele2[i].rate; + } + cardfix=cardfix*(100+ele_fix)/100; + } + cardfix=cardfix*(100+sd->right_weapon.addsize[tstatus->size])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace2[t_race2])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace[is_boss(target)?RC_BOSS:RC_NONBOSS])/100; + if( tstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100+sd->right_weapon.addrace[RC_NONDEMIHUMAN])/100; + + if( left&1 ) + { + cardfix_=cardfix_*(100+sd->left_weapon.addrace[tstatus->race])/100; + if (!(nk&NK_NO_ELEFIX)) { + int ele_fix_lh = sd->left_weapon.addele[tstatus->def_ele]; + for (i = 0; ARRAYLENGTH(sd->left_weapon.addele2) > i && sd->left_weapon.addele2[i].rate != 0; i++) { + if (sd->left_weapon.addele2[i].ele != tstatus->def_ele) continue; + if(!(sd->left_weapon.addele2[i].flag&flag&BF_WEAPONMASK && + sd->left_weapon.addele2[i].flag&flag&BF_RANGEMASK && + sd->left_weapon.addele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix_lh += sd->left_weapon.addele2[i].rate; + } + cardfix=cardfix*(100+ele_fix_lh)/100; + } + cardfix_=cardfix_*(100+sd->left_weapon.addsize[tstatus->size])/100; + cardfix_=cardfix_*(100+sd->left_weapon.addrace2[t_race2])/100; + cardfix_=cardfix_*(100+sd->left_weapon.addrace[is_boss(target)?RC_BOSS:RC_NONBOSS])/100; + if( tstatus->race != RC_DEMIHUMAN ) + cardfix_=cardfix_*(100+sd->left_weapon.addrace[RC_NONDEMIHUMAN])/100; + } + } + else + { + int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->left_weapon.addele[tstatus->def_ele]; + for (i = 0; ARRAYLENGTH(sd->right_weapon.addele2) > i && sd->right_weapon.addele2[i].rate != 0; i++) { + if (sd->right_weapon.addele2[i].ele != tstatus->def_ele) continue; + if(!(sd->right_weapon.addele2[i].flag&flag&BF_WEAPONMASK && + sd->right_weapon.addele2[i].flag&flag&BF_RANGEMASK && + sd->right_weapon.addele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += sd->right_weapon.addele2[i].rate; + } + for (i = 0; ARRAYLENGTH(sd->left_weapon.addele2) > i && sd->left_weapon.addele2[i].rate != 0; i++) { + if (sd->left_weapon.addele2[i].ele != tstatus->def_ele) continue; + if(!(sd->left_weapon.addele2[i].flag&flag&BF_WEAPONMASK && + sd->left_weapon.addele2[i].flag&flag&BF_RANGEMASK && + sd->left_weapon.addele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += sd->left_weapon.addele2[i].rate; + } + + cardfix=cardfix*(100+sd->right_weapon.addrace[tstatus->race]+sd->left_weapon.addrace[tstatus->race])/100; + cardfix=cardfix*(100+ele_fix)/100; + cardfix=cardfix*(100+sd->right_weapon.addsize[tstatus->size]+sd->left_weapon.addsize[tstatus->size])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace2[t_race2]+sd->left_weapon.addrace2[t_race2])/100; + cardfix=cardfix*(100+sd->right_weapon.addrace[is_boss(target)?RC_BOSS:RC_NONBOSS]+sd->left_weapon.addrace[is_boss(target)?RC_BOSS:RC_NONBOSS])/100; + if( tstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100+sd->right_weapon.addrace[RC_NONDEMIHUMAN]+sd->left_weapon.addrace[RC_NONDEMIHUMAN])/100; + } + } + for( i = 0; i < ARRAYLENGTH(sd->right_weapon.add_dmg) && sd->right_weapon.add_dmg[i].rate; i++ ) + { + if( sd->right_weapon.add_dmg[i].class_ == t_class ) + { + cardfix=cardfix*(100+sd->right_weapon.add_dmg[i].rate)/100; + break; + } + } + + if( left&1 ) + { + for( i = 0; i < ARRAYLENGTH(sd->left_weapon.add_dmg) && sd->left_weapon.add_dmg[i].rate; i++ ) + { + if( sd->left_weapon.add_dmg[i].class_ == t_class ) + { + cardfix_=cardfix_*(100+sd->left_weapon.add_dmg[i].rate)/100; + break; + } + } + } + + if( flag&BF_LONG ) + cardfix = cardfix * ( 100 + sd->bonus.long_attack_atk_rate ) / 100; +#ifdef RENEWAL_EDP + if( sd->sc.data[SC_EDP] ){ + cardfix = cardfix * (100 + sd->sc.data[SC_EDP]->val1 * 60 ) / 100; + cardfix_ = cardfix_ * (100 + sd->sc.data[SC_EDP]->val1 * 60 ) / 100; + } +#endif + if( (left&1) && cardfix_ != 1000 ) + bccDAMAGE_RATE(cardfix_) + else if( cardfix != 1000 ) + bccDAMAGE_RATE(cardfix) + + }else if( tsd && !(nk&NK_NO_CARDFIX_DEF) ){ + if( !(nk&NK_NO_ELEFIX) ) + { + int ele_fix = tsd->subele[s_ele]; + for (i = 0; ARRAYLENGTH(tsd->subele2) > i && tsd->subele2[i].rate != 0; i++) + { + if(tsd->subele2[i].ele != s_ele) continue; + if(!(tsd->subele2[i].flag&flag&BF_WEAPONMASK && + tsd->subele2[i].flag&flag&BF_RANGEMASK && + tsd->subele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += tsd->subele2[i].rate; + } + cardfix=cardfix*(100-ele_fix)/100; + if( left&1 && s_ele_ != s_ele ) + { + int ele_fix_lh = tsd->subele[s_ele_]; + for (i = 0; ARRAYLENGTH(tsd->subele2) > i && tsd->subele2[i].rate != 0; i++) + { + if(tsd->subele2[i].ele != s_ele_) continue; + if(!(tsd->subele2[i].flag&flag&BF_WEAPONMASK && + tsd->subele2[i].flag&flag&BF_RANGEMASK && + tsd->subele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix_lh += tsd->subele2[i].rate; + } + cardfix=cardfix*(100-ele_fix_lh)/100; + } + } + cardfix=cardfix*(100-tsd->subsize[sstatus->size])/100; + cardfix=cardfix*(100-tsd->subrace2[s_race2])/100; + cardfix=cardfix*(100-tsd->subrace[sstatus->race])/100; + cardfix=cardfix*(100-tsd->subrace[is_boss(src)?RC_BOSS:RC_NONBOSS])/100; + if( sstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100-tsd->subrace[RC_NONDEMIHUMAN])/100; + + for( i = 0; i < ARRAYLENGTH(tsd->add_def) && tsd->add_def[i].rate;i++ ) { + if( tsd->add_def[i].class_ == s_class ) { + cardfix=cardfix*(100-tsd->add_def[i].rate)/100; + break; + } + } + + if( flag&BF_SHORT ) + cardfix = cardfix * ( 100 - tsd->bonus.near_attack_def_rate ) / 100; + else // BF_LONG (there's no other choice) + cardfix = cardfix * ( 100 - tsd->bonus.long_attack_def_rate ) / 100; + + if( tsd->sc.data[SC_DEF_RATE] ) + cardfix = cardfix * ( 100 - tsd->sc.data[SC_DEF_RATE]->val1 ) / 100; + + if( cardfix != 1000 ) + bccDAMAGE_RATE(cardfix) + } + break; + case BF_MISC: + if( tsd && !(nk&NK_NO_CARDFIX_DEF) ){ + // misc damage reduction from equipment + if (!(nk&NK_NO_ELEFIX)) + { + int ele_fix = tsd->subele[s_ele]; + for (i = 0; ARRAYLENGTH(tsd->subele2) > i && tsd->subele2[i].rate != 0; i++) + { + if(tsd->subele2[i].ele != s_ele) continue; + if(!(tsd->subele2[i].flag&flag&BF_WEAPONMASK && + tsd->subele2[i].flag&flag&BF_RANGEMASK && + tsd->subele2[i].flag&flag&BF_SKILLMASK)) + continue; + ele_fix += tsd->subele2[i].rate; + } + cardfix=cardfix*(100-ele_fix)/100; + } + cardfix=cardfix*(100-tsd->subsize[sstatus->size])/100; + cardfix=cardfix*(100-tsd->subrace2[s_race2])/100; + cardfix=cardfix*(100-tsd->subrace[sstatus->race])/100; + cardfix=cardfix*(100-tsd->subrace[is_boss(src)?RC_BOSS:RC_NONBOSS])/100; + if( sstatus->race != RC_DEMIHUMAN ) + cardfix=cardfix*(100-tsd->subrace[RC_NONDEMIHUMAN])/100; + + cardfix = cardfix * ( 100 - tsd->bonus.misc_def_rate ) / 100; + if( flag&BF_SHORT ) + cardfix = cardfix * ( 100 - tsd->bonus.near_attack_def_rate ) / 100; + else // BF_LONG (there's no other choice) + cardfix = cardfix * ( 100 - tsd->bonus.long_attack_def_rate ) / 100; + + if (cardfix != 10000) + bccDAMAGE_RATE(cardfix) + } + break; + } + + return damage; +} + +/*========================================== + * Check dammage trough status. + * ATK may be MISS, BLOCKED FAIL, reduc, increase, end status... + * After this we apply bg/gvg reduction + *------------------------------------------*/ +int battle_calc_damage(struct block_list *src,struct block_list *bl,struct Damage *d,int damage,uint16 skill_id,uint16 skill_lv) +{ + struct map_session_data *sd = NULL; + struct status_change *sc; + struct status_change_entry *sce; + int div_ = d->div_, flag = d->flag; + + nullpo_ret(bl); + + if( !damage ) + return 0; + if( battle_config.ksprotection && mob_ksprotected(src, bl) ) + return 0; + + if (bl->type == BL_PC) { + sd=(struct map_session_data *)bl; + //Special no damage states + if(flag&BF_WEAPON && sd->special_state.no_weapon_damage) + DAMAGE_SUBRATE(sd->special_state.no_weapon_damage) + + if(flag&BF_MAGIC && sd->special_state.no_magic_damage) + DAMAGE_SUBRATE(sd->special_state.no_magic_damage) + + if(flag&BF_MISC && sd->special_state.no_misc_damage) + DAMAGE_SUBRATE(sd->special_state.no_misc_damage) + + if(!damage) return 0; + } + + sc = status_get_sc(bl); + + if( sc && sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + return 1; + + if (skill_id == PA_PRESSURE) + return damage; //This skill bypass everything else. + + if( sc && sc->count ) + { + //First, sc_*'s that reduce damage to 0. + if( sc->data[SC_BASILICA] && !(status_get_mode(src)&MD_BOSS) ) + { + d->dmg_lv = ATK_BLOCK; + return 0; + } + if( sc->data[SC_WHITEIMPRISON] && skill_id != HW_GRAVITATION ) { // Gravitation and Pressure do damage without removing the effect + if( skill_id == MG_NAPALMBEAT || + skill_id == MG_SOULSTRIKE || + skill_id == WL_SOULEXPANSION || + (skill_id && skill_get_ele(skill_id, skill_lv) == ELE_GHOST) || + (!skill_id && (status_get_status_data(src))->rhw.ele == ELE_GHOST) + ){ + if( skill_id == WL_SOULEXPANSION ) + damage <<= 1; // If used against a player in White Imprison, the skill deals double damage. + status_change_end(bl,SC_WHITEIMPRISON,INVALID_TIMER); // Those skills do damage and removes effect + }else{ + d->dmg_lv = ATK_BLOCK; + return 0; + } + } + + if(sc->data[SC_ZEPHYR] && + flag&(BF_LONG|BF_SHORT)){ + d->dmg_lv = ATK_BLOCK; + return 0; + } + + if( sc->data[SC_SAFETYWALL] && (flag&(BF_SHORT|BF_MAGIC))==BF_SHORT ) + { + struct skill_unit_group* group = skill_id2group(sc->data[SC_SAFETYWALL]->val3); + uint16 skill_id = sc->data[SC_SAFETYWALL]->val2; + if (group) { + if(skill_id == MH_STEINWAND){ + if (--group->val2<=0) + skill_delunitgroup(group); + d->dmg_lv = ATK_BLOCK; + return 0; + } + /** + * in RE, SW possesses a lifetime equal to 3 times the caster's health + **/ + #ifdef RENEWAL + if ( ( group->val2 - damage) > 0 ) { + group->val2 -= damage; + d->dmg_lv = ATK_BLOCK; + return 0; + } else + damage -= group->val2; + skill_delunitgroup(group); + #else + if (--group->val2<=0) + skill_delunitgroup(group); + d->dmg_lv = ATK_BLOCK; + return 0; + #endif + } + status_change_end(bl, SC_SAFETYWALL, INVALID_TIMER); + } + + if( ( sc->data[SC_PNEUMA] && (flag&(BF_MAGIC|BF_LONG)) == BF_LONG ) || sc->data[SC__MANHOLE] ) { + d->dmg_lv = ATK_BLOCK; + return 0; + } + if( sc->data[SC_WEAPONBLOCKING] && flag&(BF_SHORT|BF_WEAPON) && rnd()%100 < sc->data[SC_WEAPONBLOCKING]->val2 ) + { + clif_skill_nodamage(bl,src,GC_WEAPONBLOCKING,1,1); + d->dmg_lv = ATK_BLOCK; + sc_start2(bl,SC_COMBO,100,GC_WEAPONBLOCKING,src->id,2000); + return 0; + } + if( (sce=sc->data[SC_AUTOGUARD]) && flag&BF_WEAPON && !(skill_get_nk(skill_id)&NK_NO_CARDFIX_ATK) && rnd()%100 < sce->val2 ) + { + int delay; + clif_skill_nodamage(bl,bl,CR_AUTOGUARD,sce->val1,1); + // different delay depending on skill level [celest] + if (sce->val1 <= 5) + delay = 300; + else if (sce->val1 > 5 && sce->val1 <= 9) + delay = 200; + else + delay = 100; + unit_set_walkdelay(bl, gettick(), delay, 1); + + if(sc->data[SC_SHRINK] && rnd()%100<5*sce->val1) + skill_blown(bl,src,skill_get_blewcount(CR_SHRINK,1),-1,0); + return 0; + } + + if( (sce = sc->data[SC_MILLENNIUMSHIELD]) && sce->val2 > 0 && damage > 0 ) { + clif_skill_nodamage(bl, bl, RK_MILLENNIUMSHIELD, 1, 1); + sce->val3 -= damage; // absorb damage + d->dmg_lv = ATK_BLOCK; + sc_start(bl,SC_STUN,15,0,skill_get_time2(RK_MILLENNIUMSHIELD,sce->val1)); // There is a chance to be stuned when one shield is broken. + if( sce->val3 <= 0 ) { // Shield Down + sce->val2--; + if( sce->val2 > 0 ) { + if( sd ) + clif_millenniumshield(sd,sce->val2); + sce->val3 = 1000; // Next Shield + } else + status_change_end(bl,SC_MILLENNIUMSHIELD,INVALID_TIMER); // All shields down + } + return 0; + } + + + if( (sce=sc->data[SC_PARRYING]) && flag&BF_WEAPON && skill_id != WS_CARTTERMINATION && rnd()%100 < sce->val2 ) + { // attack blocked by Parrying + clif_skill_nodamage(bl, bl, LK_PARRYING, sce->val1,1); + return 0; + } + + if(sc->data[SC_DODGE] && ( !sc->opt1 || sc->opt1 == OPT1_BURNING ) && + (flag&BF_LONG || sc->data[SC_SPURT]) + && rnd()%100 < 20) { + if (sd && pc_issit(sd)) pc_setstand(sd); //Stand it to dodge. + clif_skill_nodamage(bl,bl,TK_DODGE,1,1); + if (!sc->data[SC_COMBO]) + sc_start4(bl, SC_COMBO, 100, TK_JUMPKICK, src->id, 1, 0, 2000); + return 0; + } + + if(sc->data[SC_HERMODE] && flag&BF_MAGIC) + return 0; + + if(sc->data[SC_TATAMIGAESHI] && (flag&(BF_MAGIC|BF_LONG)) == BF_LONG) + return 0; + + if( sc->data[SC_NEUTRALBARRIER] && (flag&(BF_MAGIC|BF_LONG)) == (BF_MAGIC|BF_LONG) ) { + d->dmg_lv = ATK_MISS; + return 0; + } + + if((sce=sc->data[SC_KAUPE]) && rnd()%100 < sce->val2) + { //Kaupe blocks damage (skill or otherwise) from players, mobs, homuns, mercenaries. + clif_specialeffect(bl, 462, AREA); + //Shouldn't end until Breaker's non-weapon part connects. + if (skill_id != ASC_BREAKER || !(flag&BF_WEAPON)) + if (--(sce->val3) <= 0) //We make it work like Safety Wall, even though it only blocks 1 time. + status_change_end(bl, SC_KAUPE, INVALID_TIMER); + return 0; + } + + if( flag&BF_MAGIC && (sce=sc->data[SC_PRESTIGE]) && rnd()%100 < sce->val2) { + clif_specialeffect(bl, 462, AREA); // Still need confirm it. + return 0; + } + + if (((sce=sc->data[SC_UTSUSEMI]) || sc->data[SC_BUNSINJYUTSU]) + && flag&BF_WEAPON && !(skill_get_nk(skill_id)&NK_NO_CARDFIX_ATK)) { + + skill_additional_effect (src, bl, skill_id, skill_lv, flag, ATK_BLOCK, gettick() ); + if( !status_isdead(src) ) + skill_counter_additional_effect( src, bl, skill_id, skill_lv, flag, gettick() ); + if (sce) { + clif_specialeffect(bl, 462, AREA); + skill_blown(src,bl,sce->val3,-1,0); + } + //Both need to be consumed if they are active. + if (sce && --(sce->val2) <= 0) + status_change_end(bl, SC_UTSUSEMI, INVALID_TIMER); + if ((sce=sc->data[SC_BUNSINJYUTSU]) && --(sce->val2) <= 0) + status_change_end(bl, SC_BUNSINJYUTSU, INVALID_TIMER); + + return 0; + } + + //Now damage increasing effects + if( sc->data[SC_AETERNA] && skill_id != PF_SOULBURN ) + { + if( src->type != BL_MER || skill_id == 0 ) + damage <<= 1; // Lex Aeterna only doubles damage of regular attacks from mercenaries + + if( skill_id != ASC_BREAKER || !(flag&BF_WEAPON) ) + status_change_end(bl, SC_AETERNA, INVALID_TIMER); //Shouldn't end until Breaker's non-weapon part connects. + } + +#ifdef RENEWAL + if( sc->data[SC_RAID] ) + { + DAMAGE_ADDRATE(20) + + if (--sc->data[SC_RAID]->val1 == 0) + status_change_end(bl, SC_RAID, INVALID_TIMER); + } +#endif + + if( damage ) { + struct map_session_data *tsd = BL_CAST(BL_PC, src); + if( sc->data[SC_DEEPSLEEP] ) { + damage += damage / 2; // 1.5 times more damage while in Deep Sleep. + status_change_end(bl,SC_DEEPSLEEP,INVALID_TIMER); + } + if( tsd && sd && sc->data[SC_CRYSTALIZE] && flag&BF_WEAPON ){ + switch(tsd->status.weapon){ + case W_MACE: + case W_2HMACE: + case W_1HAXE: + case W_2HAXE: + DAMAGE_RATE(150) + break; + case W_MUSICAL: + case W_WHIP: + if(!sd->state.arrow_atk) + break; + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + case W_DAGGER: + case W_1HSWORD: + case W_2HSWORD: + DAMAGE_RATE(50) + break; + } + } + if( sc->data[SC_VOICEOFSIREN] ) + status_change_end(bl,SC_VOICEOFSIREN,INVALID_TIMER); + } + + + //Finally damage reductions.... + // Assumptio doubles the def & mdef on RE mode, otherwise gives a reduction on the final damage. [Igniz] +#ifndef RENEWAL + if( sc->data[SC_ASSUMPTIO] ) { + if( map_flag_vs(bl->m) ) + damage = (int64)damage*2/3; //Receive 66% damage + else + damage >>= 1; //Receive 50% damage + } +#endif + + if(sc->data[SC_DEFENDER] && + (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + DAMAGE_RATE(100-sc->data[SC_DEFENDER]->val2) + + if(sc->data[SC_ADJUSTMENT] && + (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + DAMAGE_SUBRATE(20) + + if(sc->data[SC_FOGWALL] && skill_id != RK_DRAGONBREATH) { + if(flag&BF_SKILL) //25% reduction + DAMAGE_SUBRATE(25) + else if ((flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + damage >>= 2; //75% reduction + } + + // Compressed code, fixed by map.h [Epoque] + if (src->type == BL_MOB) { + int i; + if (sc->data[SC_MANU_DEF]) + for (i=0;ARRAYLENGTH(mob_manuk)>i;i++) + if (mob_manuk[i]==((TBL_MOB*)src)->class_) { + DAMAGE_SUBRATE(sc->data[SC_MANU_DEF]->val1) + break; + } + if (sc->data[SC_SPL_DEF]) + for (i=0;ARRAYLENGTH(mob_splendide)>i;i++) + if (mob_splendide[i]==((TBL_MOB*)src)->class_) { + DAMAGE_SUBRATE(sc->data[SC_SPL_DEF]->val1) + break; + } + } + + if((sce=sc->data[SC_ARMOR]) && //NPC_DEFENDER + sce->val3&flag && sce->val4&flag) + DAMAGE_SUBRATE(sc->data[SC_ARMOR]->val2) + +#ifdef RENEWAL + if(sc->data[SC_ENERGYCOAT] && (flag&BF_WEAPON || flag&BF_MAGIC) && skill_id != WS_CARTTERMINATION) +#else + if(sc->data[SC_ENERGYCOAT] && flag&BF_WEAPON && skill_id != WS_CARTTERMINATION) +#endif + { + struct status_data *status = status_get_status_data(bl); + int per = 100*status->sp / status->max_sp -1; //100% should be counted as the 80~99% interval + per /=20; //Uses 20% SP intervals. + //SP Cost: 1% + 0.5% per every 20% SP + if (!status_charge(bl, 0, (10+5*per)*status->max_sp/1000)) + status_change_end(bl, SC_ENERGYCOAT, INVALID_TIMER); + //Reduction: 6% + 6% every 20% + DAMAGE_SUBRATE(6 * (1+per)) + } + if(sc->data[SC_GRANITIC_ARMOR]){ + DAMAGE_SUBRATE(sc->data[SC_GRANITIC_ARMOR]->val2) + } + if(sc->data[SC_PAIN_KILLER]){ + DAMAGE_SUBRATE(sc->data[SC_PAIN_KILLER]->val3) + } + if((sce=sc->data[SC_MAGMA_FLOW]) && (rnd()%100 <= sce->val2) ){ + skill_castend_damage_id(bl,src,MH_MAGMA_FLOW,sce->val1,gettick(),0); + } + +/** + * In renewal steel body reduces all incoming damage by 1/10 + **/ +#ifdef RENEWAL + if( sc->data[SC_STEELBODY] ) { + damage = damage > 10 ? damage / 10 : 1; + } +#endif + + //Finally added to remove the status of immobile when aimedbolt is used. [Jobbie] + if( skill_id == RA_AIMEDBOLT && (sc->data[SC_BITE] || sc->data[SC_ANKLE] || sc->data[SC_ELECTRICSHOCKER]) ) + { + status_change_end(bl, SC_BITE, INVALID_TIMER); + status_change_end(bl, SC_ANKLE, INVALID_TIMER); + status_change_end(bl, SC_ELECTRICSHOCKER, INVALID_TIMER); + } + + //Finally Kyrie because it may, or not, reduce damage to 0. + if((sce = sc->data[SC_KYRIE]) && damage > 0){ + sce->val2-=damage; + if(flag&BF_WEAPON || skill_id == TF_THROWSTONE){ + if(sce->val2>=0) + damage=0; + else + damage=-sce->val2; + } + if((--sce->val3)<=0 || (sce->val2<=0) || skill_id == AL_HOLYLIGHT) + status_change_end(bl, SC_KYRIE, INVALID_TIMER); + } + + if( sc->data[SC_MEIKYOUSISUI] && rand()%100 < 40 ) // custom value + damage = 0; + + + if (!damage) return 0; + + if( (sce = sc->data[SC_LIGHTNINGWALK]) && flag&BF_LONG && rnd()%100 < sce->val1 ) { + int dx[8]={0,-1,-1,-1,0,1,1,1}; + int dy[8]={1,1,0,-1,-1,-1,0,1}; + uint8 dir = map_calc_dir(bl, src->x, src->y); + if( unit_movepos(bl, src->x-dx[dir], src->y-dy[dir], 1, 1) ) { + clif_slide(bl,src->x-dx[dir],src->y-dy[dir]); + unit_setdir(bl, dir); + } + d->dmg_lv = ATK_DEF; + status_change_end(bl, SC_LIGHTNINGWALK, INVALID_TIMER); + return 0; + } + + //Probably not the most correct place, but it'll do here + //(since battle_drain is strictly for players currently) + if ((sce=sc->data[SC_BLOODLUST]) && flag&BF_WEAPON && damage > 0 && + rnd()%100 < sce->val3) + status_heal(src, (int64)damage*sce->val4/100, 0, 3); + + if( sd && (sce = sc->data[SC_FORCEOFVANGUARD]) && flag&BF_WEAPON && rnd()%100 < sce->val2 ) + pc_addspiritball(sd,skill_get_time(LG_FORCEOFVANGUARD,sce->val1),sce->val3); + if (sc->data[SC_STYLE_CHANGE] && rnd()%2) { + TBL_HOM *hd = BL_CAST(BL_HOM,bl); + if (hd) hom_addspiritball(hd, 10); //add a sphere + } + + if( sc->data[SC__DEADLYINFECT] && damage > 0 && rnd()%100 < 65 + 5 * sc->data[SC__DEADLYINFECT]->val1 ) + status_change_spread(bl, src); // Deadly infect attacked side + + if( sc && sc->data[SC__SHADOWFORM] ) { + struct block_list *s_bl = map_id2bl(sc->data[SC__SHADOWFORM]->val2); + if( !s_bl ) { // If the shadow form target is not present remove the sc. + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + } else if( status_isdead(s_bl) || !battle_check_target(src,s_bl,BCT_ENEMY)) { // If the shadow form target is dead or not your enemy remove the sc in both. + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + if( s_bl->type == BL_PC ) + ((TBL_PC*)s_bl)->shadowform_id = 0; + } else { + if( (--sc->data[SC__SHADOWFORM]->val3) < 0 ) { // If you have exceded max hits supported, remove the sc in both. + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + if( s_bl->type == BL_PC ) + ((TBL_PC*)s_bl)->shadowform_id = 0; + } else { + status_damage(bl, s_bl, damage, 0, clif_damage(s_bl, s_bl, gettick(), 500, 500, damage, -1, 0, 0), 0); + return ATK_NONE; + } + } + } + + } + + //SC effects from caster side. + sc = status_get_sc(src); + + if (sc && sc->count) + { + if( sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + DAMAGE_ADDRATE(75) + // [Epoque] + if (bl->type == BL_MOB) + { + int i; + + if ( ((sce=sc->data[SC_MANU_ATK]) && (flag&BF_WEAPON)) || + ((sce=sc->data[SC_MANU_MATK]) && (flag&BF_MAGIC)) + ) + for (i=0;ARRAYLENGTH(mob_manuk)>i;i++) + if (((TBL_MOB*)bl)->class_==mob_manuk[i]) { + DAMAGE_ADDRATE(sce->val1) + break; + } + if ( ((sce=sc->data[SC_SPL_ATK]) && (flag&BF_WEAPON)) || + ((sce=sc->data[SC_SPL_MATK]) && (flag&BF_MAGIC)) + ) + for (i=0;ARRAYLENGTH(mob_splendide)>i;i++) + if (((TBL_MOB*)bl)->class_==mob_splendide[i]) { + DAMAGE_ADDRATE(sce->val1) + break; + } + } + if( sc->data[SC_POISONINGWEAPON] && skill_id != GC_VENOMPRESSURE && (flag&BF_WEAPON) && damage > 0 && rnd()%100 < sc->data[SC_POISONINGWEAPON]->val3 ) + sc_start(bl,sc->data[SC_POISONINGWEAPON]->val2,100,sc->data[SC_POISONINGWEAPON]->val1,skill_get_time2(GC_POISONINGWEAPON, 1)); + if( sc->data[SC__DEADLYINFECT] && damage > 0 && rnd()%100 < 65 + 5 * sc->data[SC__DEADLYINFECT]->val1 ) + status_change_spread(src, bl); + if (sc->data[SC_STYLE_CHANGE] && rnd()%2) { + TBL_HOM *hd = BL_CAST(BL_HOM,bl); + if (hd) hom_addspiritball(hd, 10); + } + } + + if (battle_config.pk_mode && sd && bl->type == BL_PC && damage && map[bl->m].flag.pvp) + { + if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex] + if (flag&BF_WEAPON) + DAMAGE_RATE(battle_config.pk_weapon_damage_rate) + if (flag&BF_MAGIC) + DAMAGE_RATE(battle_config.pk_magic_damage_rate) + if (flag&BF_MISC) + DAMAGE_RATE(battle_config.pk_misc_damage_rate) + } else { //Normal attacks get reductions based on range. + if (flag & BF_SHORT) + DAMAGE_RATE(battle_config.pk_short_damage_rate) + if (flag & BF_LONG) + DAMAGE_RATE(battle_config.pk_long_damage_rate) + } + if(!damage) damage = 1; + } + + if(battle_config.skill_min_damage && damage > 0 && damage < div_) + { + if ((flag&BF_WEAPON && battle_config.skill_min_damage&1) + || (flag&BF_MAGIC && battle_config.skill_min_damage&2) + || (flag&BF_MISC && battle_config.skill_min_damage&4) + ) + damage = div_; + } + + if( bl->type == BL_MOB && !status_isdead(bl) && src != bl) { + if (damage > 0 ) + mobskill_event((TBL_MOB*)bl,src,gettick(),flag); + if (skill_id) + mobskill_event((TBL_MOB*)bl,src,gettick(),MSC_SKILLUSED|(skill_id<<16)); + } + if( sd ) { + if( pc_ismadogear(sd) && rnd()%100 < 50 ) { + short element = skill_get_ele(skill_id, skill_lv); + if( !skill_id || element == -1 ) { //Take weapon's element + struct status_data *sstatus = NULL; + if( src->type == BL_PC && ((TBL_PC*)src)->bonus.arrow_ele ) + element = ((TBL_PC*)src)->bonus.arrow_ele; + else if( (sstatus = status_get_status_data(src)) ) { + element = sstatus->rhw.ele; + } + } + else if( element == -2 ) //Use enchantment's element + element = status_get_attack_sc_element(src,status_get_sc(src)); + else if( element == -3 ) //Use random element + element = rnd()%ELE_MAX; + if( element == ELE_FIRE || element == ELE_WATER ) + pc_overheat(sd,element == ELE_FIRE ? 1 : -1); + } + } + + return damage; +} + +/*========================================== + * Calculates BG related damage adjustments. + *------------------------------------------*/ +int battle_calc_bg_damage(struct block_list *src, struct block_list *bl, int damage, int div_, uint16 skill_id, uint16 skill_lv, int flag) +{ + if( !damage ) + return 0; + + if( bl->type == BL_MOB ) + { + struct mob_data* md = BL_CAST(BL_MOB, bl); + if( map[bl->m].flag.battleground && (md->class_ == MOBID_BLUE_CRYST || md->class_ == MOBID_PINK_CRYST) && flag&BF_SKILL ) + return 0; // Crystal cannot receive skill damage on battlegrounds + } + + switch( skill_id ) + { + case PA_PRESSURE: + case HW_GRAVITATION: + case NJ_ZENYNAGE: + case KO_MUCHANAGE: + break; + default: + if( flag&BF_SKILL ) + { //Skills get a different reduction than non-skills. [Skotlex] + if( flag&BF_WEAPON ) + DAMAGE_RATE(battle_config.bg_weapon_damage_rate) + if( flag&BF_MAGIC ) + DAMAGE_RATE(battle_config.bg_magic_damage_rate) + if( flag&BF_MISC ) + DAMAGE_RATE(battle_config.bg_misc_damage_rate) + } + else + { //Normal attacks get reductions based on range. + if( flag&BF_SHORT ) + DAMAGE_RATE(battle_config.bg_short_damage_rate) + if( flag&BF_LONG ) + DAMAGE_RATE(battle_config.bg_long_damage_rate) + } + + if( !damage ) damage = 1; + } + + return damage; +} + +/*========================================== + * Calculates GVG related damage adjustments. + *------------------------------------------*/ +int battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int damage,int div_,uint16 skill_id,uint16 skill_lv,int flag) +{ + struct mob_data* md = BL_CAST(BL_MOB, bl); + int class_ = status_get_class(bl); + + if (!damage) //No reductions to make. + return 0; + + if(md && md->guardian_data) { + if(class_ == MOBID_EMPERIUM && flag&BF_SKILL) { + //Skill immunity. + switch (skill_id) { +#ifndef RENEWAL + case MO_TRIPLEATTACK: +#endif + case HW_GRAVITATION: + break; + default: + return 0; + } + } + if(src->type != BL_MOB) { + struct guild *g = guild_search(status_get_guild_id(src)); + + if (class_ == MOBID_EMPERIUM && (!g || guild_checkskill(g,GD_APPROVAL) <= 0 )) + return 0; + + if (g && battle_config.guild_max_castles && guild_checkcastles(g)>=battle_config.guild_max_castles) + return 0; // [MouseJstr] + } + } + + switch (skill_id) { + //Skills with no damage reduction. + case PA_PRESSURE: + case HW_GRAVITATION: + case NJ_ZENYNAGE: + case KO_MUCHANAGE: + break; + default: + /* Uncomment if you want god-mode Emperiums at 100 defense. [Kisuka] + if (md && md->guardian_data) { + damage -= damage * (md->guardian_data->castle->defense/100) * battle_config.castle_defense_rate/100; + } + */ + if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex] + if (flag&BF_WEAPON) + DAMAGE_RATE(battle_config.gvg_weapon_damage_rate) + if (flag&BF_MAGIC) + DAMAGE_RATE(battle_config.gvg_magic_damage_rate) + if (flag&BF_MISC) + DAMAGE_RATE(battle_config.gvg_misc_damage_rate) + } else { //Normal attacks get reductions based on range. + if (flag & BF_SHORT) + DAMAGE_RATE(battle_config.gvg_short_damage_rate) + if (flag & BF_LONG) + DAMAGE_RATE(battle_config.gvg_long_damage_rate) + } + if(!damage) damage = 1; + } + return damage; +} + +/*========================================== + * HP/SP drain calculation + *------------------------------------------*/ +static int battle_calc_drain(int damage, int rate, int per) +{ + int diff = 0; + + if (per && rnd()%1000 < rate) { + diff = ((int64)damage * per) / 100; + if (diff == 0) { + if (per > 0) + diff = 1; + else + diff = -1; + } + } + return diff; +} + +/*========================================== + * Passif skill dammages increases + *------------------------------------------*/ +int battle_addmastery(struct map_session_data *sd,struct block_list *target,int dmg,int type) +{ + int damage,skill; + struct status_data *status = status_get_status_data(target); + int weapon; + damage = dmg; + + nullpo_ret(sd); + + if((skill = pc_checkskill(sd,AL_DEMONBANE)) > 0 && + target->type == BL_MOB && //This bonus doesnt work against players. + (battle_check_undead(status->race,status->def_ele) || status->race==RC_DEMON) ) + damage += (skill*(int)(3+(sd->status.base_level+1)*0.05)); // submitted by orn + //damage += (skill * 3); + if( (skill = pc_checkskill(sd, RA_RANGERMAIN)) > 0 && (status->race == RC_BRUTE || status->race == RC_PLANT || status->race == RC_FISH) ) + damage += (skill * 5); + if( (skill = pc_checkskill(sd,NC_RESEARCHFE)) > 0 && (status->def_ele == ELE_FIRE || status->def_ele == ELE_EARTH) ) + damage += (skill * 10); + if( pc_ismadogear(sd) ) + damage += 20 + 20 * pc_checkskill(sd, NC_MADOLICENCE); + + if((skill = pc_checkskill(sd,HT_BEASTBANE)) > 0 && (status->race==RC_BRUTE || status->race==RC_INSECT) ) { + damage += (skill * 4); + if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_HUNTER) + damage += sd->status.str; + } + + if(type == 0) + weapon = sd->weapontype1; + else + weapon = sd->weapontype2; + switch(weapon) + { + case W_1HSWORD: + #ifdef RENEWAL + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) + damage += (skill * 3); + #endif + case W_DAGGER: + if((skill = pc_checkskill(sd,SM_SWORD)) > 0) + damage += (skill * 4); + if((skill = pc_checkskill(sd,GN_TRAINING_SWORD)) > 0) + damage += skill * 10; + break; + case W_2HSWORD: + #ifdef RENEWAL + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) + damage += (skill * 3); + #endif + if((skill = pc_checkskill(sd,SM_TWOHAND)) > 0) + damage += (skill * 4); + break; + case W_1HSPEAR: + case W_2HSPEAR: + if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) { + if(!pc_isriding(sd)) + damage += (skill * 4); + else + damage += (skill * 5); + } + break; + case W_1HAXE: + case W_2HAXE: + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) + damage += (skill * 3); + if((skill = pc_checkskill(sd,NC_TRAININGAXE)) > 0) + damage += (skill * 5); + break; + case W_MACE: + case W_2HMACE: + if((skill = pc_checkskill(sd,PR_MACEMASTERY)) > 0) + damage += (skill * 3); + if((skill = pc_checkskill(sd,NC_TRAININGAXE)) > 0) + damage += (skill * 5); + break; + case W_FIST: + if((skill = pc_checkskill(sd,TK_RUN)) > 0) + damage += (skill * 10); + // No break, fallthrough to Knuckles + case W_KNUCKLE: + if((skill = pc_checkskill(sd,MO_IRONHAND)) > 0) + damage += (skill * 3); + break; + case W_MUSICAL: + if((skill = pc_checkskill(sd,BA_MUSICALLESSON)) > 0) + damage += (skill * 3); + break; + case W_WHIP: + if((skill = pc_checkskill(sd,DC_DANCINGLESSON)) > 0) + damage += (skill * 3); + break; + case W_BOOK: + if((skill = pc_checkskill(sd,SA_ADVANCEDBOOK)) > 0) + damage += (skill * 3); + break; + case W_KATAR: + if((skill = pc_checkskill(sd,AS_KATAR)) > 0) + damage += (skill * 3); + break; + } + + return damage; +} +/*========================================== + * Calculates the standard damage of a normal attack assuming it hits, + * it calculates nothing extra fancy, is needed for magnum break's WATK_ELEMENT bonus. [Skotlex] + *------------------------------------------ + * Pass damage2 as NULL to not calc it. + * Flag values: + * &1: Critical hit + * &2: Arrow attack + * &4: Skill is Magic Crasher + * &8: Skip target size adjustment (Extremity Fist?) + *&16: Arrow attack but BOW, REVOLVER, RIFLE, SHOTGUN, GATLING or GRENADE type weapon not equipped (i.e. shuriken, kunai and venom knives not affected by DEX) + */ +static int battle_calc_base_damage(struct status_data *status, struct weapon_atk *wa, struct status_change *sc, unsigned short t_size, struct map_session_data *sd, int flag) +{ + unsigned int atkmin=0, atkmax=0; + short type = 0; + int damage = 0; + + if (!sd) + { //Mobs/Pets + if(flag&4) + { + atkmin = status->matk_min; + atkmax = status->matk_max; + } else { + atkmin = wa->atk; + atkmax = wa->atk2; + } + if (atkmin > atkmax) + atkmin = atkmax; + } else { //PCs + atkmax = wa->atk; + type = (wa == &status->lhw)?EQI_HAND_L:EQI_HAND_R; + + if (!(flag&1) || (flag&2)) + { //Normal attacks + atkmin = status->dex; + + if (sd->equip_index[type] >= 0 && sd->inventory_data[sd->equip_index[type]]) + atkmin = atkmin*(80 + sd->inventory_data[sd->equip_index[type]]->wlv*20)/100; + + if (atkmin > atkmax) + atkmin = atkmax; + + if(flag&2 && !(flag&16)) + { //Bows + atkmin = atkmin*atkmax/100; + if (atkmin > atkmax) + atkmax = atkmin; + } + } + } + + if (sc && sc->data[SC_MAXIMIZEPOWER]) + atkmin = atkmax; + + //Weapon Damage calculation + if (!(flag&1)) + damage = (atkmax>atkmin? rnd()%(atkmax-atkmin):0)+atkmin; + else + damage = atkmax; + + if (sd) + { + //rodatazone says the range is 0~arrow_atk-1 for non crit + if (flag&2 && sd->bonus.arrow_atk) + damage += ( (flag&1) ? sd->bonus.arrow_atk : rnd()%sd->bonus.arrow_atk ); + + //SizeFix only for players + if (!(sd->special_state.no_sizefix || (flag&8))) + DAMAGE_RATE(type==EQI_HAND_L? + sd->left_weapon.atkmods[t_size]: + sd->right_weapon.atkmods[t_size]) + } + + //Finally, add baseatk + if(flag&4) + damage += status->matk_min; + else + damage += status->batk; + + //rodatazone says that Overrefine bonuses are part of baseatk + //Here we also apply the weapon_atk_rate bonus so it is correctly applied on left/right hands. + if(sd) { + if (type == EQI_HAND_L) { + if(sd->left_weapon.overrefine) + damage += rnd()%sd->left_weapon.overrefine+1; + if (sd->weapon_atk_rate[sd->weapontype2]) + DAMAGE_ADDRATE(sd->weapon_atk_rate[sd->weapontype2]) + } else { //Right hand + if(sd->right_weapon.overrefine) + damage += rnd()%sd->right_weapon.overrefine+1; + if (sd->weapon_atk_rate[sd->weapontype1]) + DAMAGE_ADDRATE(sd->weapon_atk_rate[sd->weapontype1]) + } + } + return damage; +} + +/*========================================== + * Consumes ammo for the given skill. + *------------------------------------------*/ +void battle_consume_ammo(TBL_PC*sd, int skill, int lv) +{ + int qty=1; + if (!battle_config.arrow_decrement) + return; + + if (skill) { + qty = skill_get_ammo_qty(skill, lv); + if (!qty) qty = 1; + } + + if(sd->equip_index[EQI_AMMO]>=0) //Qty check should have been done in skill_check_condition + pc_delitem(sd,sd->equip_index[EQI_AMMO],qty,0,1,LOG_TYPE_CONSUME); + + sd->state.arrow_atk = 0; +} + +static int battle_range_type( + struct block_list *src, struct block_list *target, + uint16 skill_id, uint16 skill_lv) +{ //Skill Range Criteria + if (battle_config.skillrange_by_distance && + (src->type&battle_config.skillrange_by_distance) + ) { //based on distance between src/target [Skotlex] + if (check_distance_bl(src, target, 5)) + return BF_SHORT; + return BF_LONG; + } + //based on used skill's range + if (skill_get_range2(src, skill_id, skill_lv) < 5) + return BF_SHORT; + return BF_LONG; +} + +static int battle_blewcount_bonus(struct map_session_data *sd, uint16 skill_id) +{ + int i; + if (!sd->skillblown[0].id) + return 0; + //Apply the bonus blewcount. [Skotlex] + for (i = 0; i < ARRAYLENGTH(sd->skillblown) && sd->skillblown[i].id; i++) { + if (sd->skillblown[i].id == skill_id) + return sd->skillblown[i].val; + } + return 0; +} + +struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag); +struct Damage battle_calc_misc_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag); + +//For quick div adjustment. +#define damage_div_fix(dmg, div) { if (div > 1) (dmg)*=div; else if (div < 0) (div)*=-1; } +/*========================================== + * battle_calc_weapon_attack (by Skotlex) + *------------------------------------------*/ +static struct Damage battle_calc_weapon_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int wflag) +{ + unsigned int skillratio = 100; //Skill dmg modifiers. + short skill=0; + short s_ele, s_ele_, t_class; + int i, nk; + bool n_ele = false; // non-elemental + + struct map_session_data *sd, *tsd; + struct Damage wd; + struct status_change *sc = status_get_sc(src); + struct status_change *tsc = status_get_sc(target); + struct status_data *sstatus = status_get_status_data(src); + struct status_data *tstatus = status_get_status_data(target); + struct { + unsigned hit : 1; //the attack Hit? (not a miss) + unsigned cri : 1; //Critical hit + unsigned idef : 1; //Ignore defense + unsigned idef2 : 1; //Ignore defense (left weapon) + unsigned pdef : 2; //Pierces defense (Investigate/Ice Pick) + unsigned pdef2 : 2; //1: Use def+def2/100, 2: Use def+def2/50 + unsigned infdef : 1; //Infinite defense (plants) + unsigned arrow : 1; //Attack is arrow-based + unsigned rh : 1; //Attack considers right hand (wd.damage) + unsigned lh : 1; //Attack considers left hand (wd.damage2) + unsigned weapon : 1; //It's a weapon attack (consider VVS, and all that) + } flag; + + memset(&wd,0,sizeof(wd)); + memset(&flag,0,sizeof(flag)); + + if(src==NULL || target==NULL) + { + nullpo_info(NLP_MARK); + return wd; + } + //Initial flag + flag.rh=1; + flag.weapon=1; + flag.infdef=(tstatus->mode&MD_PLANT && skill_id != RA_CLUSTERBOMB +#ifdef RENEWAL + && skill_id != HT_FREEZINGTRAP +#endif + ?1:0); + if( target->type == BL_SKILL){ + TBL_SKILL *su = (TBL_SKILL*)target; + if( su->group && (su->group->skill_id == WM_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD) ) + flag.infdef = 1; + } + + //Initial Values + wd.type=0; //Normal attack + wd.div_=skill_id?skill_get_num(skill_id,skill_lv):1; + wd.amotion=(skill_id && skill_get_inf(skill_id)&INF_GROUND_SKILL)?0:sstatus->amotion; //Amotion should be 0 for ground skills. + if(skill_id == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=tstatus->dmotion; + wd.blewcount=skill_get_blewcount(skill_id,skill_lv); + wd.flag = BF_WEAPON; //Initial Flag + wd.flag |= (skill_id||wflag)?BF_SKILL:BF_NORMAL; // Baphomet card's splash damage is counted as a skill. [Inkfish] + wd.dmg_lv=ATK_DEF; //This assumption simplifies the assignation later + nk = skill_get_nk(skill_id); + if( !skill_id && wflag ) //If flag, this is splash damage from Baphomet Card and it always hits. + nk |= NK_NO_CARDFIX_ATK|NK_IGNORE_FLEE; + flag.hit = nk&NK_IGNORE_FLEE?1:0; + flag.idef = flag.idef2 = nk&NK_IGNORE_DEF?1:0; + + if (sc && !sc->count) + sc = NULL; //Skip checking as there are no status changes active. + if (tsc && !tsc->count) + tsc = NULL; //Skip checking as there are no status changes active. + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + + if(sd) + wd.blewcount += battle_blewcount_bonus(sd, skill_id); + + //Set miscellaneous data that needs be filled regardless of hit/miss + if( + (sd && sd->state.arrow_atk) || + (!sd && ((skill_id && skill_get_ammotype(skill_id)) || sstatus->rhw.range>3)) + ) + flag.arrow = 1; + + if(skill_id){ + wd.flag |= battle_range_type(src, target, skill_id, skill_lv); + switch(skill_id) + { + case MO_FINGEROFFENSIVE: + if(sd) { + if (battle_config.finger_offensive_type) + wd.div_ = 1; + else + wd.div_ = sd->spiritball_old; + } + break; + case HT_PHANTASMIC: + //Since these do not consume ammo, they need to be explicitly set as arrow attacks. + flag.arrow = 1; + break; +#ifndef RENEWAL + case PA_SHIELDCHAIN: + case CR_SHIELDBOOMERANG: +#endif + case LG_SHIELDPRESS: + case LG_EARTHDRIVE: + flag.weapon = 0; + break; + + case KN_PIERCE: + case ML_PIERCE: + wd.div_= (wd.div_>0?tstatus->size+1:-(tstatus->size+1)); + break; + + case TF_DOUBLE: //For NPC used skill. + case GS_CHAINACTION: + wd.type = 0x08; + break; + + case GS_GROUNDDRIFT: + case KN_SPEARSTAB: + case KN_BOWLINGBASH: + case MS_BOWLINGBASH: + case MO_BALKYOUNG: + case TK_TURNKICK: + wd.blewcount=0; + break; + + case KN_AUTOCOUNTER: + wd.flag=(wd.flag&~BF_SKILLMASK)|BF_NORMAL; + break; + + case NPC_CRITICALSLASH: + case LG_PINPOINTATTACK: + flag.cri = 1; //Always critical skill. + break; + + case LK_SPIRALPIERCE: + if (!sd) wd.flag=(wd.flag&~(BF_RANGEMASK|BF_WEAPONMASK))|BF_LONG|BF_MISC; + break; + } + } else //Range for normal attacks. + wd.flag |= flag.arrow?BF_LONG:BF_SHORT; + + if ( (!skill_id || skill_id == PA_SACRIFICE) && tstatus->flee2 && rnd()%1000 < tstatus->flee2 ) + { //Check for Lucky Dodge + wd.type=0x0b; + wd.dmg_lv=ATK_LUCKY; + if (wd.div_ < 0) wd.div_*=-1; + return wd; + } + + t_class = status_get_class(target); + s_ele = s_ele_ = skill_get_ele(skill_id, skill_lv); + if( !skill_id || s_ele == -1 ) + { //Take weapon's element + s_ele = sstatus->rhw.ele; + s_ele_ = sstatus->lhw.ele; + if( sd ){ //Summoning 10 talisman will endow your weapon. + ARR_FIND(1, 6, i, sd->talisman[i] >= 10); + if( i < 5 ) s_ele = s_ele_ = i; + } + if( flag.arrow && sd && sd->bonus.arrow_ele ) + s_ele = sd->bonus.arrow_ele; + if( battle_config.attack_attr_none&src->type ) + n_ele = true; //Weapon's element is "not elemental" + } + else if( s_ele == -2 ) //Use enchantment's element + s_ele = s_ele_ = status_get_attack_sc_element(src,sc); + else if( s_ele == -3 ) //Use random element + s_ele = s_ele_ = rnd()%ELE_MAX; + switch( skill_id ) + { + case GS_GROUNDDRIFT: + s_ele = s_ele_ = wflag; //element comes in flag. + break; + case LK_SPIRALPIERCE: + if (!sd) n_ele = false; //forced neutral for monsters + break; + } + + if (!(nk & NK_NO_ELEFIX) && !n_ele) + if (src->type == BL_HOM) + n_ele = true; //skill is "not elemental" + if (sc && sc->data[SC_GOLDENE_FERSE] && ((!skill_id && (rnd() % 100 < sc->data[SC_GOLDENE_FERSE]->val4)) || skill_id == MH_STAHL_HORN)) { + s_ele = s_ele_ = ELE_HOLY; + n_ele = false; + } + + if(!skill_id) + { //Skills ALWAYS use ONLY your right-hand weapon (tested on Aegis 10.2) + if (sd && sd->weapontype1 == 0 && sd->weapontype2 > 0) + { + flag.rh=0; + flag.lh=1; + } + if (sstatus->lhw.atk) + flag.lh=1; + } + + if( sd && !skill_id ) { //Check for double attack. + if( ( ( skill_lv = pc_checkskill(sd,TF_DOUBLE) ) > 0 && sd->weapontype1 == W_DAGGER ) + || ( sd->bonus.double_rate > 0 && sd->weapontype1 != W_FIST ) //Will fail bare-handed + || ( sc && sc->data[SC_KAGEMUSYA] && sd->weapontype1 != W_FIST )) // Need confirmation + { //Success chance is not added, the higher one is used [Skotlex] + if( rnd()%100 < ( 5*skill_lv > sd->bonus.double_rate ? 5*skill_lv : sc && sc->data[SC_KAGEMUSYA]?sc->data[SC_KAGEMUSYA]->val1*3:sd->bonus.double_rate ) ) + { + wd.div_ = skill_get_num(TF_DOUBLE,skill_lv?skill_lv:1); + wd.type = 0x08; + } + } + else if( sd->weapontype1 == W_REVOLVER && (skill_lv = pc_checkskill(sd,GS_CHAINACTION)) > 0 && rnd()%100 < 5*skill_lv ) + { + wd.div_ = skill_get_num(GS_CHAINACTION,skill_lv); + wd.type = 0x08; + } + else if(sc && sc->data[SC_FEARBREEZE] && sd->weapontype1==W_BOW && (i = sd->equip_index[EQI_AMMO]) >= 0 && sd->inventory_data[i] && sd->status.inventory[i].amount > 1){ + short rate[] = { 4, 4, 7, 9, 10 }; + if(sc->data[SC_FEARBREEZE]->val1 > 0 && sc->data[SC_FEARBREEZE]->val1 < 6 && rand()%100 < rate[sc->data[SC_FEARBREEZE]->val1-1]) { + wd.type = 0x08; + wd.div_ = 2; + if(sc->data[SC_FEARBREEZE]->val1 > 2){ + int chance = rand()%100; + wd.div_ += (chance >= 40) + (chance >= 70) + (chance >= 90); + wd.div_ = min(wd.div_,sc->data[SC_FEARBREEZE]->val1); + } + wd.div_ = min(wd.div_,sd->status.inventory[i].amount); + sc->data[SC_FEARBREEZE]->val4 = wd.div_-1; + } + } + } + + //Check for critical + if( !flag.cri && !(wd.type&0x08) && sstatus->cri && + (!skill_id || + skill_id == KN_AUTOCOUNTER || + skill_id == SN_SHARPSHOOTING || skill_id == MA_SHARPSHOOTING || + skill_id == NJ_KIRIKAGE)) + { + short cri = sstatus->cri; + if (sd) + { + cri+= sd->critaddrace[tstatus->race]; + if(flag.arrow) + cri += sd->bonus.arrow_cri; + } + if( sc && sc->data[SC_CAMOUFLAGE] ) + cri += 10 * (10-sc->data[SC_CAMOUFLAGE]->val4); + //The official equation is *2, but that only applies when sd's do critical. + //Therefore, we use the old value 3 on cases when an sd gets attacked by a mob + cri -= tstatus->luk*(!sd&&tsd?3:2); + + if( tsc && tsc->data[SC_SLEEP] ) { + cri <<= 1; + } + switch (skill_id) { + case KN_AUTOCOUNTER: + if(battle_config.auto_counter_type && + (battle_config.auto_counter_type&src->type)) + flag.cri = 1; + else + cri <<= 1; + break; + case SN_SHARPSHOOTING: + case MA_SHARPSHOOTING: + cri += 200; + break; + case NJ_KIRIKAGE: + cri += 250 + 50*skill_lv; + break; + } + if(tsd && tsd->bonus.critical_def) + cri = cri * ( 100 - tsd->bonus.critical_def ) / 100; + if (rnd()%1000 < cri) + flag.cri = 1; + } + if (flag.cri) + { + wd.type = 0x0a; + flag.idef = flag.idef2 = flag.hit = 1; + } else { //Check for Perfect Hit + if(sd && sd->bonus.perfect_hit > 0 && rnd()%100 < sd->bonus.perfect_hit) + flag.hit = 1; + if (sc && sc->data[SC_FUSION]) { + flag.hit = 1; //SG_FUSION always hit [Komurka] + flag.idef = flag.idef2 = 1; //def ignore [Komurka] + } + if( !flag.hit ) + switch(skill_id) + { + case AS_SPLASHER: + if( !wflag ) // Always hits the one exploding. + flag.hit = 1; + break; + case CR_SHIELDBOOMERANG: + if( sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_CRUSADER ) + flag.hit = 1; + break; + } + if (tsc && !flag.hit && tsc->opt1 && tsc->opt1 != OPT1_STONEWAIT && tsc->opt1 != OPT1_BURNING) + flag.hit = 1; + } + + if (!flag.hit) + { //Hit/Flee calculation + short + flee = tstatus->flee, +#ifdef RENEWAL + hitrate = 0; //Default hitrate +#else + hitrate = 80; //Default hitrate +#endif + + if(battle_config.agi_penalty_type && battle_config.agi_penalty_target&target->type) { + unsigned char attacker_count; //256 max targets should be a sane max + attacker_count = unit_counttargeted(target); + if(attacker_count >= battle_config.agi_penalty_count) { + if (battle_config.agi_penalty_type == 1) + flee = (flee * (100 - (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num))/100; + else //asume type 2: absolute reduction + flee -= (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num; + if(flee < 1) flee = 1; + } + } + + hitrate+= sstatus->hit - flee; + + if(wd.flag&BF_LONG && !skill_id && //Fogwall's hit penalty is only for normal ranged attacks. + tsc && tsc->data[SC_FOGWALL]) + hitrate -= 50; + + if(sd && flag.arrow) + hitrate += sd->bonus.arrow_hit; +#ifdef RENEWAL + if( sd ) //in Renewal hit bonus from Vultures Eye is not anymore shown in status window + hitrate += pc_checkskill(sd,AC_VULTURE); +#endif + if(skill_id) + switch(skill_id) + { //Hit skill modifiers + //It is proven that bonus is applied on final hitrate, not hit. + case SM_BASH: + case MS_BASH: + hitrate += hitrate * 5 * skill_lv / 100; + break; + case MS_MAGNUM: + case SM_MAGNUM: + hitrate += hitrate * 10 * skill_lv / 100; + break; + case KN_AUTOCOUNTER: + case PA_SHIELDCHAIN: + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_UNDEADATTACK: + case NPC_TELEKINESISATTACK: + case NPC_BLEEDING: + hitrate += hitrate * 20 / 100; + break; + case KN_PIERCE: + case ML_PIERCE: + hitrate += hitrate * 5 * skill_lv / 100; + break; + case AS_SONICBLOW: + if(sd && pc_checkskill(sd,AS_SONICACCEL)>0) + hitrate += hitrate * 50 / 100; + break; + case MC_CARTREVOLUTION: + case GN_CART_TORNADO: + case GN_CARTCANNON: + if( sd && pc_checkskill(sd, GN_REMODELING_CART) ) + hitrate += pc_checkskill(sd, GN_REMODELING_CART) * 4; + break; + case GC_VENOMPRESSURE: + hitrate += 10 + 4 * skill_lv; + break; + } + + if( sd ) { + // Weaponry Research hidden bonus + if ((skill = pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0) + hitrate += hitrate * ( 2 * skill ) / 100; + + if( (sd->status.weapon == W_1HSWORD || sd->status.weapon == W_DAGGER) && + (skill = pc_checkskill(sd, GN_TRAINING_SWORD))>0 ) + hitrate += 3 * skill; + } + + hitrate = cap_value(hitrate, battle_config.min_hitrate, battle_config.max_hitrate); + + if(rnd()%100 >= hitrate) + wd.dmg_lv = ATK_FLEE; + else + flag.hit = 1; + } //End hit/miss calculation + + if (flag.hit && !flag.infdef) //No need to do the math for plants + { //Hitting attack + +//Assuming that 99% of the cases we will not need to check for the flag.rh... we don't. +//ATK_RATE scales the damage. 100 = no change. 50 is halved, 200 is doubled, etc +#define ATK_RATE( a ) { wd.damage= wd.damage*(a)/100 ; if(flag.lh) wd.damage2= wd.damage2*(a)/100; } +#define ATK_RATE2( a , b ) { wd.damage= wd.damage*(a)/100 ; if(flag.lh) wd.damage2= wd.damage2*(b)/100; } +#define ATK_RATER(a){ wd.damage = (int64)wd.damage*(a)/100;} +#define ATK_RATEL(a){ wd.damage2 = (int64)wd.damage2*(a)/100;} +//Adds dmg%. 100 = +100% (double) damage. 10 = +10% damage +#define ATK_ADDRATE( a ) { wd.damage+= wd.damage*(a)/100 ; if(flag.lh) wd.damage2+= wd.damage2*(a)/100; } +#define ATK_ADDRATE2( a , b ) { wd.damage+= wd.damage*(a)/100 ; if(flag.lh) wd.damage2+= wd.damage2*(b)/100; } +//Adds an absolute value to damage. 100 = +100 damage +#define ATK_ADD( a ) { wd.damage+= a; if (flag.lh) wd.damage2+= a; } +#define ATK_ADD2( a , b ) { wd.damage+= a; if (flag.lh) wd.damage2+= b; } + + switch (skill_id) + { //Calc base damage according to skill + case PA_SACRIFICE: + wd.damage = (int64)sstatus->max_hp* 9/100; + wd.damage2 = 0; + break; +#ifndef RENEWAL + case NJ_ISSEN: + wd.damage = 40*sstatus->str +skill_lv*(sstatus->hp/10 + 35); + wd.damage2 = 0; + break; + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + if (sd) { + short index = sd->equip_index[EQI_HAND_R]; + + if (index >= 0 && + sd->inventory_data[index] && + sd->inventory_data[index]->type == IT_WEAPON) + wd.damage = sd->inventory_data[index]->weight*8/100; //80% of weight + } else + wd.damage = sstatus->rhw.atk2*8/10; //Else use Atk2 + + ATK_ADDRATE(50*skill_lv); //Skill modifier applies to weight only. + i = sstatus->str/10; + i*=i; + ATK_ADD(i); //Add str bonus. + switch (tstatus->size) { //Size-fix. Is this modified by weapon perfection? + case SZ_SMALL: //Small: 125% + ATK_RATE(125); + break; + //case SZ_MEDIUM: //Medium: 100% + case SZ_BIG: //Large: 75% + ATK_RATE(75); + break; + } + break; +#endif + case CR_SHIELDBOOMERANG: + case PA_SHIELDCHAIN: + case LG_SHIELDPRESS: + case LG_EARTHDRIVE: + wd.damage = sstatus->batk; + if (sd) { + short index = sd->equip_index[EQI_HAND_L]; + + if (index >= 0 && + sd->inventory_data[index] && + sd->inventory_data[index]->type == IT_ARMOR) + ATK_ADD(sd->inventory_data[index]->weight/10); + } else + ATK_ADD(sstatus->rhw.atk2); //Else use Atk2 + break; + case HFLI_SBR44: //[orn] + if(src->type == BL_HOM) { + wd.damage = ((TBL_HOM*)src)->homunculus.intimacy ; + break; + } + default: + { + i = (flag.cri?1:0)| + (flag.arrow?2:0)| + (skill_id == HW_MAGICCRASHER?4:0)| + (!skill_id && sc && sc->data[SC_CHANGE]?4:0)| + (skill_id == MO_EXTREMITYFIST?8:0)| + (sc && sc->data[SC_WEAPONPERFECTION]?8:0); + if (flag.arrow && sd) + switch(sd->status.weapon) { + case W_BOW: + case W_REVOLVER: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + break; + default: + i |= 16; // for ex. shuriken must not be influenced by DEX + } + wd.damage = battle_calc_base_damage(sstatus, &sstatus->rhw, sc, tstatus->size, sd, i); + if (flag.lh) + wd.damage2 = battle_calc_base_damage(sstatus, &sstatus->lhw, sc, tstatus->size, sd, i); + + if (nk&NK_SPLASHSPLIT){ // Divide ATK among targets + if(wflag>0) + wd.damage/= wflag; + else + ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id)); + } + + //Add any bonuses that modify the base baseatk+watk (pre-skills) + if(sd) { + if (sd->bonus.atk_rate) + ATK_ADDRATE(sd->bonus.atk_rate); + + if(flag.cri && sd->bonus.crit_atk_rate) + ATK_ADDRATE(sd->bonus.crit_atk_rate); + + if(sd->status.party_id && (skill=pc_checkskill(sd,TK_POWER)) > 0){ + if( (i = party_foreachsamemap(party_sub_count, sd, 0)) > 1 ) // exclude the player himself [Inkfish] + ATK_ADDRATE(2*skill*i); + } + } + break; + } //End default case + } //End switch(skill_id) + + //Skill damage modifiers that stack linearly + if(sc && skill_id != PA_SACRIFICE) + { + if(sc->data[SC_OVERTHRUST]) + skillratio += sc->data[SC_OVERTHRUST]->val3; + if(sc->data[SC_MAXOVERTHRUST]) + skillratio += sc->data[SC_MAXOVERTHRUST]->val2; + if (sc->data[SC_BERSERK] || sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC__BLOODYLUST]) + skillratio += 100; + if(sc->data[SC_ZENKAI] && sstatus->rhw.ele == sc->data[SC_ZENKAI]->val2 ) + skillratio += sc->data[SC_ZENKAI]->val1 * 2; + } + if( !skill_id ) + { + ATK_RATE(skillratio); + } + else + { + switch( skill_id ) + { + case SM_BASH: + case MS_BASH: + skillratio += 30*skill_lv; + break; + case SM_MAGNUM: + case MS_MAGNUM: + skillratio += 20*skill_lv; + break; + case MC_MAMMONITE: + skillratio += 50*skill_lv; + break; + case HT_POWER: + skillratio += -50+8*sstatus->str; + break; + case AC_DOUBLE: + case MA_DOUBLE: + skillratio += 10*(skill_lv-1); + break; + case AC_SHOWER: + case MA_SHOWER: + #ifdef RENEWAL + skillratio += 50+10*skill_lv; + #else + skillratio += -25+5*skill_lv; + #endif + break; + case AC_CHARGEARROW: + case MA_CHARGEARROW: + skillratio += 50; + break; +#ifndef RENEWAL + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + skillratio += -50+10*skill_lv; + break; +#endif + case KN_PIERCE: + case ML_PIERCE: + skillratio += 10*skill_lv; + break; + case MER_CRASH: + skillratio += 10*skill_lv; + break; + case KN_SPEARSTAB: + skillratio += 15*skill_lv; + break; + case KN_SPEARBOOMERANG: + skillratio += 50*skill_lv; + break; + case KN_BRANDISHSPEAR: + case ML_BRANDISH: + { + int ratio = 100+20*skill_lv; + skillratio += ratio-100; + if(skill_lv>3 && wflag==1) skillratio += ratio/2; + if(skill_lv>6 && wflag==1) skillratio += ratio/4; + if(skill_lv>9 && wflag==1) skillratio += ratio/8; + if(skill_lv>6 && wflag==2) skillratio += ratio/2; + if(skill_lv>9 && wflag==2) skillratio += ratio/4; + if(skill_lv>9 && wflag==3) skillratio += ratio/2; + break; + } + case KN_BOWLINGBASH: + case MS_BOWLINGBASH: + skillratio+= 40*skill_lv; + break; + case AS_GRIMTOOTH: + skillratio += 20*skill_lv; + break; + case AS_POISONREACT: + skillratio += 30*skill_lv; + break; + case AS_SONICBLOW: + skillratio += -50+5*skill_lv; + break; + case TF_SPRINKLESAND: + skillratio += 30; + break; + case MC_CARTREVOLUTION: + skillratio += 50; + if(sd && sd->cart_weight) + skillratio += 100*sd->cart_weight/sd->cart_weight_max; // +1% every 1% weight + else if (!sd) + skillratio += 100; //Max damage for non players. + break; + case NPC_RANDOMATTACK: + skillratio += 100*skill_lv; + break; + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_UNDEADATTACK: + case NPC_TELEKINESISATTACK: + case NPC_BLOODDRAIN: + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_THUNDERBREATH: + case NPC_HELLJUDGEMENT: + case NPC_PULSESTRIKE: + skillratio += 100*(skill_lv-1); + break; + case RG_BACKSTAP: + if(sd && sd->status.weapon == W_BOW && battle_config.backstab_bow_penalty) + skillratio += (200+40*skill_lv)/2; + else + skillratio += 200+40*skill_lv; + break; + case RG_RAID: + skillratio += 40*skill_lv; + break; + case RG_INTIMIDATE: + skillratio += 30*skill_lv; + break; + case CR_SHIELDCHARGE: + skillratio += 20*skill_lv; + break; + case CR_SHIELDBOOMERANG: + skillratio += 30*skill_lv; + break; + case NPC_DARKCROSS: + case CR_HOLYCROSS: + { + int ratio = 35*skill_lv; + #ifdef RENEWAL + if(sd && sd->status.weapon == W_2HSPEAR) + ratio *= 2; + #endif + skillratio += ratio; + break; + } + case AM_DEMONSTRATION: + skillratio += 20*skill_lv; + break; + case AM_ACIDTERROR: + skillratio += 40*skill_lv; + break; + case MO_FINGEROFFENSIVE: + skillratio+= 50 * skill_lv; + break; + case MO_INVESTIGATE: + skillratio += 75*skill_lv; + flag.pdef = flag.pdef2 = 2; + break; + case MO_EXTREMITYFIST: + { //Overflow check. [Skotlex] + unsigned int ratio = skillratio + 100*(8 + sstatus->sp/10); + //You'd need something like 6K SP to reach this max, so should be fine for most purposes. + if (ratio > 60000) ratio = 60000; //We leave some room here in case skillratio gets further increased. + skillratio = (unsigned short)ratio; + } + break; + case MO_TRIPLEATTACK: + skillratio += 20*skill_lv; + break; + case MO_CHAINCOMBO: + skillratio += 50+50*skill_lv; + break; + case MO_COMBOFINISH: + skillratio += 140+60*skill_lv; + break; + case BA_MUSICALSTRIKE: + case DC_THROWARROW: + skillratio += 25+25*skill_lv; + break; + case CH_TIGERFIST: + skillratio += 100*skill_lv-60; + break; + case CH_CHAINCRUSH: + skillratio += 300+100*skill_lv; + break; + case CH_PALMSTRIKE: + skillratio += 100+100*skill_lv; + break; + case LK_HEADCRUSH: + skillratio += 40*skill_lv; + break; + case LK_JOINTBEAT: + i = 10*skill_lv-50; + // Although not clear, it's being assumed that the 2x damage is only for the break neck ailment. + if (wflag&BREAK_NECK) i*=2; + skillratio += i; + break; +#ifdef RENEWAL + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + {// Formula: Floor[Floor(Weapon Weight/2)*skill level + ATK ]*(100%+50%*s.lvl) * 5 multi-hits + short index = sd?sd->equip_index[EQI_HAND_R]:0; + int weight = 0; + + if (sd && index >= 0 && + sd->inventory_data[index] && + sd->inventory_data[index]->type == IT_WEAPON) + weight = sd->inventory_data[index]->weight/20; + ATK_ADD(weight * skill_lv) + skillratio += 50*skill_lv; + } + break; +#endif + case ASC_METEORASSAULT: + skillratio += 40*skill_lv-60; + break; + case SN_SHARPSHOOTING: + case MA_SHARPSHOOTING: + skillratio += 100+50*skill_lv; + break; + case CG_ARROWVULCAN: + skillratio += 100+100*skill_lv; + break; + case AS_SPLASHER: + skillratio += 400+50*skill_lv; + if(sd) + skillratio += 20 * pc_checkskill(sd,AS_POISONREACT); + break; + case ASC_BREAKER: + skillratio += 100*skill_lv-100; + break; + case PA_SACRIFICE: + skillratio += 10*skill_lv-10; + break; + case PA_SHIELDCHAIN: + skillratio += 30*skill_lv; + break; + case WS_CARTTERMINATION: + i = 10 * (16 - skill_lv); + if (i < 1) i = 1; + //Preserve damage ratio when max cart weight is changed. + if(sd && sd->cart_weight) + skillratio += sd->cart_weight/i * 80000/battle_config.max_cart_weight - 100; + else if (!sd) + skillratio += 80000 / i - 100; + break; + case TK_DOWNKICK: + skillratio += 60 + 20*skill_lv; + break; + case TK_STORMKICK: + skillratio += 60 + 20*skill_lv; + break; + case TK_TURNKICK: + skillratio += 90 + 30*skill_lv; + break; + case TK_COUNTER: + skillratio += 90 + 30*skill_lv; + break; + case TK_JUMPKICK: + skillratio += -70 + 10*skill_lv; + if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id) + skillratio += 10*status_get_lv(src)/3; //Tumble bonus + if (wflag) + { + skillratio += 10*status_get_lv(src)/3; //Running bonus (TODO: What is the real bonus?) + if( sc && sc->data[SC_SPURT] ) // Spurt bonus + skillratio *= 2; + } + break; + case GS_TRIPLEACTION: + skillratio += 50*skill_lv; + break; + case GS_BULLSEYE: + //Only works well against brute/demihumans non bosses. + if((tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN) + && !(tstatus->mode&MD_BOSS)) + skillratio += 400; + break; + case GS_TRACKING: + skillratio += 100 *(skill_lv+1); + break; + case GS_PIERCINGSHOT: + skillratio += 20*skill_lv; + break; + case GS_RAPIDSHOWER: + skillratio += 10*skill_lv; + break; + case GS_DESPERADO: + skillratio += 50*(skill_lv-1); + break; + case GS_DUST: + skillratio += 50*skill_lv; + break; + case GS_FULLBUSTER: + skillratio += 100*(skill_lv+2); + break; + case GS_SPREADATTACK: + #ifdef RENEWAL + skillratio += 20*(skill_lv); + #else + skillratio += 20*(skill_lv-1); + #endif + break; +#ifdef RENEWAL + case NJ_ISSEN: + skillratio += 100 * (skill_lv-1); + break; +#endif + case NJ_HUUMA: + skillratio += 50 + 150*skill_lv; + break; + case NJ_TATAMIGAESHI: +#ifdef RENEWAL + ATK_RATE(200); +#endif + skillratio += 10*skill_lv; + break; + case NJ_KASUMIKIRI: + skillratio += 10*skill_lv; + break; + case NJ_KIRIKAGE: + skillratio += 100*(skill_lv-1); + break; + case KN_CHARGEATK: + { + int k = (wflag-1)/3; //+100% every 3 cells of distance + if( k > 2 ) k = 2; // ...but hard-limited to 300%. + skillratio += 100 * k; + } + break; + case HT_PHANTASMIC: + skillratio += 50; + break; + case MO_BALKYOUNG: + skillratio += 200; + break; + case HFLI_MOON: //[orn] + skillratio += 10+110*skill_lv; + break; + case HFLI_SBR44: //[orn] + skillratio += 100 *(skill_lv-1); + break; + case NPC_VAMPIRE_GIFT: + skillratio += ((skill_lv-1)%5+1)*100; + break; + case RK_SONICWAVE: + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case RK_HUNDREDSPEAR: + skillratio += 500 + (80 * skill_lv); + if( sd ) + { + short index = sd->equip_index[EQI_HAND_R]; + if( index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->type == IT_WEAPON ) + skillratio += max(10000 - sd->inventory_data[index]->weight, 0) / 10; + skillratio += 50 * pc_checkskill(sd,LK_SPIRALPIERCE); + } // (1 + [(Casters Base Level - 100) / 200]) + skillratio = skillratio * (100 + (status_get_lv(src)-100) / 2) / 100; + break; + case RK_WINDCUTTER: + skillratio += 50 * skill_lv; + RE_LVL_DMOD(100); + break; + case RK_IGNITIONBREAK: + i = distance_bl(src,target); + if( i < 2 ) + skillratio = 200 + 200 * skill_lv; + else if( i < 4 ) + skillratio = 100 + 200 * skill_lv; + else + skillratio = 100 + 100 * skill_lv; + RE_LVL_DMOD(100); + if( sstatus->rhw.ele == ELE_FIRE ) + skillratio += skillratio / 2; + break; + case RK_CRUSHSTRIKE: + if( sd ) + {//ATK [{Weapon Level * (Weapon Upgrade Level + 6) * 100} + (Weapon ATK) + (Weapon Weight)]% + short index = sd->equip_index[EQI_HAND_R]; + if( index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_WEAPON ) + skillratio = sd->inventory_data[index]->weight/10 + sstatus->rhw.atk + + 100 * sd->inventory_data[index]->wlv * (sd->status.inventory[index].refine + 6); + } + break; + case RK_STORMBLAST: + skillratio = 100 * (sd ? pc_checkskill(sd,RK_RUNEMASTERY) : 1) + 100 * (sstatus->int_ / 4); + break; + case RK_PHANTOMTHRUST: + skillratio = 50 * skill_lv + 10 * ( sd ? pc_checkskill(sd,KN_SPEARMASTERY) : 10); + //if( s_level > 100 ) skillratio += skillratio * s_level / 150; // Base level bonus. This is official, but is disabled until I can confirm something with was changed or not. [Rytech] + //if( s_level > 100 ) skillratio += skillratio * (s_level - 100) / 200; // Base level bonus. + break; + /** + * GC Guilotine Cross + **/ + case GC_CROSSIMPACT: + skillratio += 900 + 100 * skill_lv; + RE_LVL_DMOD(120); + break; + case GC_PHANTOMMENACE: + skillratio += 200; + break; + case GC_COUNTERSLASH: + //ATK [{(Skill Level x 100) + 300} x Caster's Base Level / 120]% + ATK [(AGI x 2) + (Caster's Job Level x 4)]% + skillratio += 200 + (100 * skill_lv); + RE_LVL_DMOD(120); + skillratio += sstatus->agi + (sd?sd->status.job_level:0) * 4; + break; + case GC_ROLLINGCUTTER: + skillratio += -50 + 50 * skill_lv; + RE_LVL_DMOD(100); + break; + case GC_CROSSRIPPERSLASHER: + skillratio += 300 + 80 * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->data[SC_ROLLINGCUTTER] ) + skillratio += sc->data[SC_ROLLINGCUTTER]->val1 * sstatus->agi; + break; + /** + * Arch Bishop + **/ + case AB_DUPLELIGHT_MELEE: + skillratio += 10 * skill_lv; + break; + /** + * Ranger + **/ + case RA_ARROWSTORM: + skillratio += 900 + 80 * skill_lv; + RE_LVL_DMOD(100); + break; + case RA_AIMEDBOLT: + skillratio += 400 + 50 * skill_lv; + RE_LVL_DMOD(100); + if( tsc && (tsc->data[SC_BITE] || tsc->data[SC_ANKLE] || tsc->data[SC_ELECTRICSHOCKER]) ) + wd.div_ = tstatus->size + 2 + ( (rnd()%100 < 50-tstatus->size*10) ? 1 : 0 ); + break; + case RA_CLUSTERBOMB: + skillratio += 100 + 100 * skill_lv; + break; + case RA_WUGDASH:// ATK 300% + skillratio += 200; + break; + case RA_WUGSTRIKE: + skillratio = 200 * skill_lv; + break; + case RA_WUGBITE: + skillratio += 300 + 200 * skill_lv; + if ( skill_lv == 5 ) skillratio += 100; + break; + case RA_SENSITIVEKEEN: + skillratio += 50 * skill_lv; + break; + /** + * Mechanic + **/ + case NC_BOOSTKNUCKLE: + skillratio += 100 + 100 * skill_lv + sstatus->dex; + RE_LVL_DMOD(100); + break; + case NC_PILEBUNKER: + skillratio += 200 + 100 * skill_lv + sstatus->str; + RE_LVL_DMOD(100); + break; + case NC_VULCANARM: + skillratio = 70 * skill_lv + sstatus->dex; + RE_LVL_DMOD(100); + break; + case NC_FLAMELAUNCHER: + case NC_COLDSLOWER: + skillratio += 200 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case NC_ARMSCANNON: + switch( tstatus->size ) { + case SZ_SMALL: skillratio += 100 + 500 * skill_lv; break;// Small + case SZ_MEDIUM: skillratio += 100 + 400 * skill_lv; break;// Medium + case SZ_BIG: skillratio += 100 + 300 * skill_lv; break;// Large + } + RE_LVL_DMOD(100); + //NOTE: Their's some other factors that affects damage, but not sure how exactly. Will recheck one day. [Rytech] + break; + case NC_AXEBOOMERANG: + skillratio += 60 + 40 * skill_lv; + if( sd ) { + short index = sd->equip_index[EQI_HAND_R]; + if( index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_WEAPON ) + skillratio += sd->inventory_data[index]->weight / 10;// Weight is divided by 10 since 10 weight in coding make 1 whole actural weight. [Rytech] + } + RE_LVL_DMOD(100); + break; + case NC_POWERSWING: + skillratio += 80 + 20 * skill_lv + sstatus->str + sstatus->dex; + RE_LVL_DMOD(100); + break; + case NC_AXETORNADO: + skillratio += 100 + 100 * skill_lv + sstatus->vit; + RE_LVL_DMOD(100); + break; + case SC_FATALMENACE: + skillratio += 100 * skill_lv; + break; + case SC_TRIANGLESHOT: + skillratio += 270 + 30 * skill_lv; + break; + case SC_FEINTBOMB: + skillratio += 100 + 100 * skill_lv; + break; + case LG_CANNONSPEAR:// Stimated formula. Still need confirm it. + skillratio += -100 + (50 + sstatus->str) * skill_lv; + RE_LVL_DMOD(100); + break; + case LG_BANISHINGPOINT: + skillratio += -100 + ((50 * skill_lv) + (30 * ((sd)?pc_checkskill(sd,SM_BASH):1))); + RE_LVL_DMOD(100); + break; + case LG_SHIELDPRESS: + skillratio += 60 + 43 * skill_lv; + RE_LVL_DMOD(100); + break; + case LG_PINPOINTATTACK: + skillratio = ((100 * skill_lv) + (10 * status_get_agi(src)) ); + RE_LVL_DMOD(100); + break; + case LG_RAGEBURST: + if( sd && sd->spiritball_old ) + skillratio += -100 + (sd->spiritball_old * 200); + else + skillratio += -100 + 15 * 200; + RE_LVL_DMOD(100); + break; + case LG_SHIELDSPELL:// [(Casters Base Level x 4) + (Shield DEF x 10) + (Casters VIT x 2)] % + if( sd ) { + struct item_data *shield_data = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; + skillratio = status_get_lv(src) * 4 + status_get_vit(src) * 2; + if( shield_data ) + skillratio += shield_data->def * 10; + } else + skillratio += 2400; //2500% + break; + case LG_MOONSLASHER: + skillratio += -100 + (120 * skill_lv + ((sd) ? pc_checkskill(sd,LG_OVERBRAND) : 5) * 80); + RE_LVL_DMOD(100); + break; + case LG_OVERBRAND: + skillratio = 400 * skill_lv + (pc_checkskill(sd,CR_SPEARQUICKEN) * 30); + RE_LVL_DMOD(100); + break; + case LG_OVERBRAND_BRANDISH: + skillratio = 300 * skill_lv + (2 * (sstatus->str + sstatus->dex) / 3); + RE_LVL_DMOD(100); + break; + case LG_OVERBRAND_PLUSATK: + skillratio = 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case LG_RAYOFGENESIS: + skillratio = 300 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case LG_EARTHDRIVE: + skillratio = (skillratio + 100) * skill_lv; + RE_LVL_DMOD(100); + break; + case LG_HESPERUSLIT: + skillratio += 120 * skill_lv - 100; + break; + case SR_DRAGONCOMBO: + skillratio += 40 * skill_lv; + RE_LVL_DMOD(100); + break; + case SR_SKYNETBLOW: + //ATK [{(Skill Level x 80) + (Caster AGI)} x Caster Base Level / 100] % + skillratio = 80 * skill_lv + sstatus->agi; + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_DRAGONCOMBO )//ATK [{(Skill Level x 100) + (Caster AGI) + 150} x Caster Base Level / 100] % + skillratio = 100 * skill_lv + sstatus->agi + 150; + RE_LVL_DMOD(100); + break; + case SR_EARTHSHAKER: + if( tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || // [(Skill Level x 150) x (Caster Base Level / 100) + (Caster INT x 3)] % + tsc->data[SC_CHASEWALK] || tsc->data[SC_CLOAKINGEXCEED] || tsc->data[SC__INVISIBILITY]) ){ + skillratio = 150 * skill_lv; + RE_LVL_DMOD(100); + skillratio += sstatus->int_ * 3; + }else{ //[(Skill Level x 50) x (Caster Base Level / 100) + (Caster INT x 2)] % + skillratio += 50 * (skill_lv-2); + RE_LVL_DMOD(100); + skillratio += sstatus->int_ * 2; + } + break; + case SR_FALLENEMPIRE:// ATK [(Skill Level x 150 + 100) x Caster Base Level / 150] % + skillratio += 150 *skill_lv; + RE_LVL_DMOD(150); + break; + case SR_TIGERCANNON:// ATK [((Caster consumed HP + SP) / 4) x Caster Base Level / 100] % + { + int hp = (int64)sstatus->max_hp * (10 + 2 * skill_lv) / 100, + sp = (int64)sstatus->max_sp * (6 + skill_lv) / 100; + skillratio = ((int64)hp+sp) / 4; + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) // ATK [((Caster consumed HP + SP) / 2) x Caster Base Level / 100] % + skillratio = (int64)hp+sp / 2; + RE_LVL_DMOD(100); + } + break; + case SR_RAMPAGEBLASTER: + skillratio += 20 * skill_lv * (sd?sd->spiritball_old:5) - 100; + if( sc && sc->data[SC_EXPLOSIONSPIRITS] ){ + skillratio += sc->data[SC_EXPLOSIONSPIRITS]->val1 * 20; + RE_LVL_DMOD(120); + }else + RE_LVL_DMOD(150); + break; + case SR_KNUCKLEARROW: + if( wflag&4 ){ // ATK [(Skill Level x 150) + (1000 x Target current weight / Maximum weight) + (Target Base Level x 5) x (Caster Base Level / 150)] % + skillratio = 150 * skill_lv + status_get_lv(target) * 5 * (status_get_lv(src) / 100) ; + if( tsd && tsd->weight ) + skillratio += 100 * (tsd->weight / tsd->max_weight); + }else // ATK [(Skill Level x 100 + 500) x Caster Base Level / 100] % + skillratio += 400 + (100 * skill_lv); + RE_LVL_DMOD(100); + break; + case SR_WINDMILL: // ATK [(Caster Base Level + Caster DEX) x Caster Base Level / 100] % + skillratio = status_get_lv(src) + sstatus->dex; + RE_LVL_DMOD(100); + break; + case SR_GATEOFHELL: + if( sc && sc->data[SC_COMBO] + && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) + skillratio += 800 * skill_lv -100; + else + skillratio += 500 * skill_lv -100; + RE_LVL_DMOD(100); + break; + case SR_GENTLETOUCH_QUIET: + skillratio += 100 * skill_lv - 100 + sstatus->dex; + RE_LVL_DMOD(100); + break; + case SR_HOWLINGOFLION: + skillratio += 300 * skill_lv - 100; + RE_LVL_DMOD(150); + break; + case SR_RIDEINLIGHTNING: // ATK [{(Skill Level x 200) + Additional Damage} x Caster Base Level / 100] % + if( (sstatus->rhw.ele) == ELE_WIND || (sstatus->lhw.ele) == ELE_WIND ) + skillratio += skill_lv * 50; + skillratio += -100 + 200 * skill_lv; + RE_LVL_DMOD(100); + break; + case WM_REVERBERATION_MELEE: + // ATK [{(Skill Level x 100) + 300} x Caster Base Level / 100] + skillratio += 200 + 100 * pc_checkskill(sd, WM_REVERBERATION); + RE_LVL_DMOD(100); + break; + case WM_SEVERE_RAINSTORM_MELEE: + //ATK [{(Caster DEX + AGI) x (Skill Level / 5)} x Caster Base Level / 100] % + skillratio = (sstatus->dex + sstatus->agi) * (skill_lv * 2); + RE_LVL_DMOD(100); + skillratio /= 10; + break; + case WM_GREAT_ECHO: + skillratio += 800 + 100 * skill_lv; + if( sd ) { // Still need official value [pakpil] + short lv = (short)skill_lv; + skillratio += 100 * skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),0); + } + break; + case WM_SOUND_OF_DESTRUCTION: + skillratio += 400; + break; + case GN_CART_TORNADO: + // ATK [( Skill Level x 50 ) + ( Cart Weight / ( 150 - Caster Base STR ))] + ( Cart Remodeling Skill Level x 50 )] % + skillratio = 50 * skill_lv; + if( sd && sd->cart_weight) + skillratio += sd->cart_weight/10 / max(150-sstatus->str,1) + pc_checkskill(sd, GN_REMODELING_CART) * 50; + break; + case GN_CARTCANNON: + // ATK [{( Cart Remodeling Skill Level x 50 ) x ( INT / 40 )} + ( Cart Cannon Skill Level x 60 )] % + skillratio = 60 * skill_lv; + if( sd ) skillratio += pc_checkskill(sd, GN_REMODELING_CART) * 50 * (sstatus->int_ / 40); + break; + case GN_SPORE_EXPLOSION: + skillratio += 200 + 100 * skill_lv; + break; + case GN_CRAZYWEED_ATK: + skillratio += 400 + 100 * skill_lv; + break; + case GN_SLINGITEM_RANGEMELEEATK: + if( sd ) { + switch( sd->itemid ) { + case 13260: // Apple Bomob + case 13261: // Coconut Bomb + case 13262: // Melon Bomb + case 13263: // Pinapple Bomb + skillratio += 400; // Unconfirded + break; + case 13264: // Banana Bomb 2000% + skillratio += 1900; + break; + case 13265: skillratio -= 75; break; // Black Lump 25% + case 13266: skillratio -= 25; break; // Hard Black Lump 75% + case 13267: skillratio += 100; break; // Extremely Hard Black Lump 200% + } + } else + skillratio += 300; // Bombs + break; + case SO_VARETYR_SPEAR://ATK [{( Striking Level x 50 ) + ( Varetyr Spear Skill Level x 50 )} x Caster Base Level / 100 ] % + skillratio = 50 * skill_lv + ( sd ? pc_checkskill(sd, SO_STRIKING) * 50 : 0 ); + if( sc && sc->data[SC_BLAST_OPTION] ) + skillratio += sd ? sd->status.job_level * 5 : 0; + break; + // Physical Elemantal Spirits Attack Skills + case EL_CIRCLE_OF_FIRE: + case EL_FIRE_BOMB_ATK: + case EL_STONE_RAIN: + skillratio += 200; + break; + case EL_FIRE_WAVE_ATK: + skillratio += 500; + break; + case EL_TIDAL_WEAPON: + skillratio += 1400; + break; + case EL_WIND_SLASH: + skillratio += 100; + break; + case EL_HURRICANE: + skillratio += 600; + break; + case EL_TYPOON_MIS: + case EL_WATER_SCREW_ATK: + skillratio += 900; + break; + case EL_STONE_HAMMER: + skillratio += 400; + break; + case EL_ROCK_CRUSHER: + skillratio += 700; + break; + case KO_JYUMONJIKIRI: + if( tsc && tsc->data[SC_JYUMONJIKIRI] ) + wd.div_ = wd.div_ * -1;// needs more info + skillratio += -100 + 150 * skill_lv; + case KO_HUUMARANKA: + skillratio += -100 + 150 * skill_lv + sstatus->dex/2 + sstatus->agi/2; // needs more info + break; + case KO_SETSUDAN: + skillratio += 100 * (skill_lv-1); + break; + case KO_BAKURETSU: + skillratio = 50 * skill_lv * (sd?pc_checkskill(sd,NJ_TOBIDOUGU):10); + break; + case MH_NEEDLE_OF_PARALYZE: + skillratio += 600 + 100 * skill_lv; + break; + case MH_STAHL_HORN: + skillratio += 400 + 100 * skill_lv; + break; + case MH_LAVA_SLIDE: + skillratio = 70 * skill_lv; + break; + case MH_TINDER_BREAKER: + case MH_MAGMA_FLOW: + skillratio += -100 + 100 * skill_lv; + break; + } + + ATK_RATE(skillratio); + + //Constant/misc additions from skills + switch (skill_id) { + case MO_EXTREMITYFIST: + ATK_ADD(250 + 150*skill_lv); + break; + case TK_DOWNKICK: + case TK_STORMKICK: + case TK_TURNKICK: + case TK_COUNTER: + case TK_JUMPKICK: + //TK_RUN kick damage bonus. + if(sd && sd->weapontype1 == W_FIST && sd->weapontype2 == W_FIST) + ATK_ADD(10*pc_checkskill(sd, TK_RUN)); + break; + case GS_MAGICALBULLET: + if(sstatus->matk_max>sstatus->matk_min) { + ATK_ADD(sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min)); + } else { + ATK_ADD(sstatus->matk_min); + } + break; + case NJ_SYURIKEN: + ATK_ADD(4*skill_lv); + break; +#ifdef RENEWAL + case NJ_ISSEN: + // Damage = (current HP + atk * skill_lv) - (sdef+edef) + ATK_ADD(sstatus->hp); + wd.damage2 = 0;// needs more info if this really 0 for dual weilding KG/OB. [malufett] + if( sc && sc->data[SC_BUNSINJYUTSU] && (i=sc->data[SC_BUNSINJYUTSU]->val2) > 0){ + wd.div_ = -( i + 2 ); // mirror image number of hits + 2 + ATK_ADDRATE(20 + i*20); // (20 + 20 * mirror image) % + } + break; +#endif + case HT_FREEZINGTRAP: + if(sd) + ATK_ADD( 40 * pc_checkskill(sd, RA_RESEARCHTRAP) ); + break; + case RA_WUGDASH ://(Caster Current Weight x 10 / 8) + if( sd && sd->weight ) + ATK_ADD( sd->weight / 8 ); + case RA_WUGSTRIKE: + case RA_WUGBITE: + if(sd) + ATK_ADD(30*pc_checkskill(sd, RA_TOOTHOFWUG)); + break; + case SR_GATEOFHELL: + ATK_ADD (sstatus->max_hp - status_get_hp(src)); + if(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE){ + ATK_ADD ( ((int64)sstatus->max_sp * (1 + skill_lv * 2 / 10)) + 40 * status_get_lv(src) ); + }else{ + ATK_ADD ( ((int64)sstatus->sp * (1 + skill_lv * 2 / 10)) + 10 * status_get_lv(src) ); + } + break; + case SR_TIGERCANNON: // (Tiger Cannon skill level x 240) + (Target Base Level x 40) + ATK_ADD( skill_lv * 240 + status_get_lv(target) * 40 ); + if( sc && sc->data[SC_COMBO] + && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) // (Tiger Cannon skill level x 500) + (Target Base Level x 40) + ATK_ADD( skill_lv * 500 + status_get_lv(target) * 40 ); + break; + case SR_FALLENEMPIRE:// [(Target Size value + Skill Level - 1) x Caster STR] + [(Target current weight x Caster DEX / 120)] + ATK_ADD( ((tstatus->size+1)*2 + skill_lv - 1) * sstatus->str); + if( tsd && tsd->weight ){ + ATK_ADD( (tsd->weight/10) * sstatus->dex / 120 ); + }else{ + ATK_ADD( status_get_lv(target) * 50 ); //mobs + } + break; + case KO_SETSUDAN: + if( tsc && tsc->data[SC_SPIRIT] ){ + ATK_ADDRATE(10*tsc->data[SC_SPIRIT]->val1);// +10% custom value. + status_change_end(target,SC_SPIRIT,INVALID_TIMER); + } + break; + case KO_KAIHOU: + if( sd ){ + ARR_FIND(1, 6, i, sd->talisman[i] > 0); + if( i < 5 ){ + s_ele = i; + ATK_ADDRATE(100 * sd->talisman[i]);// +100% custom value. + pc_del_talisman(sd, sd->talisman[i], i); + } + } + break; + } + } + //Div fix. + damage_div_fix(wd.damage, wd.div_); + + //The following are applied on top of current damage and are stackable. + if ( sc ) { + if( sc->data[SC_TRUESIGHT] ) + ATK_ADDRATE(2*sc->data[SC_TRUESIGHT]->val1); + if( sc->data[SC_GLOOMYDAY_SK] && + ( skill_id == LK_SPIRALPIERCE || skill_id == KN_BRANDISHSPEAR || + skill_id == CR_SHIELDBOOMERANG || skill_id == PA_SHIELDCHAIN || + skill_id == LG_SHIELDPRESS ) ) + ATK_ADDRATE(sc->data[SC_GLOOMYDAY_SK]->val2); + if( sc->data[SC_EDP] ){ + switch(skill_id){ + case AS_SPLASHER: case AS_VENOMKNIFE: + case AS_GRIMTOOTH: + break; +#ifndef RENEWAL_EDP + case ASC_BREAKER: case ASC_METEORASSAULT: break; +#else + case AS_SONICBLOW: + case ASC_BREAKER: + case GC_COUNTERSLASH: + case GC_CROSSIMPACT: + ATK_RATE(50); // only modifier is halved but still benefit with the damage bonus +#endif + default: + ATK_ADDRATE(sc->data[SC_EDP]->val3); + } + } + if(sc->data[SC_STYLE_CHANGE]){ + TBL_HOM *hd = BL_CAST(BL_HOM,src); + if (hd) ATK_ADD(hd->homunculus.spiritball * 3); + } + } + + switch (skill_id) { + case AS_SONICBLOW: + if (sc && sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_ASSASIN) + ATK_ADDRATE(map_flag_gvg(src->m)?25:100); //+25% dmg on woe/+100% dmg on nonwoe + + if(sd && pc_checkskill(sd,AS_SONICACCEL)>0) + ATK_ADDRATE(10); + break; + case CR_SHIELDBOOMERANG: + if(sc && sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_CRUSADER) + ATK_ADDRATE(100); + break; + case NC_AXETORNADO: + if( (sstatus->rhw.ele) == ELE_WIND || (sstatus->lhw.ele) == ELE_WIND ) + ATK_ADDRATE(50); + break; + } + + if( sd ) + { + if (skill_id && (i = pc_skillatk_bonus(sd, skill_id))) + ATK_ADDRATE(i); + + if( skill_id != PA_SACRIFICE && skill_id != MO_INVESTIGATE && skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS && skill_id != PA_SHIELDCHAIN && !flag.cri ) + { //Elemental/Racial adjustments + if( sd->right_weapon.def_ratio_atk_ele & (1<<tstatus->def_ele) || + sd->right_weapon.def_ratio_atk_race & (1<<tstatus->race) || + sd->right_weapon.def_ratio_atk_race & (1<<(is_boss(target)?RC_BOSS:RC_NONBOSS)) + ) + flag.pdef = 1; + + if( sd->left_weapon.def_ratio_atk_ele & (1<<tstatus->def_ele) || + sd->left_weapon.def_ratio_atk_race & (1<<tstatus->race) || + sd->left_weapon.def_ratio_atk_race & (1<<(is_boss(target)?RC_BOSS:RC_NONBOSS)) + ) + { //Pass effect onto right hand if configured so. [Skotlex] + if (battle_config.left_cardfix_to_right && flag.rh) + flag.pdef = 1; + else + flag.pdef2 = 1; + } + } + + if (skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS) + { //Ignore Defense? + if (!flag.idef && ( + sd->right_weapon.ignore_def_ele & (1<<tstatus->def_ele) || + sd->right_weapon.ignore_def_race & (1<<tstatus->race) || + sd->right_weapon.ignore_def_race & (is_boss(target)?1<<RC_BOSS:1<<RC_NONBOSS) + )) + flag.idef = 1; + + if (!flag.idef2 && ( + sd->left_weapon.ignore_def_ele & (1<<tstatus->def_ele) || + sd->left_weapon.ignore_def_race & (1<<tstatus->race) || + sd->left_weapon.ignore_def_race & (is_boss(target)?1<<RC_BOSS:1<<RC_NONBOSS) + )) { + if(battle_config.left_cardfix_to_right && flag.rh) //Move effect to right hand. [Skotlex] + flag.idef = 1; + else + flag.idef2 = 1; + } + } + } + + if (!flag.idef || !flag.idef2) + { //Defense reduction + short vit_def; + defType def1 = status_get_def(target); //Don't use tstatus->def1 due to skill timer reductions. + short def2 = tstatus->def2; +#ifdef RENEWAL + if( tsc && tsc->data[SC_ASSUMPTIO] ) + def1 <<= 1; // only eDEF is doubled +#endif + if( sd ) + { + i = sd->ignore_def[is_boss(target)?RC_BOSS:RC_NONBOSS]; + i += sd->ignore_def[tstatus->race]; + if( i ) + { + if( i > 100 ) i = 100; + def1 -= def1 * i / 100; + def2 -= def2 * i / 100; + } + } + + if( sc && sc->data[SC_EXPIATIO] ){ + i = 5 * sc->data[SC_EXPIATIO]->val1; // 5% per level + def1 -= def1 * i / 100; + def2 -= def2 * i / 100; + } + + if( tsc && tsc->data[SC_GT_REVITALIZE] && tsc->data[SC_GT_REVITALIZE]->val4 ) + def2 += 2 * tsc->data[SC_GT_REVITALIZE]->val4; + + if( tsc && tsc->data[SC_CAMOUFLAGE] ){ + i = 5 * (10-tsc->data[SC_CAMOUFLAGE]->val4); + def1 -= def1 * i / 100; + def2 -= def2 * i / 100; + } + + if( battle_config.vit_penalty_type && battle_config.vit_penalty_target&target->type ) { + unsigned char target_count; //256 max targets should be a sane max + target_count = unit_counttargeted(target); + if(target_count >= battle_config.vit_penalty_count) { + if(battle_config.vit_penalty_type == 1) { + if( !tsc || !tsc->data[SC_STEELBODY] ) + def1 = (def1 * (100 - (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num))/100; + } else { //Assume type 2 + if( !tsc || !tsc->data[SC_STEELBODY] ) + def1 -= (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num; + def2 -= (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num; + } + } + if(skill_id == AM_ACIDTERROR) def1 = 0; //Acid Terror ignores only armor defense. [Skotlex] + if(def2 < 1) def2 = 1; + } + //Vitality reduction from rodatazone: http://rodatazone.simgaming.net/mechanics/substats.php#def + if (tsd) //Sd vit-eq + { +#ifndef RENEWAL + //[VIT*0.5] + rnd([VIT*0.3], max([VIT*0.3],[VIT^2/150]-1)) + vit_def = def2*(def2-15)/150; + vit_def = def2/2 + (vit_def>0?rnd()%vit_def:0); +#else + vit_def = def2; +#endif + if((battle_check_undead(sstatus->race,sstatus->def_ele) || sstatus->race==RC_DEMON) && //This bonus already doesnt work vs players + src->type == BL_MOB && (skill=pc_checkskill(tsd,AL_DP)) > 0) + vit_def += skill*(int)(3 +(tsd->status.base_level+1)*0.04); // submitted by orn + if( src->type == BL_MOB && (skill=pc_checkskill(tsd,RA_RANGERMAIN))>0 && + (sstatus->race == RC_BRUTE || sstatus->race == RC_FISH || sstatus->race == RC_PLANT) ) + vit_def += skill*5; +#ifdef RENEWAL + if( skill == NJ_ISSEN ){//TODO: do better implementation if other skills(same func) are found [malufett] + vit_def += def1; + def1 = 0; + } +#endif + } + else { //Mob-Pet vit-eq +#ifndef RENEWAL + //VIT + rnd(0,[VIT/20]^2-1) + vit_def = (def2/20)*(def2/20); + vit_def = def2 + (vit_def>0?rnd()%vit_def:0); +#else + vit_def = def2; +#endif + } + + + if (battle_config.weapon_defense_type) { + vit_def += def1*battle_config.weapon_defense_type; + def1 = 0; + } + #ifdef RENEWAL + /** + * RE DEF Reduction + * Damage = Attack * (4000+eDEF)/(4000+eDEF) - sDEF + * Pierce defence gains 1 atk per def/2 + **/ + + ATK_ADD2( + flag.pdef ?(def1/2):0, + flag.pdef2?(def1/2):0 + ); + if( !flag.idef && !flag.pdef ) + wd.damage = wd.damage * (4000+def1) / (4000+10*def1) - vit_def; + if( flag.lh && !flag.idef2 && !flag.pdef2 ) + wd.damage2 = wd.damage2 * (4000+def1) / (4000+10*def1) - vit_def; + + #else + if (def1 > 100) def1 = 100; + ATK_RATE2( + flag.idef ?100:(flag.pdef ? (int64)flag.pdef*(def1+vit_def) : (100-def1)), + flag.idef2?100:(flag.pdef2? (int64)flag.pdef2*(def1+vit_def) : (100-def1)) + ); + ATK_ADD2( + flag.idef ||flag.pdef ?0:-vit_def, + flag.idef2||flag.pdef2?0:-vit_def + ); + #endif + } + + //Post skill/vit reduction damage increases + if( sc ) + { //SC skill damages + if(sc->data[SC_AURABLADE] +#ifndef RENEWAL + && skill_id != LK_SPIRALPIERCE && skill_id != ML_SPIRALPIERCE +#endif + ){ + int lv = sc->data[SC_AURABLADE]->val1; +#ifdef RENEWAL + lv *= ((skill_id == LK_SPIRALPIERCE || skill_id == ML_SPIRALPIERCE)?wd.div_:1); // +100 per hit in lv 5 +#endif + ATK_ADD(20*lv); + } + + if(sc->data[SC_GN_CARTBOOST]) + ATK_ADD(10*sc->data[SC_GN_CARTBOOST]->val1); + + if(sc->data[SC_GT_CHANGE] && sc->data[SC_GT_CHANGE]->val2){ + struct block_list *bl; // ATK increase: ATK [{(Caster DEX / 4) + (Caster STR / 2)} x Skill Level / 5] + if( (bl = map_id2bl(sc->data[SC_GT_CHANGE]->val2)) ) + ATK_ADD( ( status_get_dex(bl)/4 + status_get_str(bl)/2 ) * sc->data[SC_GT_CHANGE]->val1 / 5 ); + } + + if(sc->data[SC_CAMOUFLAGE]) + ATK_ADD(30 * (10-sc->data[SC_CAMOUFLAGE]->val4) ); + } + + //Refine bonus + if( sd && flag.weapon && skill_id != MO_INVESTIGATE && skill_id != MO_EXTREMITYFIST ) + { // Counts refine bonus multiple times + if( skill_id == MO_FINGEROFFENSIVE ) + { + ATK_ADD2(wd.div_*sstatus->rhw.atk2, wd.div_*sstatus->lhw.atk2); + } else { + ATK_ADD2(sstatus->rhw.atk2, sstatus->lhw.atk2); + } + } + + //Set to min of 1 + if (flag.rh && wd.damage < 1) wd.damage = 1; + if (flag.lh && wd.damage2 < 1) wd.damage2 = 1; + + if (sd && flag.weapon && + skill_id != MO_INVESTIGATE && + skill_id != MO_EXTREMITYFIST && + skill_id != CR_GRANDCROSS) + { //Add mastery damage + if(skill_id != ASC_BREAKER && sd->status.weapon == W_KATAR && + (skill=pc_checkskill(sd,ASC_KATAR)) > 0) + { //Adv Katar Mastery is does not applies to ASC_BREAKER, + // but other masteries DO apply >_> + ATK_ADDRATE(10+ 2*skill); + } + + wd.damage = battle_addmastery(sd,target,wd.damage,0); + if (flag.lh) + wd.damage2 = battle_addmastery(sd,target,wd.damage2,1); + + if (sc && sc->data[SC_MIRACLE]) i = 2; //Star anger + else + ARR_FIND(0, MAX_PC_FEELHATE, i, t_class == sd->hate_mob[i]); + if (i < MAX_PC_FEELHATE && (skill=pc_checkskill(sd,sg_info[i].anger_id))) + { + skillratio = sd->status.base_level + sstatus->dex + sstatus->luk; + if (i == 2) skillratio += sstatus->str; //Star Anger + if (skill<4) + skillratio /= 12-3*skill; + ATK_ADDRATE(skillratio); + } + if (skill_id == NJ_SYURIKEN && (skill = pc_checkskill(sd,NJ_TOBIDOUGU)) > 0) + ATK_ADD(3*skill); + if (skill_id == NJ_KUNAI) + ATK_ADD(60); + } + } //Here ends flag.hit section, the rest of the function applies to both hitting and missing attacks + else if(wd.div_ < 0) //Since the attack missed... + wd.div_ *= -1; + + if(sd && (skill=pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0) + ATK_ADD(skill*2); + + if(skill_id==TF_POISON) + ATK_ADD(15*skill_lv); + + if( !(nk&NK_NO_ELEFIX) && !n_ele ) + { //Elemental attribute fix + if( wd.damage > 0 ) + { + wd.damage=battle_attr_fix(src,target,wd.damage,s_ele,tstatus->def_ele, tstatus->ele_lv); + if( skill_id == MC_CARTREVOLUTION ) //Cart Revolution applies the element fix once more with neutral element + wd.damage = battle_attr_fix(src,target,wd.damage,ELE_NEUTRAL,tstatus->def_ele, tstatus->ele_lv); + if( skill_id== GS_GROUNDDRIFT ) //Additional 50*lv Neutral damage. + wd.damage += battle_attr_fix(src,target,50*skill_lv,ELE_NEUTRAL,tstatus->def_ele, tstatus->ele_lv); + } + if( flag.lh && wd.damage2 > 0 ) + wd.damage2 = battle_attr_fix(src,target,wd.damage2,s_ele_,tstatus->def_ele, tstatus->ele_lv); + if( sc && sc->data[SC_WATK_ELEMENT] ) + { // Descriptions indicate this means adding a percent of a normal attack in another element. [Skotlex] + int damage = battle_calc_base_damage(sstatus, &sstatus->rhw, sc, tstatus->size, sd, (flag.arrow?2:0)) * sc->data[SC_WATK_ELEMENT]->val2 / 100; + wd.damage += battle_attr_fix(src, target, damage, sc->data[SC_WATK_ELEMENT]->val1, tstatus->def_ele, tstatus->ele_lv); + + if( flag.lh ) + { + damage = battle_calc_base_damage(sstatus, &sstatus->lhw, sc, tstatus->size, sd, (flag.arrow?2:0)) * sc->data[SC_WATK_ELEMENT]->val2 / 100; + wd.damage2 += battle_attr_fix(src, target, damage, sc->data[SC_WATK_ELEMENT]->val1, tstatus->def_ele, tstatus->ele_lv); + } + } + #ifdef RENEWAL + /** + * In RE Shield Bommerang takes weapon element only for damage calculation, + * - resist calculation is always against neutral + **/ + if ( skill_id == CR_SHIELDBOOMERANG ) + s_ele = s_ele_ = ELE_NEUTRAL; + #endif + } + + if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS) + return wd; //Enough, rest is not needed. + + if (sd) + { + if (skill_id != CR_SHIELDBOOMERANG) //Only Shield boomerang doesn't takes the Star Crumbs bonus. + ATK_ADD2(wd.div_*sd->right_weapon.star, wd.div_*sd->left_weapon.star); + if (skill_id==MO_FINGEROFFENSIVE) { //The finger offensive spheres on moment of attack do count. [Skotlex] + ATK_ADD(wd.div_*sd->spiritball_old*3); + } else { + ATK_ADD(wd.div_*sd->spiritball*3); + } + + //Card Fix, sd side + wd.damage = battle_calc_cardfix(BF_WEAPON, src, target, nk, s_ele, s_ele_, wd.damage, 2, wd.flag); + if( flag.lh ) + wd.damage2 = battle_calc_cardfix(BF_WEAPON, src, target, nk, s_ele, s_ele_, wd.damage2, 3, wd.flag); + + if( skill_id == CR_SHIELDBOOMERANG || skill_id == PA_SHIELDCHAIN ) + { //Refine bonus applies after cards and elements. + short index= sd->equip_index[EQI_HAND_L]; + if( index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR ) + ATK_ADD(10*sd->status.inventory[index].refine); + } + } //if (sd) + + //Card Fix, tsd side + if(tsd) + wd.damage = battle_calc_cardfix(BF_WEAPON, src, target, nk, s_ele, s_ele_, wd.damage, flag.lh, wd.flag); + + if( flag.infdef ) + { //Plants receive 1 damage when hit + short class_ = status_get_class(target); + if( flag.hit || wd.damage > 0 ) + wd.damage = wd.div_; // In some cases, right hand no need to have a weapon to increase damage + if( flag.lh && (flag.hit || wd.damage2 > 0) ) + wd.damage2 = wd.div_; + if( flag.hit && class_ == MOBID_EMPERIUM ) { + if(wd.damage2 > 0) { + wd.damage2 = battle_attr_fix(src,target,wd.damage2,s_ele_,tstatus->def_ele, tstatus->ele_lv); + wd.damage2 = battle_calc_gvg_damage(src,target,wd.damage2,wd.div_,skill_id,skill_lv,wd.flag); + } + else if(wd.damage > 0) { + wd.damage = battle_attr_fix(src,target,wd.damage,s_ele_,tstatus->def_ele, tstatus->ele_lv); + wd.damage = battle_calc_gvg_damage(src,target,wd.damage,wd.div_,skill_id,skill_lv,wd.flag); + } + return wd; + } + if( !(battle_config.skill_min_damage&1) ) + //Do not return if you are supposed to deal greater damage to plants than 1. [Skotlex] + return wd; + } + + if (sd) { + if (!flag.rh && flag.lh) { //Move lh damage to the rh + wd.damage = wd.damage2; + wd.damage2 = 0; + flag.rh=1; + flag.lh=0; + } else if(flag.rh && flag.lh) { //Dual-wield + if (wd.damage) { + if( (skill = pc_checkskill(sd,AS_RIGHT)) ) + ATK_RATER(50 + (skill * 10)) + else if( (skill = pc_checkskill(sd,KO_RIGHT)) ) + ATK_RATER(70 + (skill * 10)) + if(wd.damage < 1) wd.damage = 1; + } + if (wd.damage2) { + if( (skill = pc_checkskill(sd,AS_LEFT)) ) + ATK_RATEL(30 + (skill * 10)) + else if( (skill = pc_checkskill(sd,KO_LEFT)) ) + ATK_RATEL(50 + (skill * 10)) + if(wd.damage2 < 1) wd.damage2 = 1; + } + } else if(sd->status.weapon == W_KATAR && !skill_id) { //Katars (offhand damage only applies to normal attacks, tested on Aegis 10.2) + skill = pc_checkskill(sd,TF_DOUBLE); + wd.damage2 = (int64)wd.damage * (1 + (skill * 2))/100; + + if(wd.damage && !wd.damage2) wd.damage2 = 1; + flag.lh = 1; + } + } + + if(!flag.rh && wd.damage) + wd.damage=0; + + if(!flag.lh && wd.damage2) + wd.damage2=0; + + if( wd.damage + wd.damage2 ) + { //There is a total damage value + if(!wd.damage2) + { + wd.damage = battle_calc_damage(src,target,&wd,wd.damage,skill_id,skill_lv); + if( map_flag_gvg2(target->m) ) + wd.damage=battle_calc_gvg_damage(src,target,wd.damage,wd.div_,skill_id,skill_lv,wd.flag); + else if( map[target->m].flag.battleground ) + wd.damage=battle_calc_bg_damage(src,target,wd.damage,wd.div_,skill_id,skill_lv,wd.flag); + } + else if(!wd.damage) + { + wd.damage2 = battle_calc_damage(src,target,&wd,wd.damage2,skill_id,skill_lv); + if( map_flag_gvg2(target->m) ) + wd.damage2 = battle_calc_gvg_damage(src,target,wd.damage2,wd.div_,skill_id,skill_lv,wd.flag); + else if( map[target->m].flag.battleground ) + wd.damage = battle_calc_bg_damage(src,target,wd.damage2,wd.div_,skill_id,skill_lv,wd.flag); + } + else + { + int d1 = wd.damage + wd.damage2,d2 = wd.damage2; + wd.damage = battle_calc_damage(src,target,&wd,d1,skill_id,skill_lv); + if( map_flag_gvg2(target->m) ) + wd.damage = battle_calc_gvg_damage(src,target,wd.damage,wd.div_,skill_id,skill_lv,wd.flag); + else if( map[target->m].flag.battleground ) + wd.damage = battle_calc_bg_damage(src,target,wd.damage,wd.div_,skill_id,skill_lv,wd.flag); + wd.damage2 = (int64)d2*100/d1 * wd.damage/100; + if(wd.damage > 1 && wd.damage2 < 1) wd.damage2 = 1; + wd.damage-=wd.damage2; + } + } + //Reject Sword bugreport:4493 by Daegaladh + if(wd.damage && tsc && tsc->data[SC_REJECTSWORD] && + (src->type!=BL_PC || ( + ((TBL_PC *)src)->weapontype1 == W_DAGGER || + ((TBL_PC *)src)->weapontype1 == W_1HSWORD || + ((TBL_PC *)src)->status.weapon == W_2HSWORD + )) && + rnd()%100 < tsc->data[SC_REJECTSWORD]->val2 + ) { + ATK_RATER(50) + status_fix_damage(target,src,wd.damage,clif_damage(target,src,gettick(),0,0,wd.damage,0,0,0)); + clif_skill_nodamage(target,target,ST_REJECTSWORD,tsc->data[SC_REJECTSWORD]->val1,1); + if( --(tsc->data[SC_REJECTSWORD]->val3) <= 0 ) + status_change_end(target, SC_REJECTSWORD, INVALID_TIMER); + } + if(skill_id == ASC_BREAKER) { //Breaker's int-based damage (a misc attack?) + struct Damage md = battle_calc_misc_attack(src, target, skill_id, skill_lv, wflag); + wd.damage += md.damage; + } + if( sc ) { + //SG_FUSION hp penalty [Komurka] + if (sc->data[SC_FUSION]) { + int hp= sstatus->max_hp; + if (sd && tsd) { + hp = 8*hp/100; + if (((int64)sstatus->hp * 100) <= ((int64)sstatus->max_hp * 20)) + hp = sstatus->hp; + } else + hp = 2*hp/100; //2% hp loss per hit + status_zap(src, hp, 0); + } + /** + * affecting non-skills + **/ + if( !skill_id ) { + /** + * RK Enchant Blade + **/ + if( sc->data[SC_ENCHANTBLADE] && sd && ( (flag.rh && sd->weapontype1) || (flag.lh && sd->weapontype2) ) ) { + //[( ( Skill Lv x 20 ) + 100 ) x ( casterBaseLevel / 150 )] + casterInt + ATK_ADD( ( sc->data[SC_ENCHANTBLADE]->val1*20+100 ) * status_get_lv(src) / 150 + status_get_int(src) ); + } + } + status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER); + } + if( skill_id == LG_RAYOFGENESIS ) { + struct Damage md = battle_calc_magic_attack(src, target, skill_id, skill_lv, wflag); + wd.damage += md.damage; + } + + return wd; +} + +/*========================================== + * battle_calc_magic_attack [DracoRPG] + *------------------------------------------*/ +struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag) +{ + int i, nk; + short s_ele = 0; + unsigned int skillratio = 100; //Skill dmg modifiers. + + TBL_PC *sd; +// TBL_PC *tsd; + struct status_change *sc, *tsc; + struct Damage ad; + struct status_data *sstatus = status_get_status_data(src); + struct status_data *tstatus = status_get_status_data(target); + struct { + unsigned imdef : 1; + unsigned infdef : 1; + } flag; + + memset(&ad,0,sizeof(ad)); + memset(&flag,0,sizeof(flag)); + + if(src==NULL || target==NULL) + { + nullpo_info(NLP_MARK); + return ad; + } + //Initial Values + ad.damage = 1; + ad.div_=skill_get_num(skill_id,skill_lv); + ad.amotion=skill_get_inf(skill_id)&INF_GROUND_SKILL?0:sstatus->amotion; //Amotion should be 0 for ground skills. + ad.dmotion=tstatus->dmotion; + ad.blewcount = skill_get_blewcount(skill_id,skill_lv); + ad.flag=BF_MAGIC|BF_SKILL; + ad.dmg_lv=ATK_DEF; + nk = skill_get_nk(skill_id); + flag.imdef = nk&NK_IGNORE_DEF?1:0; + + sd = BL_CAST(BL_PC, src); +// tsd = BL_CAST(BL_PC, target); + sc = status_get_sc(src); + tsc = status_get_sc(target); + + //Initialize variables that will be used afterwards + s_ele = skill_get_ele(skill_id, skill_lv); + + if (s_ele == -1){ // pl=-1 : the skill takes the weapon's element + s_ele = sstatus->rhw.ele; + if( sd ){ //Summoning 10 talisman will endow your weapon + ARR_FIND(1, 6, i, sd->talisman[i] >= 10); + if( i < 5 ) s_ele = i; + } + }else if (s_ele == -2) //Use status element + s_ele = status_get_attack_sc_element(src,status_get_sc(src)); + else if( s_ele == -3 ) //Use random element + s_ele = rnd()%ELE_MAX; + + if( skill_id == SO_PSYCHIC_WAVE ) { + if( sc && sc->count ) { + if( sc->data[SC_HEATER_OPTION] ) s_ele = sc->data[SC_HEATER_OPTION]->val4; + else if( sc->data[SC_COOLER_OPTION] ) s_ele = sc->data[SC_COOLER_OPTION]->val4; + else if( sc->data[SC_BLAST_OPTION] ) s_ele = sc->data[SC_BLAST_OPTION]->val3; + else if( sc->data[SC_CURSED_SOIL_OPTION] ) s_ele = sc->data[SC_CURSED_SOIL_OPTION]->val4; + } + } + + //Set miscellaneous data that needs be filled + if(sd) { + sd->state.arrow_atk = 0; + ad.blewcount += battle_blewcount_bonus(sd, skill_id); + } + + //Skill Range Criteria + ad.flag |= battle_range_type(src, target, skill_id, skill_lv); + flag.infdef=(tstatus->mode&MD_PLANT?1:0); + if( target->type == BL_SKILL){ + TBL_SKILL *su = (TBL_SKILL*)target; + if( su->group && (su->group->skill_id == WM_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD) ) + flag.infdef = 1; + } + + switch(skill_id) + { + case MG_FIREWALL: + case NJ_KAENSIN: + ad.dmotion = 0; //No flinch animation. + if ( tstatus->def_ele == ELE_FIRE || battle_check_undead(tstatus->race, tstatus->def_ele) ) + ad.blewcount = 0; //No knockback + break; + case PR_SANCTUARY: + ad.dmotion = 0; //No flinch animation. + break; + } + + if (!flag.infdef) //No need to do the math for plants + { +#ifdef RENEWAL + ad.damage = 0; //reinitialize.. +#endif +//MATK_RATE scales the damage. 100 = no change. 50 is halved, 200 is doubled, etc +#define MATK_RATE( a ) { ad.damage= ad.damage*(a)/100; } +//Adds dmg%. 100 = +100% (double) damage. 10 = +10% damage +#define MATK_ADDRATE( a ) { ad.damage+= ad.damage*(a)/100; } +//Adds an absolute value to damage. 100 = +100 damage +#define MATK_ADD( a ) { ad.damage+= a; } + + switch (skill_id) + { //Calc base damage according to skill + case AL_HEAL: + case PR_BENEDICTIO: + case PR_SANCTUARY: + /** + * Arch Bishop + **/ + case AB_HIGHNESSHEAL: + ad.damage = skill_calc_heal(src, target, skill_id, skill_lv, false); + break; + case PR_ASPERSIO: + ad.damage = 40; + break; + case ALL_RESURRECTION: + case PR_TURNUNDEAD: + //Undead check is on skill_castend_damageid code. + #ifdef RENEWAL + i = 10*skill_lv + sstatus->luk + sstatus->int_ + status_get_lv(src) + + 300 - 300*tstatus->hp/tstatus->max_hp; + #else + i = 20*skill_lv + sstatus->luk + sstatus->int_ + status_get_lv(src) + + 200 - 200*tstatus->hp/tstatus->max_hp; + #endif + if(i > 700) i = 700; + if(rnd()%1000 < i && !(tstatus->mode&MD_BOSS)) + ad.damage = tstatus->hp; + else { + #ifdef RENEWAL + if (sstatus->matk_max > sstatus->matk_min) { + MATK_ADD(sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min)); + } else { + MATK_ADD(sstatus->matk_min); + } + MATK_RATE(skill_lv); + #else + ad.damage = status_get_lv(src) + sstatus->int_ + skill_lv * 10; + #endif + } + break; + case PF_SOULBURN: + ad.damage = tstatus->sp * 2; + break; + /** + * Arch Bishop + **/ + case AB_RENOVATIO: + //Damage calculation from iRO wiki. [Jobbie] + ad.damage = (int)((15 * status_get_lv(src)) + (1.5 * sstatus->int_)); + break; + default: { + if (sstatus->matk_max > sstatus->matk_min) { + MATK_ADD(sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min)); + } else { + MATK_ADD(sstatus->matk_min); + } + + if (nk&NK_SPLASHSPLIT) { // Divide MATK in case of multiple targets skill + if(mflag>0) + ad.damage/= mflag; + else + ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id)); + } + + switch(skill_id){ + case MG_NAPALMBEAT: + skillratio += skill_lv*10-30; + break; + case MG_FIREBALL: + #ifdef RENEWAL + skillratio += 20*skill_lv; + #else + skillratio += skill_lv*10-30; + #endif + break; + case MG_SOULSTRIKE: + if (battle_check_undead(tstatus->race,tstatus->def_ele)) + skillratio += 5*skill_lv; + break; + case MG_FIREWALL: + skillratio -= 50; + break; + case MG_FIREBOLT: + case MG_COLDBOLT: + case MG_LIGHTNINGBOLT: + if ( sc && sc->data[SC_SPELLFIST] && mflag&BF_SHORT ) { + skillratio += (sc->data[SC_SPELLFIST]->val4 * 100) + (sc->data[SC_SPELLFIST]->val2 * 100) - 100;// val4 = used bolt level, val2 = used spellfist level. [Rytech] + ad.div_ = 1;// ad mods, to make it work similar to regular hits [Xazax] + ad.flag = BF_WEAPON|BF_SHORT; + ad.type = 0; + } + break; + case MG_THUNDERSTORM: + /** + * in Renewal Thunder Storm boost is 100% (in pre-re, 80%) + **/ + #ifndef RENEWAL + skillratio -= 20; + #endif + break; + case MG_FROSTDIVER: + skillratio += 10*skill_lv; + break; + case AL_HOLYLIGHT: + skillratio += 25; + if (sd && sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_PRIEST) + skillratio *= 5; //Does 5x damage include bonuses from other skills? + break; + case AL_RUWACH: + skillratio += 45; + break; + case WZ_FROSTNOVA: + skillratio += (100+skill_lv*10)*2/3-100; + break; + case WZ_FIREPILLAR: + if (skill_lv > 10) + skillratio += 100; + else + skillratio -= 80; + break; + case WZ_SIGHTRASHER: + skillratio += 20*skill_lv; + break; + case WZ_WATERBALL: + skillratio += 30*skill_lv; + break; + case WZ_STORMGUST: + skillratio += 40*skill_lv; + break; + case HW_NAPALMVULCAN: + skillratio += 10*skill_lv-30; + break; + case SL_STIN: + skillratio += (tstatus->size!=SZ_SMALL?-99:10*skill_lv); //target size must be small (0) for full damage. + break; + case SL_STUN: + skillratio += (tstatus->size!=SZ_BIG?5*skill_lv:-99); //Full damage is dealt on small/medium targets + break; + case SL_SMA: + skillratio += -60 + status_get_lv(src); //Base damage is 40% + lv% + break; + case NJ_KOUENKA: + skillratio -= 10; + break; + case NJ_KAENSIN: + skillratio -= 50; + break; + case NJ_BAKUENRYU: + skillratio += 50*(skill_lv-1); + break; + case NJ_HYOUSYOURAKU: + skillratio += 50*skill_lv; + break; + case NJ_RAIGEKISAI: + skillratio += 60 + 40*skill_lv; + break; + case NJ_KAMAITACHI: + case NPC_ENERGYDRAIN: + skillratio += 100*skill_lv; + break; + case NPC_EARTHQUAKE: + skillratio += 100 +100*skill_lv +100*(skill_lv/2); + break; + #ifdef RENEWAL + case WZ_HEAVENDRIVE: + case WZ_METEOR: + skillratio += 25; + break; + case WZ_VERMILION: + { + int interval = 0, per = interval, ratio = per; + while( (per++) < skill_lv ){ + ratio += interval; + if(per%3==0) interval += 20; + } + if( skill_lv > 9 ) + ratio -= 10; + skillratio += ratio; + } + break; + case NJ_HUUJIN: + skillratio += 50; + break; + #else + case WZ_VERMILION: + skillratio += 20*skill_lv-20; + break; + #endif + /** + * Arch Bishop + **/ + case AB_JUDEX: + skillratio += 180 + 20 * skill_lv; + if (skill_lv > 4) skillratio += 20; + RE_LVL_DMOD(100); + break; + case AB_ADORAMUS: + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case AB_DUPLELIGHT_MAGIC: + skillratio += 100 + 20 * skill_lv; + break; + /** + * Warlock + **/ + case WL_SOULEXPANSION: + skillratio += 300 + 100 * skill_lv + sstatus->int_; + RE_LVL_DMOD(100); + break; + case WL_FROSTMISTY: + skillratio += 100 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_JACKFROST: + if( tsc && tsc->data[SC_FREEZING] ){ + skillratio += 900 + 300 * skill_lv; + RE_LVL_DMOD(100); + }else{ + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(150); + } + break; + case WL_DRAINLIFE: + skillratio = 200 * skill_lv + sstatus->int_; + RE_LVL_DMOD(100); + break; + case WL_CRIMSONROCK: + skillratio += 1200 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_HELLINFERNO: + skillratio = 300 * skill_lv; + RE_LVL_DMOD(100); + // Shadow: MATK [{( Skill Level x 300 ) x ( Caster Base Level / 100 ) x 4/5 }] % + // Fire : MATK [{( Skill Level x 300 ) x ( Caster Base Level / 100 ) /5 }] % + if( mflag&ELE_DARK ){ skillratio *= 4; s_ele = ELE_DARK; } + skillratio /= 5; + break; + case WL_COMET: { + struct status_change * sc = status_get_sc(src); + if( sc ) + i = distance_xy(target->x, target->y, sc->comet_x, sc->comet_y); + else + i = 8; + if( i < 2 ) skillratio = 2500 + 500 * skill_lv; + else + if( i < 4 ) skillratio = 1600 + 400 * skill_lv; + else + if( i < 6 ) skillratio = 1200 + 300 * skill_lv; + else + skillratio = 800 + 200 * skill_lv; + } + break; + case WL_CHAINLIGHTNING_ATK: + skillratio += 100 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_EARTHSTRAIN: + skillratio += 1900 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_TETRAVORTEX_FIRE: + case WL_TETRAVORTEX_WATER: + case WL_TETRAVORTEX_WIND: + case WL_TETRAVORTEX_GROUND: + skillratio += 400 + 500 * skill_lv; + break; + case WL_SUMMON_ATK_FIRE: + case WL_SUMMON_ATK_WATER: + case WL_SUMMON_ATK_WIND: + case WL_SUMMON_ATK_GROUND: + skillratio = skill_lv * (status_get_lv(src) + ( sd ? sd->status.job_level : 50 ));// This is close to official, but lacking a little info to finalize. [Rytech] + RE_LVL_DMOD(100); + break; + case LG_RAYOFGENESIS: + { + int16 lv = skill_lv; + int bandingBonus = 0; + if( sc && sc->data[SC_BANDING] ) + bandingBonus = 200 * (sd ? skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),0) : 1); + skillratio = ((300 * skill_lv) + bandingBonus) * (sd ? sd->status.job_level : 1) / 25; + } + break; + case LG_SHIELDSPELL:// [(Casters Base Level x 4) + (Shield MDEF x 100) + (Casters INT x 2)] % + if( sd ) { + skillratio = status_get_lv(src) * 4 + sd->bonus.shieldmdef * 100 + status_get_int(src) * 2; + } else + skillratio += 1900; //2000% + break; + case WM_METALICSOUND: + skillratio += 120 * skill_lv + 60 * ( sd? pc_checkskill(sd, WM_LESSON) : 10 ) - 100; + break; + /*case WM_SEVERE_RAINSTORM: + skillratio += 50 * skill_lv; + break; + + WM_SEVERE_RAINSTORM just set a unit place, + refer to WM_SEVERE_RAINSTORM_MELEE to set the formula. + */ + case WM_REVERBERATION_MAGIC: + // MATK [{(Skill Level x 100) + 100} x Casters Base Level / 100] % + skillratio += 100 * (sd ? pc_checkskill(sd, WM_REVERBERATION) : 1); + RE_LVL_DMOD(100); + break; + case SO_FIREWALK: + skillratio = 300; + RE_LVL_DMOD(100); + if( sc && sc->data[SC_HEATER_OPTION] ) + skillratio += sc->data[SC_HEATER_OPTION]->val3; + break; + case SO_ELECTRICWALK: + skillratio = 300; + RE_LVL_DMOD(100); + if( sc && sc->data[SC_BLAST_OPTION] ) + skillratio += sd ? sd->status.job_level / 2 : 0; + break; + case SO_EARTHGRAVE: + skillratio = ( 200 * ( sd ? pc_checkskill(sd, SA_SEISMICWEAPON) : 10 ) + sstatus->int_ * skill_lv ); + RE_LVL_DMOD(100); + if( sc && sc->data[SC_CURSED_SOIL_OPTION] ) + skillratio += sc->data[SC_CURSED_SOIL_OPTION]->val2; + break; + case SO_DIAMONDDUST: + skillratio = ( 200 * ( sd ? pc_checkskill(sd, SA_FROSTWEAPON) : 10 ) + sstatus->int_ * skill_lv ); + RE_LVL_DMOD(100); + if( sc && sc->data[SC_COOLER_OPTION] ) + skillratio += sc->data[SC_COOLER_OPTION]->val3; + break; + case SO_POISON_BUSTER: + skillratio += 1100 + 300 * skill_lv; + if( sc && sc->data[SC_CURSED_SOIL_OPTION] ) + skillratio += sc->data[SC_CURSED_SOIL_OPTION]->val2; + break; + case SO_PSYCHIC_WAVE: + skillratio += -100 + skill_lv * 70 + (sstatus->int_ * 3); + RE_LVL_DMOD(100); + if( sc ){ + if( sc->data[SC_HEATER_OPTION] ) + skillratio += sc->data[SC_HEATER_OPTION]->val3; + else if(sc->data[SC_COOLER_OPTION] ) + skillratio += sc->data[SC_COOLER_OPTION]->val3; + else if(sc->data[SC_BLAST_OPTION] ) + skillratio += sc->data[SC_BLAST_OPTION]->val2; + else if(sc->data[SC_CURSED_SOIL_OPTION] ) + skillratio += sc->data[SC_CURSED_SOIL_OPTION]->val3; + } + break; + case SO_VARETYR_SPEAR: //MATK [{( Endow Tornado skill level x 50 ) + ( Caster INT x Varetyr Spear Skill level )} x Caster Base Level / 100 ] % + skillratio = status_get_int(src) * skill_lv + ( sd ? pc_checkskill(sd, SA_LIGHTNINGLOADER) * 50 : 0 ); + RE_LVL_DMOD(100); + if( sc && sc->data[SC_BLAST_OPTION] ) + skillratio += sd ? sd->status.job_level * 5 : 0; + break; + case SO_CLOUD_KILL: + skillratio += -100 + skill_lv * 40; + RE_LVL_DMOD(100); + if( sc && sc->data[SC_CURSED_SOIL_OPTION] ) + skillratio += sc->data[SC_CURSED_SOIL_OPTION]->val2; + break; + case GN_DEMONIC_FIRE: + if( skill_lv > 20) + { // Fire expansion Lv.2 + skillratio += 110 + 20 * (skill_lv - 20) + status_get_int(src) * 3; // Need official INT bonus. [LimitLine] + } + else if( skill_lv > 10 ) + { // Fire expansion Lv.1 + skillratio += 110 + 20 * (skill_lv - 10) / 2; + } + else + skillratio += 110 + 20 * skill_lv; + break; + // Magical Elemental Spirits Attack Skills + case EL_FIRE_MANTLE: + case EL_WATER_SCREW: + skillratio += 900; + break; + case EL_FIRE_ARROW: + case EL_ROCK_CRUSHER_ATK: + skillratio += 200; + break; + case EL_FIRE_BOMB: + case EL_ICE_NEEDLE: + case EL_HURRICANE_ATK: + skillratio += 400; + break; + case EL_FIRE_WAVE: + case EL_TYPOON_MIS_ATK: + skillratio += 1100; + break; + case MH_ERASER_CUTTER: + if(skill_lv%2) skillratio += 400; //600:800:1000 + else skillratio += 700; //1000:1200 + skillratio += 100 * skill_lv; + break; + case MH_XENO_SLASHER: + if(skill_lv%2) skillratio += 350 + 50 * skill_lv; //500:600:700 + else skillratio += 400 + 100 * skill_lv; //700:900 + break; + case MH_HEILIGE_STANGE: + skillratio += 400 + 250 * skill_lv; + break; + case MH_POISON_MIST: + skillratio += 100 * skill_lv; + break; + } + + MATK_RATE(skillratio); + + //Constant/misc additions from skills + if (skill_id == WZ_FIREPILLAR) + MATK_ADD(50); + } + } +#ifdef RENEWAL + ad.damage = battle_calc_cardfix(BF_MAGIC, src, target, nk, s_ele, 0, ad.damage, 0, ad.flag); +#endif + if(sd) { + //Damage bonuses + if ((i = pc_skillatk_bonus(sd, skill_id))) + ad.damage += (int64)ad.damage*i/100; + + //Ignore Defense? + if (!flag.imdef && ( + sd->bonus.ignore_mdef_ele & ( 1 << tstatus->def_ele ) || + sd->bonus.ignore_mdef_race & ( 1 << tstatus->race ) || + sd->bonus.ignore_mdef_race & ( is_boss(target) ? 1 << RC_BOSS : 1 << RC_NONBOSS ) + )) + flag.imdef = 1; + } + + if(!flag.imdef){ + defType mdef = tstatus->mdef; + int mdef2= tstatus->mdef2; +#ifdef RENEWAL + if(tsc && tsc->data[SC_ASSUMPTIO]) + mdef <<= 1; // only eMDEF is doubled +#endif + if(sd) { + i = sd->ignore_mdef[is_boss(target)?RC_BOSS:RC_NONBOSS]; + i+= sd->ignore_mdef[tstatus->race]; + if (i) + { + if (i > 100) i = 100; + mdef -= mdef * i/100; + //mdef2-= mdef2* i/100; + } + } + #ifdef RENEWAL + /** + * RE MDEF Reduction + * Damage = Magic Attack * (1000+eMDEF)/(1000+eMDEF) - sMDEF + **/ + ad.damage = ad.damage * (1000 + mdef) / (1000 + mdef * 10) - mdef2; + #else + if(battle_config.magic_defense_type) + ad.damage = ad.damage - mdef*battle_config.magic_defense_type - mdef2; + else + ad.damage = ad.damage * (100-mdef)/100 - mdef2; + #endif + } + + if (skill_id == NPC_EARTHQUAKE) + { //Adds atk2 to the damage, should be influenced by number of hits and skill-ratio, but not mdef reductions. [Skotlex] + //Also divide the extra bonuses from atk2 based on the number in range [Kevin] + if(mflag>0) + ad.damage+= (sstatus->rhw.atk2*skillratio/100)/mflag; + else + ShowError("Zero range by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id)); + } + + if(ad.damage<1) + ad.damage=1; + else if(sc){//only applies when hit + // TODO: there is another factor that contribute with the damage and need to be formulated. [malufett] + switch(skill_id){ + case MG_LIGHTNINGBOLT: + case MG_THUNDERSTORM: + case MG_FIREBOLT: + case MG_FIREWALL: + case MG_COLDBOLT: + case MG_FROSTDIVER: + case WZ_EARTHSPIKE: + case WZ_HEAVENDRIVE: + if(sc->data[SC_GUST_OPTION] || sc->data[SC_PETROLOGY_OPTION] + || sc->data[SC_PYROTECHNIC_OPTION] || sc->data[SC_AQUAPLAY_OPTION]) + ad.damage += (6 + sstatus->int_/4) + max(sstatus->dex-10,0)/30; + break; + } + } + + if (!(nk&NK_NO_ELEFIX)) + ad.damage=battle_attr_fix(src, target, ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv); + + if( skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS ) + { //Apply the physical part of the skill's damage. [Skotlex] + struct Damage wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag); + ad.damage = battle_attr_fix(src, target, wd.damage + ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv) * (100 + 40*skill_lv)/100; + if( src == target ) + { + if( src->type == BL_PC ) + ad.damage = ad.damage/2; + else + ad.damage = 0; + } + } + +#ifndef RENEWAL + ad.damage = battle_calc_cardfix(BF_MAGIC, src, target, nk, s_ele, 0, ad.damage, 0, ad.flag); +#endif + } + + damage_div_fix(ad.damage, ad.div_); + + if (flag.infdef && ad.damage) + ad.damage = ad.damage>0?1:-1; + + ad.damage=battle_calc_damage(src,target,&ad,ad.damage,skill_id,skill_lv); + if( map_flag_gvg2(target->m) ) + ad.damage=battle_calc_gvg_damage(src,target,ad.damage,ad.div_,skill_id,skill_lv,ad.flag); + else if( map[target->m].flag.battleground ) + ad.damage=battle_calc_bg_damage(src,target,ad.damage,ad.div_,skill_id,skill_lv,ad.flag); + + switch( skill_id ) { /* post-calc modifiers */ + case SO_VARETYR_SPEAR: { // Physical damage. + struct Damage wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag); + if(!flag.infdef && ad.damage > 1) + ad.damage += wd.damage; + break; + } + //case HM_ERASER_CUTTER: + } + + return ad; +} + +/*========================================== + * Calculate Misc dammage for skill_id + *------------------------------------------*/ +struct Damage battle_calc_misc_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag) +{ + int skill; + short i, nk; + short s_ele; + + struct map_session_data *sd, *tsd; + struct Damage md; //DO NOT CONFUSE with md of mob_data! + struct status_data *sstatus = status_get_status_data(src); + struct status_data *tstatus = status_get_status_data(target); + + memset(&md,0,sizeof(md)); + + if( src == NULL || target == NULL ){ + nullpo_info(NLP_MARK); + return md; + } + + //Some initial values + md.amotion=skill_get_inf(skill_id)&INF_GROUND_SKILL?0:sstatus->amotion; + md.dmotion=tstatus->dmotion; + md.div_=skill_get_num( skill_id,skill_lv ); + md.blewcount=skill_get_blewcount(skill_id,skill_lv); + md.dmg_lv=ATK_DEF; + md.flag=BF_MISC|BF_SKILL; + + nk = skill_get_nk(skill_id); + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + + if(sd) { + sd->state.arrow_atk = 0; + md.blewcount += battle_blewcount_bonus(sd, skill_id); + } + + s_ele = skill_get_ele(skill_id, skill_lv); + if (s_ele < 0 && s_ele != -3) //Attack that takes weapon's element for misc attacks? Make it neutral [Skotlex] + s_ele = ELE_NEUTRAL; + else if (s_ele == -3) //Use random element + s_ele = rnd()%ELE_MAX; + + //Skill Range Criteria + md.flag |= battle_range_type(src, target, skill_id, skill_lv); + + switch( skill_id ) + { +#ifdef RENEWAL + case HT_LANDMINE: + case MA_LANDMINE: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + md.damage = skill_lv * sstatus->dex * (3+status_get_lv(src)/100) * (1+sstatus->int_/35); + md.damage += md.damage * (rnd()%20-10) / 100; + md.damage += 40 * (sd?pc_checkskill(sd,RA_RESEARCHTRAP):0); + break; +#else + case HT_LANDMINE: + case MA_LANDMINE: + md.damage=skill_lv*(sstatus->dex+75)*(100+sstatus->int_)/100; + break; + case HT_BLASTMINE: + md.damage=skill_lv*(sstatus->dex/2+50)*(100+sstatus->int_)/100; + break; + case HT_CLAYMORETRAP: + md.damage=skill_lv*(sstatus->dex/2+75)*(100+sstatus->int_)/100; + break; +#endif + case HT_BLITZBEAT: + case SN_FALCONASSAULT: + //Blitz-beat Damage. + if(!sd || (skill = pc_checkskill(sd,HT_STEELCROW)) <= 0) + skill=0; + md.damage=(sstatus->dex/10+sstatus->int_/2+skill*3+40)*2; + if(mflag > 1) //Autocasted Blitz. + nk|=NK_SPLASHSPLIT; + + if (skill_id == SN_FALCONASSAULT) + { + //Div fix of Blitzbeat + skill = skill_get_num(HT_BLITZBEAT, 5); + damage_div_fix(md.damage, skill); + + //Falcon Assault Modifier + md.damage=(int64)md.damage*(150+70*skill_lv)/100; + } + break; + case TF_THROWSTONE: + md.damage=50; + break; + case BA_DISSONANCE: + md.damage=30+skill_lv*10; + if (sd) + md.damage+= 3*pc_checkskill(sd,BA_MUSICALLESSON); + break; + case NPC_SELFDESTRUCTION: + md.damage = sstatus->hp; + break; + case NPC_SMOKING: + md.damage=3; + break; + case NPC_DARKBREATH: + md.damage = 500 + (skill_lv-1)*1000 + rnd()%1000; + if(md.damage > 9999) md.damage = 9999; + break; + case PA_PRESSURE: + md.damage=500+300*skill_lv; + break; + case PA_GOSPEL: + md.damage = 1+rnd()%9999; + break; + case CR_ACIDDEMONSTRATION: // updated the formula based on a Japanese formula found to be exact [Reddozen] + if(tstatus->vit+sstatus->int_) //crash fix + md.damage = (int)((int64)7*tstatus->vit*sstatus->int_*sstatus->int_ / (10*(tstatus->vit+sstatus->int_))); + else + md.damage = 0; + if (tsd) md.damage>>=1; + if (md.damage < 0 || md.damage > INT_MAX>>1) + //Overflow prevention, will anyone whine if I cap it to a few billion? + //Not capped to INT_MAX to give some room for further damage increase. + md.damage = INT_MAX>>1; + break; + case NJ_ZENYNAGE: + case KO_MUCHANAGE: + md.damage = skill_get_zeny(skill_id ,skill_lv); + if (!md.damage) md.damage = 2; + md.damage = rand()%md.damage + md.damage / (skill_id==NJ_ZENYNAGE?1:2) ; + if (is_boss(target)) + md.damage=md.damage / (skill_id==NJ_ZENYNAGE?3:2); + else if (tsd) // need confirmation for KO_MUCHANAGE + md.damage=md.damage/2; + break; + case GS_FLING: + md.damage = sd?sd->status.job_level:status_get_lv(src); + break; + case HVAN_EXPLOSION: //[orn] + md.damage = (int64)sstatus->max_hp * (50 + 50 * skill_lv) / 100; + break ; + case ASC_BREAKER: + md.damage = 500+rnd()%500 + 5*skill_lv * sstatus->int_; + nk|=NK_IGNORE_FLEE|NK_NO_ELEFIX; //These two are not properties of the weapon based part. + break; + case HW_GRAVITATION: + md.damage = 200+200*skill_lv; + md.dmotion = 0; //No flinch animation. + break; + case NPC_EVILLAND: + md.damage = skill_calc_heal(src,target,skill_id,skill_lv,false); + break; + case RK_DRAGONBREATH: + md.damage = ((status_get_hp(src) / 50) + (status_get_max_sp(src) / 4)) * skill_lv; + RE_LVL_MDMOD(150); + if (sd) md.damage = (int64)md.damage * (100 + 5 * (pc_checkskill(sd,RK_DRAGONTRAINING) - 1)) / 100; + md.flag |= BF_LONG|BF_WEAPON; + break; + /** + * Ranger + **/ + case RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + md.damage = skill_lv * sstatus->dex + sstatus->int_ * 5 ; + RE_LVL_TMDMOD(); + if(sd) + { + int researchskill_lv = pc_checkskill(sd,RA_RESEARCHTRAP); + if(researchskill_lv) + md.damage = (int64)md.damage * 20 * researchskill_lv / (skill_id == RA_CLUSTERBOMB?50:100); + else + md.damage = 0; + }else + md.damage = (int64)md.damage * 200 / (skill_id == RA_CLUSTERBOMB?50:100); + + break; + /** + * Mechanic + **/ + case NC_SELFDESTRUCTION: + { + short totaldef = tstatus->def2 + (short)status_get_def(target); + md.damage = ( (sd?pc_checkskill(sd,NC_MAINFRAME):10) + 8 ) * ( skill_lv + 1 ) * ( status_get_sp(src) + sstatus->vit ); + RE_LVL_MDMOD(100); + md.damage += status_get_hp(src) - totaldef; + } + break; + case GN_THORNS_TRAP: + md.damage = 100 + 200 * skill_lv + sstatus->int_; + break; + case GN_HELLS_PLANT_ATK: + //[{( Hell Plant Skill Level x Casters Base Level ) x 10 } + {( Casters INT x 7 ) / 2 } x { 18 + ( Casters Job Level / 4 )] x ( 5 / ( 10 - Summon Flora Skill Level )) + md.damage = ( skill_lv * status_get_lv(src) * 10 ) + ( sstatus->int_ * 7 / 2 ) * ( 18 + (sd?sd->status.job_level:0) / 4 ) * ( 5 / (10 - (sd?pc_checkskill(sd,AM_CANNIBALIZE):0)) ); + break; + case KO_HAPPOKUNAI: + { + struct Damage wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag); + short totaldef = tstatus->def2 + (short)status_get_def(target); + md.damage = (int64)wd.damage * 60 * (5 + skill_lv) / 100; + md.damage -= totaldef; + } + break; + case KO_MAKIBISHI: + md.damage = 20 * skill_lv; + break; + } + + if (nk&NK_SPLASHSPLIT){ // Divide ATK among targets + if(mflag>0) + md.damage/= mflag; + else + ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id)); + } + + damage_div_fix(md.damage, md.div_); + + if (!(nk&NK_IGNORE_FLEE)) + { + struct status_change *sc = status_get_sc(target); + i = 0; //Temp for "hit or no hit" + if(sc && sc->opt1 && sc->opt1 != OPT1_STONEWAIT && sc->opt1 != OPT1_BURNING) + i = 1; + else { + short + flee = tstatus->flee, +#ifdef RENEWAL + hitrate = 0; //Default hitrate +#else + hitrate = 80; //Default hitrate +#endif + + if(battle_config.agi_penalty_type && battle_config.agi_penalty_target&target->type) { + unsigned char attacker_count; //256 max targets should be a sane max + attacker_count = unit_counttargeted(target); + if(attacker_count >= battle_config.agi_penalty_count) + { + if (battle_config.agi_penalty_type == 1) + flee = (flee * (100 - (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num))/100; + else //asume type 2: absolute reduction + flee -= (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num; + if(flee < 1) flee = 1; + } + } + + hitrate+= sstatus->hit - flee; +#ifdef RENEWAL + if( sd ) //in Renewal hit bonus from Vultures Eye is not anymore shown in status window + hitrate += pc_checkskill(sd,AC_VULTURE); +#endif + hitrate = cap_value(hitrate, battle_config.min_hitrate, battle_config.max_hitrate); + + if(rnd()%100 < hitrate) + i = 1; + } + if (!i) { + md.damage = 0; + md.dmg_lv=ATK_FLEE; + } + } + + md.damage = battle_calc_cardfix(BF_MISC, src, target, nk, s_ele, 0, md.damage, 0, md.flag); + + if (sd && (i = pc_skillatk_bonus(sd, skill_id))) + md.damage += (int64)md.damage*i/100; + + if(md.damage < 0) + md.damage = 0; + else if(md.damage && tstatus->mode&MD_PLANT){ + switch(skill_id){ + case HT_LANDMINE: + case MA_LANDMINE: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case RA_CLUSTERBOMB: +#ifdef RENEWAL + break; +#endif + default: + md.damage = 1; + } + }else if( target->type == BL_SKILL ){ + TBL_SKILL *su = (TBL_SKILL*)target; + if( su->group && (su->group->skill_id == WM_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD) ) + md.damage = 1; + } + + if(!(nk&NK_NO_ELEFIX)) + md.damage=battle_attr_fix(src, target, md.damage, s_ele, tstatus->def_ele, tstatus->ele_lv); + + md.damage=battle_calc_damage(src,target,&md,md.damage,skill_id,skill_lv); + if( map_flag_gvg2(target->m) ) + md.damage=battle_calc_gvg_damage(src,target,md.damage,md.div_,skill_id,skill_lv,md.flag); + else if( map[target->m].flag.battleground ) + md.damage=battle_calc_bg_damage(src,target,md.damage,md.div_,skill_id,skill_lv,md.flag); + + switch( skill_id ) { + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + if( md.damage == 1 ) break; + case RA_CLUSTERBOMB: + { + struct Damage wd; + wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag); + md.damage += wd.damage; + } + break; + case NJ_ZENYNAGE: + if( sd ) { + if ( md.damage > sd->status.zeny ) + md.damage = sd->status.zeny; + pc_payzeny(sd, md.damage,LOG_TYPE_STEAL,NULL); + } + break; + } + + return md; +} +/*========================================== + * Battle main entry, from skill_attack + *------------------------------------------*/ +struct Damage battle_calc_attack(int attack_type,struct block_list *bl,struct block_list *target,uint16 skill_id,uint16 skill_lv,int count) +{ + struct Damage d; + switch(attack_type) { + case BF_WEAPON: d = battle_calc_weapon_attack(bl,target,skill_id,skill_lv,count); break; + case BF_MAGIC: d = battle_calc_magic_attack(bl,target,skill_id,skill_lv,count); break; + case BF_MISC: d = battle_calc_misc_attack(bl,target,skill_id,skill_lv,count); break; + default: + ShowError("battle_calc_attack: unknown attack type! %d\n",attack_type); + memset(&d,0,sizeof(d)); + break; + } + if( d.damage + d.damage2 < 1 ) + { //Miss/Absorbed + //Weapon attacks should go through to cause additional effects. + if (d.dmg_lv == ATK_DEF /*&& attack_type&(BF_MAGIC|BF_MISC)*/) // Isn't it that additional effects don't apply if miss? + d.dmg_lv = ATK_MISS; + d.dmotion = 0; + } + else // Some skills like Weaponry Research will cause damage even if attack is dodged + d.dmg_lv = ATK_DEF; + return d; +} + +//Calculates BF_WEAPON returned damage. +int battle_calc_return_damage(struct block_list* bl, struct block_list *src, int *dmg, int flag, uint16 skill_id){ + struct map_session_data* sd = NULL; + int rdamage = 0, damage = *dmg; + struct status_change* sc; + + sd = BL_CAST(BL_PC, bl); + sc = status_get_sc(bl); + + if( sc && sc->data[SC_REFLECTDAMAGE] ) { + int max_damage = (int64)status_get_max_hp(bl) * status_get_lv(bl) / 100; + rdamage = (int64)(*dmg) * sc->data[SC_REFLECTDAMAGE]->val2 / 100; + if( rdamage > max_damage ) rdamage = max_damage; + }else if( sc && sc->data[SC_CRESCENTELBOW] && !is_boss(src) && rnd()%100 < sc->data[SC_CRESCENTELBOW]->val2 ){ + //ATK [{(Target HP / 100) x Skill Level} x Caster Base Level / 125] % + [Received damage x {1 + (Skill Level x 0.2)}] + int ratio = (int64)(status_get_hp(src) / 100) * sc->data[SC_CRESCENTELBOW]->val1 * status_get_lv(bl) / 125; + if (ratio > 5000) ratio = 5000; // Maximum of 5000% ATK + rdamage = (int64)rdamage * ratio / 100 + (*dmg) * (10 + sc->data[SC_CRESCENTELBOW]->val1 * 20 / 10) / 10; + skill_blown(bl, src, skill_get_blewcount(SR_CRESCENTELBOW_AUTOSPELL, sc->data[SC_CRESCENTELBOW]->val1), unit_getdir(src), 0); + clif_skill_damage(bl, src, gettick(), status_get_amotion(src), 0, rdamage, + 1, SR_CRESCENTELBOW_AUTOSPELL, sc->data[SC_CRESCENTELBOW]->val1, 6); // This is how official does + clif_damage(src, bl, gettick(), status_get_amotion(src)+1000, 0, rdamage/10, 1, 0, 0); + status_damage(src, bl, status_damage(bl, src, rdamage, 0, 0, 1)/10, 0, 0, 1); + status_change_end(bl, SC_CRESCENTELBOW, INVALID_TIMER); + return 0; // Just put here to minimize redundancy + }else if (flag & BF_SHORT) {//Bounces back part of the damage. + if ( sd && sd->bonus.short_weapon_damage_return ) { + rdamage += (int64)damage * sd->bonus.short_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + if( sc && sc->count ) { + if ( sc->data[SC_REFLECTSHIELD] && skill_id != WS_CARTTERMINATION ) { + rdamage += (int64)damage * sc->data[SC_REFLECTSHIELD]->val2 / 100; + if (rdamage < 1) rdamage = 1; + } + if(sc->data[SC_DEATHBOUND] && skill_id != WS_CARTTERMINATION && !(src->type == BL_MOB && is_boss(src)) ) { + uint8 dir = map_calc_dir(bl,src->x,src->y), + t_dir = unit_getdir(bl); + int rd1 = 0; + + if( distance_bl(src,bl) <= 0 || !map_check_dir(dir,t_dir) ) { + rd1 = (int64)min(damage,status_get_max_hp(bl)) * sc->data[SC_DEATHBOUND]->val2 / 100; // Amplify damage. + *dmg = (int64)rd1 * 30 / 100; // Received damage = 30% of amplifly damage. + clif_skill_damage(src,bl,gettick(), status_get_amotion(src), 0, -30000, 1, RK_DEATHBOUND, sc->data[SC_DEATHBOUND]->val1,6); + status_change_end(bl,SC_DEATHBOUND,INVALID_TIMER); + rdamage += rd1; + if (rdamage < 1) rdamage = 1; + } + } + } + } else { + if (sd && sd->bonus.long_weapon_damage_return) { + rdamage += (int64)damage * sd->bonus.long_weapon_damage_return / 100; + if (rdamage < 1) rdamage = 1; + } + } + + if( sc && sc->data[SC_KYOMU] ) // Nullify reflecting ability + rdamage = 0; + + return rdamage; +} + +void battle_drain(TBL_PC *sd, struct block_list *tbl, int rdamage, int ldamage, int race, int boss) +{ + struct weapon_data *wd; + int type, thp = 0, tsp = 0, rhp = 0, rsp = 0, hp, sp, i, *damage; + for (i = 0; i < 4; i++) { + //First two iterations: Right hand + if (i < 2) { wd = &sd->right_weapon; damage = &rdamage; } + else { wd = &sd->left_weapon; damage = &ldamage; } + if (*damage <= 0) continue; + //First and Third iterations: race, other two boss/nonboss state + if (i == 0 || i == 2) + type = race; + else + type = boss?RC_BOSS:RC_NONBOSS; + + hp = wd->hp_drain[type].value; + if (wd->hp_drain[type].rate) + hp += battle_calc_drain(*damage, wd->hp_drain[type].rate, wd->hp_drain[type].per); + + sp = wd->sp_drain[type].value; + if (wd->sp_drain[type].rate) + sp += battle_calc_drain(*damage, wd->sp_drain[type].rate, wd->sp_drain[type].per); + + if (hp) { + if (wd->hp_drain[type].type) + rhp += hp; + thp += hp; + } + if (sp) { + if (wd->sp_drain[type].type) + rsp += sp; + tsp += sp; + } + } + + if (sd->bonus.sp_vanish_rate && rnd()%1000 < sd->bonus.sp_vanish_rate) + status_percent_damage(&sd->bl, tbl, 0, (unsigned char)sd->bonus.sp_vanish_per, false); + + if( sd->sp_gain_race_attack[race] ) + tsp += sd->sp_gain_race_attack[race]; + if( sd->hp_gain_race_attack[race] ) + thp += sd->hp_gain_race_attack[race]; + + if (!thp && !tsp) return; + + status_heal(&sd->bl, thp, tsp, battle_config.show_hp_sp_drain?3:1); + + if (rhp || rsp) + status_zap(tbl, rhp, rsp); +} +// Deals the same damage to targets in area. [pakpil] +int battle_damage_area( struct block_list *bl, va_list ap) { + unsigned int tick; + int amotion, dmotion, damage; + struct block_list *src; + + nullpo_ret(bl); + + tick=va_arg(ap, unsigned int); + src=va_arg(ap,struct block_list *); + amotion=va_arg(ap,int); + dmotion=va_arg(ap,int); + damage=va_arg(ap,int); + if( bl->type == BL_MOB && ((TBL_MOB*)bl)->class_ == MOBID_EMPERIUM ) + return 0; + if( bl != src && battle_check_target(src,bl,BCT_ENEMY) > 0 ) { + map_freeblock_lock(); + if( src->type == BL_PC ) + battle_drain((TBL_PC*)src, bl, damage, damage, status_get_race(bl), is_boss(bl)); + if( amotion ) + battle_delay_damage(tick, amotion,src,bl,0,CR_REFLECTSHIELD,0,damage,ATK_DEF,0); + else + status_fix_damage(src,bl,damage,0); + clif_damage(bl,bl,tick,amotion,dmotion,damage,1,ATK_BLOCK,0); + skill_additional_effect(src, bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); + map_freeblock_unlock(); + } + + return 0; +} +/*========================================== + * Do a basic physical attack (call trough unit_attack_timer) + *------------------------------------------*/ +enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* target, unsigned int tick, int flag) { + struct map_session_data *sd = NULL, *tsd = NULL; + struct status_data *sstatus, *tstatus; + struct status_change *sc, *tsc; + int damage,rdamage=0,rdelay=0; + int skillv; + struct Damage wd; + + nullpo_retr(ATK_NONE, src); + nullpo_retr(ATK_NONE, target); + + if (src->prev == NULL || target->prev == NULL) + return ATK_NONE; + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(target); + + sc = status_get_sc(src); + tsc = status_get_sc(target); + + if (sc && !sc->count) //Avoid sc checks when there's none to check for. [Skotlex] + sc = NULL; + if (tsc && !tsc->count) + tsc = NULL; + + if (sd) + { + sd->state.arrow_atk = (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)); + if (sd->state.arrow_atk) + { + int index = sd->equip_index[EQI_AMMO]; + if (index<0) { + clif_arrow_fail(sd,0); + return ATK_NONE; + } + //Ammo check by Ishizu-chan + if (sd->inventory_data[index]) + switch (sd->status.weapon) { + case W_BOW: + if (sd->inventory_data[index]->look != A_ARROW) { + clif_arrow_fail(sd,0); + return ATK_NONE; + } + break; + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + if (sd->inventory_data[index]->look != A_BULLET) { + clif_arrow_fail(sd,0); + return ATK_NONE; + } + break; + case W_GRENADE: + if (sd->inventory_data[index]->look != A_GRENADE) { + clif_arrow_fail(sd,0); + return ATK_NONE; + } + break; + } + } + } + if (sc && sc->count) { + if (sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4 & 2)) + status_change_end(src, SC_CLOAKING, INVALID_TIMER); + else if (sc->data[SC_CLOAKINGEXCEED] && !(sc->data[SC_CLOAKINGEXCEED]->val4 & 2)) + status_change_end(src, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + if( tsc && tsc->data[SC_AUTOCOUNTER] && status_check_skilluse(target, src, KN_AUTOCOUNTER, 1) ) + { + uint8 dir = map_calc_dir(target,src->x,src->y); + int t_dir = unit_getdir(target); + int dist = distance_bl(src, target); + if(dist <= 0 || (!map_check_dir(dir,t_dir) && dist <= tstatus->rhw.range+1)) + { + uint16 skill_lv = tsc->data[SC_AUTOCOUNTER]->val1; + clif_skillcastcancel(target); //Remove the casting bar. [Skotlex] + clif_damage(src, target, tick, sstatus->amotion, 1, 0, 1, 0, 0); //Display MISS. + status_change_end(target, SC_AUTOCOUNTER, INVALID_TIMER); + skill_attack(BF_WEAPON,target,target,src,KN_AUTOCOUNTER,skill_lv,tick,0); + return ATK_BLOCK; + } + } + + if( tsc && tsc->data[SC_BLADESTOP_WAIT] && !is_boss(src) && (src->type == BL_PC || tsd == NULL || distance_bl(src, target) <= (tsd->status.weapon == W_FIST ? 1 : 2)) ) + { + uint16 skill_lv = tsc->data[SC_BLADESTOP_WAIT]->val1; + int duration = skill_get_time2(MO_BLADESTOP,skill_lv); + status_change_end(target, SC_BLADESTOP_WAIT, INVALID_TIMER); + if(sc_start4(src, SC_BLADESTOP, 100, sd?pc_checkskill(sd, MO_BLADESTOP):5, 0, 0, target->id, duration)) + { //Target locked. + clif_damage(src, target, tick, sstatus->amotion, 1, 0, 1, 0, 0); //Display MISS. + clif_bladestop(target, src->id, 1); + sc_start4(target, SC_BLADESTOP, 100, skill_lv, 0, 0, src->id, duration); + return ATK_BLOCK; + } + } + + if(sd && (skillv = pc_checkskill(sd,MO_TRIPLEATTACK)) > 0) { + int triple_rate= 30 - skillv; //Base Rate + if (sc && sc->data[SC_SKILLRATE_UP] && sc->data[SC_SKILLRATE_UP]->val1 == MO_TRIPLEATTACK) { + triple_rate+= triple_rate*(sc->data[SC_SKILLRATE_UP]->val2)/100; + status_change_end(src, SC_SKILLRATE_UP, INVALID_TIMER); + } + if (rnd()%100 < triple_rate) { + if( skill_attack(BF_WEAPON,src,src,target,MO_TRIPLEATTACK,skillv,tick,0) ) + return ATK_DEF; + return ATK_MISS; + } + } + + if (sc) { + if (sc->data[SC_SACRIFICE]) { + uint16 skill_lv = sc->data[SC_SACRIFICE]->val1; + damage_lv ret_val; + + if( --sc->data[SC_SACRIFICE]->val2 <= 0 ) + status_change_end(src, SC_SACRIFICE, INVALID_TIMER); + + /** + * We need to calculate the DMG before the hp reduction, because it can kill the source. + * For futher information: bugreport:4950 + **/ + ret_val = (damage_lv)skill_attack(BF_WEAPON,src,src,target,PA_SACRIFICE,skill_lv,tick,0); + + status_zap(src, sstatus->max_hp*9/100, 0);//Damage to self is always 9% + if( ret_val == ATK_NONE ) + return ATK_MISS; + return ret_val; + } + if (sc->data[SC_MAGICALATTACK]) { + if( skill_attack(BF_MAGIC,src,src,target,NPC_MAGICALATTACK,sc->data[SC_MAGICALATTACK]->val1,tick,0) ) + return ATK_DEF; + return ATK_MISS; + } + if( sc->data[SC_GT_ENERGYGAIN] ) { + if( sd && rnd()%100 < 10 + 5 * sc->data[SC_GT_ENERGYGAIN]->val1) + pc_addspiritball(sd, + skill_get_time(MO_CALLSPIRITS, sc->data[SC_GT_ENERGYGAIN]->val1), + sc->data[SC_GT_ENERGYGAIN]->val1); + } + if( tsc && tsc->data[SC_GT_ENERGYGAIN] ) { + if( tsd && rnd()%100 < 10 + 5 * tsc->data[SC_GT_ENERGYGAIN]->val1) + pc_addspiritball(tsd, + skill_get_time(MO_CALLSPIRITS, tsc->data[SC_GT_ENERGYGAIN]->val1), + tsc->data[SC_GT_ENERGYGAIN]->val1); + } + if( sc && sc->data[SC_CRUSHSTRIKE] ){ + uint16 skill_lv = sc->data[SC_CRUSHSTRIKE]->val1; + status_change_end(src, SC_CRUSHSTRIKE, INVALID_TIMER); + if( skill_attack(BF_WEAPON,src,src,target,RK_CRUSHSTRIKE,skill_lv,tick,0) ) + return ATK_DEF; + return ATK_MISS; + } + } + + if(tsc && tsc->data[SC_KAAHI] && tsc->data[SC_KAAHI]->val4 == INVALID_TIMER && tstatus->hp < tstatus->max_hp) + tsc->data[SC_KAAHI]->val4 = add_timer(tick + skill_get_time2(SL_KAAHI,tsc->data[SC_KAAHI]->val1), kaahi_heal_timer, target->id, SC_KAAHI); //Activate heal. + + wd = battle_calc_attack(BF_WEAPON, src, target, 0, 0, flag); + + if( sc && sc->count ) { + if (sc->data[SC_EXEEDBREAK]) { + ATK_RATER(sc->data[SC_EXEEDBREAK]->val1) + status_change_end(src, SC_EXEEDBREAK, INVALID_TIMER); + } + if( sc->data[SC_SPELLFIST] ) { + if( --(sc->data[SC_SPELLFIST]->val1) >= 0 ){ + struct Damage ad = battle_calc_attack(BF_MAGIC,src,target,sc->data[SC_SPELLFIST]->val3,sc->data[SC_SPELLFIST]->val4,flag|BF_SHORT); + wd.damage = ad.damage; + }else + status_change_end(src,SC_SPELLFIST,INVALID_TIMER); + } + if( sc->data[SC_GIANTGROWTH] && (wd.flag&BF_SHORT) && rnd()%100 < sc->data[SC_GIANTGROWTH]->val2 ) + wd.damage *= 3; // Triple Damage + + if( sd && sc->data[SC_FEARBREEZE] && sc->data[SC_FEARBREEZE]->val4 > 0 && sd->status.inventory[sd->equip_index[EQI_AMMO]].amount >= sc->data[SC_FEARBREEZE]->val4 && battle_config.arrow_decrement){ + pc_delitem(sd,sd->equip_index[EQI_AMMO],sc->data[SC_FEARBREEZE]->val4,0,1,LOG_TYPE_CONSUME); + sc->data[SC_FEARBREEZE]->val4 = 0; + } + } + if (sd && sd->state.arrow_atk) //Consume arrow. + battle_consume_ammo(sd, 0, 0); + + damage = wd.damage + wd.damage2; + if( damage > 0 && src != target ) + { + if( sc && sc->data[SC_DUPLELIGHT] && (wd.flag&BF_SHORT) && rnd()%100 <= 10+2*sc->data[SC_DUPLELIGHT]->val1 ) + { // Activates it only from melee damage + uint16 skill_id; + if( rnd()%2 == 1 ) + skill_id = AB_DUPLELIGHT_MELEE; + else + skill_id = AB_DUPLELIGHT_MAGIC; + skill_attack(skill_get_type(skill_id), src, src, target, skill_id, sc->data[SC_DUPLELIGHT]->val1, tick, SD_LEVEL); + } + + rdamage = battle_calc_return_damage(target,src, &damage, wd.flag, 0); + if( rdamage > 0 ) { + if( tsc && tsc->data[SC_REFLECTDAMAGE] ) { + if( src != target )// Don't reflect your own damage (Grand Cross) + map_foreachinshootrange(battle_damage_area,target,skill_get_splash(LG_REFLECTDAMAGE,1),BL_CHAR,tick,target,wd.amotion,wd.dmotion,rdamage,tstatus->race,0); + } else { + rdelay = clif_damage(src, src, tick, wd.amotion, sstatus->dmotion, rdamage, 1, 4, 0); + //Use Reflect Shield to signal this kind of skill trigger. [Skotlex] + skill_additional_effect(target,src,CR_REFLECTSHIELD,1,BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); + } + } + } + + wd.dmotion = clif_damage(src, target, tick, wd.amotion, wd.dmotion, wd.damage, wd.div_ , wd.type, wd.damage2); + + if (sd && sd->bonus.splash_range > 0 && damage > 0) + skill_castend_damage_id(src, target, 0, 1, tick, 0); + if ( target->type == BL_SKILL && damage > 0 ){ + TBL_SKILL *su = (TBL_SKILL*)target; + if( su->group && su->group->skill_id == HT_BLASTMINE) + skill_blown(src, target, 3, -1, 0); + } + map_freeblock_lock(); + + battle_delay_damage(tick, wd.amotion, src, target, wd.flag, 0, 0, damage, wd.dmg_lv, wd.dmotion); + if( tsc ) { + if( tsc->data[SC_DEVOTION] ) { + struct status_change_entry *sce = tsc->data[SC_DEVOTION]; + struct block_list *d_bl = map_id2bl(sce->val1); + + if( d_bl && ( + (d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == target->id) || + (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == target->id) + ) && check_distance_bl(target, d_bl, sce->val3) ) + { + clif_damage(d_bl, d_bl, gettick(), 0, 0, damage, 0, 0, 0); + status_fix_damage(NULL, d_bl, damage, 0); + } + else + status_change_end(target, SC_DEVOTION, INVALID_TIMER); + } else if( tsc->data[SC_CIRCLE_OF_FIRE_OPTION] && (wd.flag&BF_SHORT) && target->type == BL_PC ) { + struct elemental_data *ed = ((TBL_PC*)target)->ed; + if( ed ) { + clif_skill_damage(&ed->bl, target, tick, status_get_amotion(src), 0, -30000, 1, EL_CIRCLE_OF_FIRE, tsc->data[SC_CIRCLE_OF_FIRE_OPTION]->val1, 6); + skill_attack(BF_MAGIC,&ed->bl,&ed->bl,src,EL_CIRCLE_OF_FIRE,tsc->data[SC_CIRCLE_OF_FIRE_OPTION]->val1,tick,wd.flag); + } + } else if( tsc->data[SC_WATER_SCREEN_OPTION] && tsc->data[SC_WATER_SCREEN_OPTION]->val1 ) { + struct block_list *e_bl = map_id2bl(tsc->data[SC_WATER_SCREEN_OPTION]->val1); + if( e_bl && !status_isdead(e_bl) ) { + clif_damage(e_bl,e_bl,tick,wd.amotion,wd.dmotion,damage,wd.div_,wd.type,wd.damage2); + status_damage(target,e_bl,damage,0,0,0); + // Just show damage in target. + clif_damage(src, target, tick, wd.amotion, wd.dmotion, damage, wd.div_, wd.type, wd.damage2 ); + map_freeblock_unlock(); + return ATK_NONE; + } + } + } + if (sc && sc->data[SC_AUTOSPELL] && rnd()%100 < sc->data[SC_AUTOSPELL]->val4) { + int sp = 0; + uint16 skill_id = sc->data[SC_AUTOSPELL]->val2; + uint16 skill_lv = sc->data[SC_AUTOSPELL]->val3; + int i = rnd()%100; + if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_SAGE) + i = 0; //Max chance, no skill_lv reduction. [Skotlex] + if (i >= 50) skill_lv -= 2; + else if (i >= 15) skill_lv--; + if (skill_lv < 1) skill_lv = 1; + sp = skill_get_sp(skill_id,skill_lv) * 2 / 3; + + if (status_charge(src, 0, sp)) { + switch (skill_get_casttype(skill_id)) { + case CAST_GROUND: + skill_castend_pos2(src, target->x, target->y, skill_id, skill_lv, tick, flag); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, target, skill_id, skill_lv, tick, flag); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, target, skill_id, skill_lv, tick, flag); + break; + } + } + } + if (sd) { + if( wd.flag&BF_SHORT && sc && sc->data[SC__AUTOSHADOWSPELL] && rnd()%100 < sc->data[SC__AUTOSHADOWSPELL]->val3 && + sd->status.skill[sc->data[SC__AUTOSHADOWSPELL]->val1].id != 0 && sd->status.skill[sc->data[SC__AUTOSHADOWSPELL]->val1].flag == SKILL_FLAG_PLAGIARIZED ) + { + int r_skill = sd->status.skill[sc->data[SC__AUTOSHADOWSPELL]->val1].id, + r_lv = sc->data[SC__AUTOSHADOWSPELL]->val2, type; + + if (r_skill != AL_HOLYLIGHT && r_skill != PR_MAGNUS) { + if( (type = skill_get_casttype(r_skill)) == CAST_GROUND ) { + int maxcount = 0; + + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(r_skill)&UF_NOREITERATION ) + type = -1; + + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(r_skill)&UF_NOFOOTSET ) + type = -1; + + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(r_skill, r_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { + if(sd->ud.skillunit[v]->skill_id == r_skill) + maxcount--; + } + if( maxcount == 0 ) + type = -1; + } + + if( type != CAST_GROUND ){ + clif_skill_fail(sd,r_skill,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return wd.dmg_lv; + } + } + + sd->state.autocast = 1; + skill_consume_requirement(sd,r_skill,r_lv,3); + switch( type ) { + case CAST_GROUND: + skill_castend_pos2(src, target->x, target->y, r_skill, r_lv, tick, flag); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, target, r_skill, r_lv, tick, flag); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, target, r_skill, r_lv, tick, flag); + break; + } + sd->state.autocast = 0; + + sd->ud.canact_tick = tick + skill_delayfix(src, r_skill, r_lv); + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, r_skill, r_lv), 0, 0, 1); + } + } + + if (wd.flag & BF_WEAPON && src != target && damage > 0) { + if (battle_config.left_cardfix_to_right) + battle_drain(sd, target, wd.damage, wd.damage, tstatus->race, is_boss(target)); + else + battle_drain(sd, target, wd.damage, wd.damage2, tstatus->race, is_boss(target)); + } + } + if (rdamage > 0 && !(tsc && tsc->data[SC_REFLECTDAMAGE])) { //By sending attack type "none" skill_additional_effect won't be invoked. [Skotlex] + if(tsd && src != target) + battle_drain(tsd, src, rdamage, rdamage, sstatus->race, is_boss(src)); + battle_delay_damage(tick, wd.amotion, target, src, 0, CR_REFLECTSHIELD, 0, rdamage, ATK_DEF, rdelay); + } + + if (tsc) { + if (tsc->data[SC_POISONREACT] && + (rnd()%100 < tsc->data[SC_POISONREACT]->val3 + || sstatus->def_ele == ELE_POISON) && +// check_distance_bl(src, target, tstatus->rhw.range+1) && Doesn't checks range! o.O; + status_check_skilluse(target, src, TF_POISON, 0) + ) { //Poison React + struct status_change_entry *sce = tsc->data[SC_POISONREACT]; + if (sstatus->def_ele == ELE_POISON) { + sce->val2 = 0; + skill_attack(BF_WEAPON,target,target,src,AS_POISONREACT,sce->val1,tick,0); + } else { + skill_attack(BF_WEAPON,target,target,src,TF_POISON, 5, tick, 0); + --sce->val2; + } + if (sce->val2 <= 0) + status_change_end(target, SC_POISONREACT, INVALID_TIMER); + } + } + map_freeblock_unlock(); + return wd.dmg_lv; +} + +int battle_check_undead(int race,int element) +{ + if(battle_config.undead_detect_type == 0) { + if(element == ELE_UNDEAD) + return 1; + } + else if(battle_config.undead_detect_type == 1) { + if(race == RC_UNDEAD) + return 1; + } + else { + if(element == ELE_UNDEAD || race == RC_UNDEAD) + return 1; + } + return 0; +} + +//Returns the upmost level master starting with the given object +struct block_list* battle_get_master(struct block_list *src) +{ + struct block_list *prev; //Used for infinite loop check (master of yourself?) + do { + prev = src; + switch (src->type) { + case BL_PET: + if (((TBL_PET*)src)->msd) + src = (struct block_list*)((TBL_PET*)src)->msd; + break; + case BL_MOB: + if (((TBL_MOB*)src)->master_id) + src = map_id2bl(((TBL_MOB*)src)->master_id); + break; + case BL_HOM: + if (((TBL_HOM*)src)->master) + src = (struct block_list*)((TBL_HOM*)src)->master; + break; + case BL_MER: + if (((TBL_MER*)src)->master) + src = (struct block_list*)((TBL_MER*)src)->master; + break; + case BL_ELEM: + if (((TBL_ELEM*)src)->master) + src = (struct block_list*)((TBL_ELEM*)src)->master; + break; + case BL_SKILL: + if (((TBL_SKILL*)src)->group && ((TBL_SKILL*)src)->group->src_id) + src = map_id2bl(((TBL_SKILL*)src)->group->src_id); + break; + } + } while (src && src != prev); + return prev; +} + +/*========================================== + * Checks the state between two targets (rewritten by Skotlex) + * (enemy, friend, party, guild, etc) + * See battle.h for possible values/combinations + * to be used here (BCT_* constants) + * Return value is: + * 1: flag holds true (is enemy, party, etc) + * -1: flag fails + * 0: Invalid target (non-targetable ever) + *------------------------------------------*/ +int battle_check_target( struct block_list *src, struct block_list *target,int flag) +{ + int16 m; //map + int state = 0; //Initial state none + int strip_enemy = 1; //Flag which marks whether to remove the BCT_ENEMY status if it's also friend/ally. + struct block_list *s_bl = src, *t_bl = target; + + nullpo_ret(src); + nullpo_ret(target); + + m = target->m; + + //t_bl/s_bl hold the 'master' of the attack, while src/target are the actual + //objects involved. + if( (t_bl = battle_get_master(target)) == NULL ) + t_bl = target; + + if( (s_bl = battle_get_master(src)) == NULL ) + s_bl = src; + + if ( s_bl->type == BL_PC ) { + switch( t_bl->type ) { + case BL_MOB: // Source => PC, Target => MOB + if ( pc_has_permission((TBL_PC*)s_bl, PC_PERM_DISABLE_PVM) ) + return 0; + break; + case BL_PC: + if (pc_has_permission((TBL_PC*)s_bl, PC_PERM_DISABLE_PVP)) + return 0; + break; + default:/* anything else goes */ + break; + } + } + + switch( target->type ) { // Checks on actual target + case BL_PC: { + struct status_change* sc = status_get_sc(src); + if (((TBL_PC*)target)->invincible_timer != INVALID_TIMER || pc_isinvisible((TBL_PC*)target)) + return -1; //Cannot be targeted yet. + if( sc && sc->count ) { + if( sc->data[SC_VOICEOFSIREN] && sc->data[SC_VOICEOFSIREN]->val2 == target->id ) + return -1; + } + } + break; + case BL_MOB: + if(((((TBL_MOB*)target)->special_state.ai == 2 || //Marine Spheres + (((TBL_MOB*)target)->special_state.ai == 3 && battle_config.summon_flora&1)) && //Floras + s_bl->type == BL_PC && src->type != BL_MOB) || ((TBL_MOB*)target)->special_state.ai == 4) //Zanzoe + { //Targettable by players + state |= BCT_ENEMY; + strip_enemy = 0; + } + break; + case BL_SKILL: + { + TBL_SKILL *su = (TBL_SKILL*)target; + if( !su->group ) + return 0; + if( skill_get_inf2(su->group->skill_id)&INF2_TRAP ) { //Only a few skills can target traps... + switch( battle_getcurrentskill(src) ) { + case RK_DRAGONBREATH:// it can only hit traps in pvp/gvg maps + if( !map[m].flag.pvp && !map[m].flag.gvg ) + break; + case 0://you can hit them without skills + case MA_REMOVETRAP: + case HT_REMOVETRAP: + case AC_SHOWER: + case MA_SHOWER: + case WZ_SIGHTRASHER: + case WZ_SIGHTBLASTER: + case SM_MAGNUM: + case MS_MAGNUM: + case RA_DETONATOR: + case RA_SENSITIVEKEEN: + case GN_CRAZYWEED_ATK: + case RK_STORMBLAST: + case RK_PHANTOMTHRUST: + case SR_RAMPAGEBLASTER: + case NC_COLDSLOWER: + case NC_SELFDESTRUCTION: +#ifdef RENEWAL + case KN_BOWLINGBASH: + case KN_SPEARSTAB: + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + case MO_FINGEROFFENSIVE: + case MO_INVESTIGATE: + case MO_TRIPLEATTACK: + case MO_EXTREMITYFIST: + case CR_HOLYCROSS: + case ASC_METEORASSAULT: + case RG_RAID: + case MC_CARTREVOLUTION: +#endif + state |= BCT_ENEMY; + strip_enemy = 0; + break; + default: + if(su->group->skill_id == WM_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD){ + state |= BCT_ENEMY; + strip_enemy = 0; + }else + return 0; + } + } else if (su->group->skill_id==WZ_ICEWALL || + su->group->skill_id == GN_WALLOFTHORN) { + state |= BCT_ENEMY; + strip_enemy = 0; + } else //Excepting traps and icewall, you should not be able to target skills. + return 0; + } + break; + //Valid targets with no special checks here. + case BL_MER: + case BL_HOM: + case BL_ELEM: + break; + //All else not specified is an invalid target. + default: + return 0; + } //end switch actual target + + switch( t_bl->type ) + { //Checks on target master + case BL_PC: + { + struct map_session_data *sd; + if( t_bl == s_bl ) break; + sd = BL_CAST(BL_PC, t_bl); + + if( sd->state.monster_ignore && flag&BCT_ENEMY ) + return 0; // Global inminuty only to Attacks + if( sd->status.karma && s_bl->type == BL_PC && ((TBL_PC*)s_bl)->status.karma ) + state |= BCT_ENEMY; // Characters with bad karma may fight amongst them + if( sd->state.killable ) { + state |= BCT_ENEMY; // Everything can kill it + strip_enemy = 0; + } + break; + } + case BL_MOB: + { + struct mob_data *md = BL_CAST(BL_MOB, t_bl); + + if( !((agit_flag || agit2_flag) && map[m].flag.gvg_castle) && md->guardian_data && md->guardian_data->guild_id ) + return 0; // Disable guardians/emperiums owned by Guilds on non-woe times. + break; + } + default: break; //other type doesn't have slave yet + } //end switch master target + + switch( src->type ) { //Checks on actual src type + case BL_PET: + if (t_bl->type != BL_MOB && flag&BCT_ENEMY) + return 0; //Pet may not attack non-mobs. + if (t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->guardian_data && flag&BCT_ENEMY) + return 0; //pet may not attack Guardians/Emperium + break; + case BL_SKILL: { + struct skill_unit *su = (struct skill_unit *)src; + if (!su->group) + return 0; + + if (su->group->src_id == target->id) { + int inf2 = skill_get_inf2(su->group->skill_id); + if (inf2&INF2_NO_TARGET_SELF) + return -1; + if (inf2&INF2_TARGET_SELF) + return 1; + } + } + break; + case BL_MER: + if (t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->class_ == MOBID_EMPERIUM && flag&BCT_ENEMY) + return 0; //mercenary may not attack Emperium + break; + } //end switch actual src + + switch( s_bl->type ) + { //Checks on source master + case BL_PC: + { + struct map_session_data *sd = BL_CAST(BL_PC, s_bl); + if( s_bl != t_bl ) + { + if( sd->state.killer ) + { + state |= BCT_ENEMY; // Can kill anything + strip_enemy = 0; + } + else if( sd->duel_group && !((!battle_config.duel_allow_pvp && map[m].flag.pvp) || (!battle_config.duel_allow_gvg && map_flag_gvg(m))) ) + { + if( t_bl->type == BL_PC && (sd->duel_group == ((TBL_PC*)t_bl)->duel_group) ) + return (BCT_ENEMY&flag)?1:-1; // Duel targets can ONLY be your enemy, nothing else. + else + return 0; // You can't target anything out of your duel + } + } + if( map_flag_gvg(m) && !sd->status.guild_id && t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->class_ == MOBID_EMPERIUM ) + return 0; //If you don't belong to a guild, can't target emperium. + if( t_bl->type != BL_PC ) + state |= BCT_ENEMY; //Natural enemy. + break; + } + case BL_MOB: + { + struct mob_data *md = BL_CAST(BL_MOB, s_bl); + if( !((agit_flag || agit2_flag) && map[m].flag.gvg_castle) && md->guardian_data && md->guardian_data->guild_id ) + return 0; // Disable guardians/emperium owned by Guilds on non-woe times. + + if( !md->special_state.ai ) + { //Normal mobs + if( + ( target->type == BL_MOB && t_bl->type == BL_PC && ( ((TBL_MOB*)target)->special_state.ai != 4 && ((TBL_MOB*)target)->special_state.ai != 1 ) ) || + ( t_bl->type == BL_MOB && !((TBL_MOB*)t_bl)->special_state.ai ) + ) + state |= BCT_PARTY; //Normal mobs with no ai are friends. + else + state |= BCT_ENEMY; //However, all else are enemies. + } + else + { + if( t_bl->type == BL_MOB && !((TBL_MOB*)t_bl)->special_state.ai ) + state |= BCT_ENEMY; //Natural enemy for AI mobs are normal mobs. + } + break; + } + default: + //Need some sort of default behaviour for unhandled types. + if (t_bl->type != s_bl->type) + state |= BCT_ENEMY; + break; + } //end switch on src master + + if( (flag&BCT_ALL) == BCT_ALL ) + { //All actually stands for all attackable chars + if( target->type&BL_CHAR ) + return 1; + else + return -1; + } + if( flag == BCT_NOONE ) //Why would someone use this? no clue. + return -1; + + if( t_bl == s_bl ) + { //No need for further testing. + state |= BCT_SELF|BCT_PARTY|BCT_GUILD; + if( state&BCT_ENEMY && strip_enemy ) + state&=~BCT_ENEMY; + return (flag&state)?1:-1; + } + + if( map_flag_vs(m) ) + { //Check rivalry settings. + int sbg_id = 0, tbg_id = 0; + if( map[m].flag.battleground ) + { + sbg_id = bg_team_get_id(s_bl); + tbg_id = bg_team_get_id(t_bl); + } + if( flag&(BCT_PARTY|BCT_ENEMY) ) + { + int s_party = status_get_party_id(s_bl); + if( s_party && s_party == status_get_party_id(t_bl) && !(map[m].flag.pvp && map[m].flag.pvp_noparty) && !(map_flag_gvg(m) && map[m].flag.gvg_noparty) && (!map[m].flag.battleground || sbg_id == tbg_id) ) + state |= BCT_PARTY; + else + state |= BCT_ENEMY; + } + if( flag&(BCT_GUILD|BCT_ENEMY) ) + { + int s_guild = status_get_guild_id(s_bl); + int t_guild = status_get_guild_id(t_bl); + if( !(map[m].flag.pvp && map[m].flag.pvp_noguild) && s_guild && t_guild && (s_guild == t_guild || guild_isallied(s_guild, t_guild)) && (!map[m].flag.battleground || sbg_id == tbg_id) ) + state |= BCT_GUILD; + else + state |= BCT_ENEMY; + } + if( state&BCT_ENEMY && map[m].flag.battleground && sbg_id && sbg_id == tbg_id ) + state &= ~BCT_ENEMY; + + if( state&BCT_ENEMY && battle_config.pk_mode && !map_flag_gvg(m) && s_bl->type == BL_PC && t_bl->type == BL_PC ) + { // Prevent novice engagement on pk_mode (feature by Valaris) + TBL_PC *sd = (TBL_PC*)s_bl, *sd2 = (TBL_PC*)t_bl; + if ( + (sd->class_&MAPID_UPPERMASK) == MAPID_NOVICE || + (sd2->class_&MAPID_UPPERMASK) == MAPID_NOVICE || + (int)sd->status.base_level < battle_config.pk_min_level || + (int)sd2->status.base_level < battle_config.pk_min_level || + (battle_config.pk_level_range && abs((int)sd->status.base_level - (int)sd2->status.base_level) > battle_config.pk_level_range) + ) + state &= ~BCT_ENEMY; + } + }//end map_flag_vs chk rivality + else + { //Non pvp/gvg, check party/guild settings. + if( flag&BCT_PARTY || state&BCT_ENEMY ) + { + int s_party = status_get_party_id(s_bl); + if(s_party && s_party == status_get_party_id(t_bl)) + state |= BCT_PARTY; + } + if( flag&BCT_GUILD || state&BCT_ENEMY ) + { + int s_guild = status_get_guild_id(s_bl); + int t_guild = status_get_guild_id(t_bl); + if(s_guild && t_guild && (s_guild == t_guild || guild_isallied(s_guild, t_guild))) + state |= BCT_GUILD; + } + } //end non pvp/gvg chk rivality + + if( !state ) //If not an enemy, nor a guild, nor party, nor yourself, it's neutral. + state = BCT_NEUTRAL; + //Alliance state takes precedence over enemy one. + else if( state&BCT_ENEMY && strip_enemy && state&(BCT_SELF|BCT_PARTY|BCT_GUILD) ) + state&=~BCT_ENEMY; + + return (flag&state)?1:-1; +} +/*========================================== + * Check if can attack from this range + * Basic check then calling path_search for obstacle etc.. + *------------------------------------------*/ +bool battle_check_range(struct block_list *src, struct block_list *bl, int range) +{ + int d; + nullpo_retr(false, src); + nullpo_retr(false, bl); + + if( src->m != bl->m ) + return false; + +#ifndef CIRCULAR_AREA + if( src->type == BL_PC ) { // Range for players' attacks and skills should always have a circular check. [Angezerus] + int dx = src->x - bl->x, dy = src->y - bl->y; + if( !check_distance(dx, dy, range) ) + return false; + } else +#endif + if( !check_distance_bl(src, bl, range) ) + return false; + + if( (d = distance_bl(src, bl)) < 2 ) + return true; // No need for path checking. + + if( d > AREA_SIZE ) + return false; // Avoid targetting objects beyond your range of sight. + + return path_search_long(NULL,src->m,src->x,src->y,bl->x,bl->y,CELL_CHKWALL); +} + +static const struct _battle_data { + const char* str; + int* val; + int defval; + int min; + int max; +} battle_data[] = { + { "warp_point_debug", &battle_config.warp_point_debug, 0, 0, 1, }, + { "enable_critical", &battle_config.enable_critical, BL_PC, BL_NUL, BL_ALL, }, + { "mob_critical_rate", &battle_config.mob_critical_rate, 100, 0, INT_MAX, }, + { "critical_rate", &battle_config.critical_rate, 100, 0, INT_MAX, }, + { "enable_baseatk", &battle_config.enable_baseatk, BL_PC|BL_HOM, BL_NUL, BL_ALL, }, + { "enable_perfect_flee", &battle_config.enable_perfect_flee, BL_PC|BL_PET, BL_NUL, BL_ALL, }, + { "casting_rate", &battle_config.cast_rate, 100, 0, INT_MAX, }, + { "delay_rate", &battle_config.delay_rate, 100, 0, INT_MAX, }, + { "delay_dependon_dex", &battle_config.delay_dependon_dex, 0, 0, 1, }, + { "delay_dependon_agi", &battle_config.delay_dependon_agi, 0, 0, 1, }, + { "skill_delay_attack_enable", &battle_config.sdelay_attack_enable, 0, 0, 1, }, + { "left_cardfix_to_right", &battle_config.left_cardfix_to_right, 0, 0, 1, }, + { "skill_add_range", &battle_config.skill_add_range, 0, 0, INT_MAX, }, + { "skill_out_range_consume", &battle_config.skill_out_range_consume, 1, 0, 1, }, + { "skillrange_by_distance", &battle_config.skillrange_by_distance, ~BL_PC, BL_NUL, BL_ALL, }, + { "skillrange_from_weapon", &battle_config.use_weapon_skill_range, ~BL_PC, BL_NUL, BL_ALL, }, + { "player_damage_delay_rate", &battle_config.pc_damage_delay_rate, 100, 0, INT_MAX, }, + { "defunit_not_enemy", &battle_config.defnotenemy, 0, 0, 1, }, + { "gvg_traps_target_all", &battle_config.vs_traps_bctall, BL_PC, BL_NUL, BL_ALL, }, + { "traps_setting", &battle_config.traps_setting, 0, 0, 1, }, + { "summon_flora_setting", &battle_config.summon_flora, 1|2, 0, 1|2, }, + { "clear_skills_on_death", &battle_config.clear_unit_ondeath, BL_NUL, BL_NUL, BL_ALL, }, + { "clear_skills_on_warp", &battle_config.clear_unit_onwarp, BL_ALL, BL_NUL, BL_ALL, }, + { "random_monster_checklv", &battle_config.random_monster_checklv, 0, 0, 1, }, + { "attribute_recover", &battle_config.attr_recover, 1, 0, 1, }, + { "flooritem_lifetime", &battle_config.flooritem_lifetime, 60000, 1000, INT_MAX, }, + { "item_auto_get", &battle_config.item_auto_get, 0, 0, 1, }, + { "item_first_get_time", &battle_config.item_first_get_time, 3000, 0, INT_MAX, }, + { "item_second_get_time", &battle_config.item_second_get_time, 1000, 0, INT_MAX, }, + { "item_third_get_time", &battle_config.item_third_get_time, 1000, 0, INT_MAX, }, + { "mvp_item_first_get_time", &battle_config.mvp_item_first_get_time, 10000, 0, INT_MAX, }, + { "mvp_item_second_get_time", &battle_config.mvp_item_second_get_time, 10000, 0, INT_MAX, }, + { "mvp_item_third_get_time", &battle_config.mvp_item_third_get_time, 2000, 0, INT_MAX, }, + { "drop_rate0item", &battle_config.drop_rate0item, 0, 0, 1, }, + { "base_exp_rate", &battle_config.base_exp_rate, 100, 0, INT_MAX, }, + { "job_exp_rate", &battle_config.job_exp_rate, 100, 0, INT_MAX, }, + { "pvp_exp", &battle_config.pvp_exp, 1, 0, 1, }, + { "death_penalty_type", &battle_config.death_penalty_type, 0, 0, 2, }, + { "death_penalty_base", &battle_config.death_penalty_base, 0, 0, INT_MAX, }, + { "death_penalty_job", &battle_config.death_penalty_job, 0, 0, INT_MAX, }, + { "zeny_penalty", &battle_config.zeny_penalty, 0, 0, INT_MAX, }, + { "hp_rate", &battle_config.hp_rate, 100, 1, INT_MAX, }, + { "sp_rate", &battle_config.sp_rate, 100, 1, INT_MAX, }, + { "restart_hp_rate", &battle_config.restart_hp_rate, 0, 0, 100, }, + { "restart_sp_rate", &battle_config.restart_sp_rate, 0, 0, 100, }, + { "guild_aura", &battle_config.guild_aura, 31, 0, 31, }, + { "mvp_hp_rate", &battle_config.mvp_hp_rate, 100, 1, INT_MAX, }, + { "mvp_exp_rate", &battle_config.mvp_exp_rate, 100, 0, INT_MAX, }, + { "monster_hp_rate", &battle_config.monster_hp_rate, 100, 1, INT_MAX, }, + { "monster_max_aspd", &battle_config.monster_max_aspd, 199, 100, 199, }, + { "view_range_rate", &battle_config.view_range_rate, 100, 0, INT_MAX, }, + { "chase_range_rate", &battle_config.chase_range_rate, 100, 0, INT_MAX, }, + { "gtb_sc_immunity", &battle_config.gtb_sc_immunity, 50, 0, INT_MAX, }, + { "guild_max_castles", &battle_config.guild_max_castles, 0, 0, INT_MAX, }, + { "guild_skill_relog_delay", &battle_config.guild_skill_relog_delay, 0, 0, 1, }, + { "emergency_call", &battle_config.emergency_call, 11, 0, 31, }, + { "atcommand_spawn_quantity_limit", &battle_config.atc_spawn_quantity_limit, 100, 0, INT_MAX, }, + { "atcommand_slave_clone_limit", &battle_config.atc_slave_clone_limit, 25, 0, INT_MAX, }, + { "partial_name_scan", &battle_config.partial_name_scan, 0, 0, 1, }, + { "player_skillfree", &battle_config.skillfree, 0, 0, 1, }, + { "player_skillup_limit", &battle_config.skillup_limit, 1, 0, 1, }, + { "weapon_produce_rate", &battle_config.wp_rate, 100, 0, INT_MAX, }, + { "potion_produce_rate", &battle_config.pp_rate, 100, 0, INT_MAX, }, + { "monster_active_enable", &battle_config.monster_active_enable, 1, 0, 1, }, + { "monster_damage_delay_rate", &battle_config.monster_damage_delay_rate, 100, 0, INT_MAX, }, + { "monster_loot_type", &battle_config.monster_loot_type, 0, 0, 1, }, +// { "mob_skill_use", &battle_config.mob_skill_use, 1, 0, 1, }, //Deprecated + { "mob_skill_rate", &battle_config.mob_skill_rate, 100, 0, INT_MAX, }, + { "mob_skill_delay", &battle_config.mob_skill_delay, 100, 0, INT_MAX, }, + { "mob_count_rate", &battle_config.mob_count_rate, 100, 0, INT_MAX, }, + { "mob_spawn_delay", &battle_config.mob_spawn_delay, 100, 0, INT_MAX, }, + { "plant_spawn_delay", &battle_config.plant_spawn_delay, 100, 0, INT_MAX, }, + { "boss_spawn_delay", &battle_config.boss_spawn_delay, 100, 0, INT_MAX, }, + { "no_spawn_on_player", &battle_config.no_spawn_on_player, 0, 0, 100, }, + { "force_random_spawn", &battle_config.force_random_spawn, 0, 0, 1, }, + { "slaves_inherit_mode", &battle_config.slaves_inherit_mode, 2, 0, 3, }, + { "slaves_inherit_speed", &battle_config.slaves_inherit_speed, 3, 0, 3, }, + { "summons_trigger_autospells", &battle_config.summons_trigger_autospells, 1, 0, 1, }, + { "pc_damage_walk_delay_rate", &battle_config.pc_walk_delay_rate, 20, 0, INT_MAX, }, + { "damage_walk_delay_rate", &battle_config.walk_delay_rate, 100, 0, INT_MAX, }, + { "multihit_delay", &battle_config.multihit_delay, 80, 0, INT_MAX, }, + { "quest_skill_learn", &battle_config.quest_skill_learn, 0, 0, 1, }, + { "quest_skill_reset", &battle_config.quest_skill_reset, 0, 0, 1, }, + { "basic_skill_check", &battle_config.basic_skill_check, 1, 0, 1, }, + { "guild_emperium_check", &battle_config.guild_emperium_check, 1, 0, 1, }, + { "guild_exp_limit", &battle_config.guild_exp_limit, 50, 0, 99, }, + { "player_invincible_time", &battle_config.pc_invincible_time, 5000, 0, INT_MAX, }, + { "pet_catch_rate", &battle_config.pet_catch_rate, 100, 0, INT_MAX, }, + { "pet_rename", &battle_config.pet_rename, 0, 0, 1, }, + { "pet_friendly_rate", &battle_config.pet_friendly_rate, 100, 0, INT_MAX, }, + { "pet_hungry_delay_rate", &battle_config.pet_hungry_delay_rate, 100, 10, INT_MAX, }, + { "pet_hungry_friendly_decrease", &battle_config.pet_hungry_friendly_decrease, 5, 0, INT_MAX, }, + { "pet_status_support", &battle_config.pet_status_support, 0, 0, 1, }, + { "pet_attack_support", &battle_config.pet_attack_support, 0, 0, 1, }, + { "pet_damage_support", &battle_config.pet_damage_support, 0, 0, 1, }, + { "pet_support_min_friendly", &battle_config.pet_support_min_friendly, 900, 0, 950, }, + { "pet_equip_min_friendly", &battle_config.pet_equip_min_friendly, 900, 0, 950, }, + { "pet_support_rate", &battle_config.pet_support_rate, 100, 0, INT_MAX, }, + { "pet_attack_exp_to_master", &battle_config.pet_attack_exp_to_master, 0, 0, 1, }, + { "pet_attack_exp_rate", &battle_config.pet_attack_exp_rate, 100, 0, INT_MAX, }, + { "pet_lv_rate", &battle_config.pet_lv_rate, 0, 0, INT_MAX, }, + { "pet_max_stats", &battle_config.pet_max_stats, 99, 0, INT_MAX, }, + { "pet_max_atk1", &battle_config.pet_max_atk1, 750, 0, INT_MAX, }, + { "pet_max_atk2", &battle_config.pet_max_atk2, 1000, 0, INT_MAX, }, + { "pet_disable_in_gvg", &battle_config.pet_no_gvg, 0, 0, 1, }, + { "skill_min_damage", &battle_config.skill_min_damage, 2|4, 0, 1|2|4, }, + { "finger_offensive_type", &battle_config.finger_offensive_type, 0, 0, 1, }, + { "heal_exp", &battle_config.heal_exp, 0, 0, INT_MAX, }, + { "resurrection_exp", &battle_config.resurrection_exp, 0, 0, INT_MAX, }, + { "shop_exp", &battle_config.shop_exp, 0, 0, INT_MAX, }, + { "max_heal_lv", &battle_config.max_heal_lv, 11, 1, INT_MAX, }, + { "max_heal", &battle_config.max_heal, 9999, 0, INT_MAX, }, + { "combo_delay_rate", &battle_config.combo_delay_rate, 100, 0, INT_MAX, }, + { "item_check", &battle_config.item_check, 0, 0, 1, }, + { "item_use_interval", &battle_config.item_use_interval, 100, 0, INT_MAX, }, + { "cashfood_use_interval", &battle_config.cashfood_use_interval, 60000, 0, INT_MAX, }, + { "wedding_modifydisplay", &battle_config.wedding_modifydisplay, 0, 0, 1, }, + { "wedding_ignorepalette", &battle_config.wedding_ignorepalette, 0, 0, 1, }, + { "xmas_ignorepalette", &battle_config.xmas_ignorepalette, 0, 0, 1, }, + { "summer_ignorepalette", &battle_config.summer_ignorepalette, 0, 0, 1, }, + { "natural_healhp_interval", &battle_config.natural_healhp_interval, 6000, NATURAL_HEAL_INTERVAL, INT_MAX, }, + { "natural_healsp_interval", &battle_config.natural_healsp_interval, 8000, NATURAL_HEAL_INTERVAL, INT_MAX, }, + { "natural_heal_skill_interval", &battle_config.natural_heal_skill_interval, 10000, NATURAL_HEAL_INTERVAL, INT_MAX, }, + { "natural_heal_weight_rate", &battle_config.natural_heal_weight_rate, 50, 50, 101 }, + { "arrow_decrement", &battle_config.arrow_decrement, 1, 0, 2, }, + { "max_aspd", &battle_config.max_aspd, 190, 100, 199, }, + { "max_third_aspd", &battle_config.max_third_aspd, 193, 100, 199, }, + { "max_walk_speed", &battle_config.max_walk_speed, 300, 100, 100*DEFAULT_WALK_SPEED, }, + { "max_lv", &battle_config.max_lv, 99, 0, MAX_LEVEL, }, + { "aura_lv", &battle_config.aura_lv, 99, 0, INT_MAX, }, + { "max_hp", &battle_config.max_hp, 32500, 100, 1000000000, }, + { "max_sp", &battle_config.max_sp, 32500, 100, 1000000000, }, + { "max_cart_weight", &battle_config.max_cart_weight, 8000, 100, 1000000, }, + { "max_parameter", &battle_config.max_parameter, 99, 10, 10000, }, + { "max_baby_parameter", &battle_config.max_baby_parameter, 80, 10, 10000, }, + { "max_def", &battle_config.max_def, 99, 0, INT_MAX, }, + { "over_def_bonus", &battle_config.over_def_bonus, 0, 0, 1000, }, + { "skill_log", &battle_config.skill_log, BL_NUL, BL_NUL, BL_ALL, }, + { "battle_log", &battle_config.battle_log, 0, 0, 1, }, + { "etc_log", &battle_config.etc_log, 1, 0, 1, }, + { "save_clothcolor", &battle_config.save_clothcolor, 1, 0, 1, }, + { "undead_detect_type", &battle_config.undead_detect_type, 0, 0, 2, }, + { "auto_counter_type", &battle_config.auto_counter_type, BL_ALL, BL_NUL, BL_ALL, }, + { "min_hitrate", &battle_config.min_hitrate, 5, 0, 100, }, + { "max_hitrate", &battle_config.max_hitrate, 100, 0, 100, }, + { "agi_penalty_target", &battle_config.agi_penalty_target, BL_PC, BL_NUL, BL_ALL, }, + { "agi_penalty_type", &battle_config.agi_penalty_type, 1, 0, 2, }, + { "agi_penalty_count", &battle_config.agi_penalty_count, 3, 2, INT_MAX, }, + { "agi_penalty_num", &battle_config.agi_penalty_num, 10, 0, INT_MAX, }, + { "vit_penalty_target", &battle_config.vit_penalty_target, BL_PC, BL_NUL, BL_ALL, }, + { "vit_penalty_type", &battle_config.vit_penalty_type, 1, 0, 2, }, + { "vit_penalty_count", &battle_config.vit_penalty_count, 3, 2, INT_MAX, }, + { "vit_penalty_num", &battle_config.vit_penalty_num, 5, 0, INT_MAX, }, + { "weapon_defense_type", &battle_config.weapon_defense_type, 0, 0, INT_MAX, }, + { "magic_defense_type", &battle_config.magic_defense_type, 0, 0, INT_MAX, }, + { "skill_reiteration", &battle_config.skill_reiteration, BL_NUL, BL_NUL, BL_ALL, }, + { "skill_nofootset", &battle_config.skill_nofootset, BL_PC, BL_NUL, BL_ALL, }, + { "player_cloak_check_type", &battle_config.pc_cloak_check_type, 1, 0, 1|2|4, }, + { "monster_cloak_check_type", &battle_config.monster_cloak_check_type, 4, 0, 1|2|4, }, + { "sense_type", &battle_config.estimation_type, 1|2, 0, 1|2, }, + { "gvg_short_attack_damage_rate", &battle_config.gvg_short_damage_rate, 80, 0, INT_MAX, }, + { "gvg_long_attack_damage_rate", &battle_config.gvg_long_damage_rate, 80, 0, INT_MAX, }, + { "gvg_weapon_attack_damage_rate", &battle_config.gvg_weapon_damage_rate, 60, 0, INT_MAX, }, + { "gvg_magic_attack_damage_rate", &battle_config.gvg_magic_damage_rate, 60, 0, INT_MAX, }, + { "gvg_misc_attack_damage_rate", &battle_config.gvg_misc_damage_rate, 60, 0, INT_MAX, }, + { "gvg_flee_penalty", &battle_config.gvg_flee_penalty, 20, 0, INT_MAX, }, + { "pk_short_attack_damage_rate", &battle_config.pk_short_damage_rate, 80, 0, INT_MAX, }, + { "pk_long_attack_damage_rate", &battle_config.pk_long_damage_rate, 70, 0, INT_MAX, }, + { "pk_weapon_attack_damage_rate", &battle_config.pk_weapon_damage_rate, 60, 0, INT_MAX, }, + { "pk_magic_attack_damage_rate", &battle_config.pk_magic_damage_rate, 60, 0, INT_MAX, }, + { "pk_misc_attack_damage_rate", &battle_config.pk_misc_damage_rate, 60, 0, INT_MAX, }, + { "mob_changetarget_byskill", &battle_config.mob_changetarget_byskill, 0, 0, 1, }, + { "attack_direction_change", &battle_config.attack_direction_change, BL_ALL, BL_NUL, BL_ALL, }, + { "land_skill_limit", &battle_config.land_skill_limit, BL_ALL, BL_NUL, BL_ALL, }, + { "monster_class_change_full_recover", &battle_config.monster_class_change_recover, 1, 0, 1, }, + { "produce_item_name_input", &battle_config.produce_item_name_input, 0x1|0x2, 0, 0x9F, }, + { "display_skill_fail", &battle_config.display_skill_fail, 2, 0, 1|2|4|8, }, + { "chat_warpportal", &battle_config.chat_warpportal, 0, 0, 1, }, + { "mob_warp", &battle_config.mob_warp, 0, 0, 1|2|4, }, + { "dead_branch_active", &battle_config.dead_branch_active, 1, 0, 1, }, + { "vending_max_value", &battle_config.vending_max_value, 10000000, 1, MAX_ZENY, }, + { "vending_over_max", &battle_config.vending_over_max, 1, 0, 1, }, + { "show_steal_in_same_party", &battle_config.show_steal_in_same_party, 0, 0, 1, }, + { "party_hp_mode", &battle_config.party_hp_mode, 0, 0, 1, }, + { "show_party_share_picker", &battle_config.party_show_share_picker, 1, 0, 1, }, + { "show_picker.item_type", &battle_config.show_picker_item_type, 112, 0, INT_MAX, }, + { "party_update_interval", &battle_config.party_update_interval, 1000, 100, INT_MAX, }, + { "party_item_share_type", &battle_config.party_share_type, 0, 0, 1|2|3, }, + { "attack_attr_none", &battle_config.attack_attr_none, ~BL_PC, BL_NUL, BL_ALL, }, + { "gx_allhit", &battle_config.gx_allhit, 0, 0, 1, }, + { "gx_disptype", &battle_config.gx_disptype, 1, 0, 1, }, + { "devotion_level_difference", &battle_config.devotion_level_difference, 10, 0, INT_MAX, }, + { "player_skill_partner_check", &battle_config.player_skill_partner_check, 1, 0, 1, }, + { "invite_request_check", &battle_config.invite_request_check, 1, 0, 1, }, + { "skill_removetrap_type", &battle_config.skill_removetrap_type, 0, 0, 1, }, + { "disp_experience", &battle_config.disp_experience, 0, 0, 1, }, + { "disp_zeny", &battle_config.disp_zeny, 0, 0, 1, }, + { "castle_defense_rate", &battle_config.castle_defense_rate, 100, 0, 100, }, + { "bone_drop", &battle_config.bone_drop, 0, 0, 2, }, + { "buyer_name", &battle_config.buyer_name, 1, 0, 1, }, + { "skill_wall_check", &battle_config.skill_wall_check, 1, 0, 1, }, + { "cell_stack_limit", &battle_config.cell_stack_limit, 1, 1, 255, }, + { "dancing_weaponswitch_fix", &battle_config.dancing_weaponswitch_fix, 1, 0, 1, }, + +// eAthena additions + { "item_logarithmic_drops", &battle_config.logarithmic_drops, 0, 0, 1, }, + { "item_drop_common_min", &battle_config.item_drop_common_min, 1, 1, 10000, }, + { "item_drop_common_max", &battle_config.item_drop_common_max, 10000, 1, 10000, }, + { "item_drop_equip_min", &battle_config.item_drop_equip_min, 1, 1, 10000, }, + { "item_drop_equip_max", &battle_config.item_drop_equip_max, 10000, 1, 10000, }, + { "item_drop_card_min", &battle_config.item_drop_card_min, 1, 1, 10000, }, + { "item_drop_card_max", &battle_config.item_drop_card_max, 10000, 1, 10000, }, + { "item_drop_mvp_min", &battle_config.item_drop_mvp_min, 1, 1, 10000, }, + { "item_drop_mvp_max", &battle_config.item_drop_mvp_max, 10000, 1, 10000, }, + { "item_drop_heal_min", &battle_config.item_drop_heal_min, 1, 1, 10000, }, + { "item_drop_heal_max", &battle_config.item_drop_heal_max, 10000, 1, 10000, }, + { "item_drop_use_min", &battle_config.item_drop_use_min, 1, 1, 10000, }, + { "item_drop_use_max", &battle_config.item_drop_use_max, 10000, 1, 10000, }, + { "item_drop_add_min", &battle_config.item_drop_adddrop_min, 1, 1, 10000, }, + { "item_drop_add_max", &battle_config.item_drop_adddrop_max, 10000, 1, 10000, }, + { "item_drop_treasure_min", &battle_config.item_drop_treasure_min, 1, 1, 10000, }, + { "item_drop_treasure_max", &battle_config.item_drop_treasure_max, 10000, 1, 10000, }, + { "item_rate_mvp", &battle_config.item_rate_mvp, 100, 0, 1000000, }, + { "item_rate_common", &battle_config.item_rate_common, 100, 0, 1000000, }, + { "item_rate_common_boss", &battle_config.item_rate_common_boss, 100, 0, 1000000, }, + { "item_rate_equip", &battle_config.item_rate_equip, 100, 0, 1000000, }, + { "item_rate_equip_boss", &battle_config.item_rate_equip_boss, 100, 0, 1000000, }, + { "item_rate_card", &battle_config.item_rate_card, 100, 0, 1000000, }, + { "item_rate_card_boss", &battle_config.item_rate_card_boss, 100, 0, 1000000, }, + { "item_rate_heal", &battle_config.item_rate_heal, 100, 0, 1000000, }, + { "item_rate_heal_boss", &battle_config.item_rate_heal_boss, 100, 0, 1000000, }, + { "item_rate_use", &battle_config.item_rate_use, 100, 0, 1000000, }, + { "item_rate_use_boss", &battle_config.item_rate_use_boss, 100, 0, 1000000, }, + { "item_rate_adddrop", &battle_config.item_rate_adddrop, 100, 0, 1000000, }, + { "item_rate_treasure", &battle_config.item_rate_treasure, 100, 0, 1000000, }, + { "prevent_logout", &battle_config.prevent_logout, 10000, 0, 60000, }, + { "alchemist_summon_reward", &battle_config.alchemist_summon_reward, 1, 0, 2, }, + { "drops_by_luk", &battle_config.drops_by_luk, 0, 0, INT_MAX, }, + { "drops_by_luk2", &battle_config.drops_by_luk2, 0, 0, INT_MAX, }, + { "equip_natural_break_rate", &battle_config.equip_natural_break_rate, 0, 0, INT_MAX, }, + { "equip_self_break_rate", &battle_config.equip_self_break_rate, 100, 0, INT_MAX, }, + { "equip_skill_break_rate", &battle_config.equip_skill_break_rate, 100, 0, INT_MAX, }, + { "pk_mode", &battle_config.pk_mode, 0, 0, 2, }, + { "pk_level_range", &battle_config.pk_level_range, 0, 0, INT_MAX, }, + { "manner_system", &battle_config.manner_system, 0xFFF, 0, 0xFFF, }, + { "pet_equip_required", &battle_config.pet_equip_required, 0, 0, 1, }, + { "multi_level_up", &battle_config.multi_level_up, 0, 0, 1, }, + { "max_exp_gain_rate", &battle_config.max_exp_gain_rate, 0, 0, INT_MAX, }, + { "backstab_bow_penalty", &battle_config.backstab_bow_penalty, 0, 0, 1, }, + { "night_at_start", &battle_config.night_at_start, 0, 0, 1, }, + { "show_mob_info", &battle_config.show_mob_info, 0, 0, 1|2|4, }, + { "ban_hack_trade", &battle_config.ban_hack_trade, 0, 0, INT_MAX, }, + { "packet_ver_flag", &battle_config.packet_ver_flag, 0xFFFFFF,0x0000,INT_MAX, }, + { "min_hair_style", &battle_config.min_hair_style, 0, 0, INT_MAX, }, + { "max_hair_style", &battle_config.max_hair_style, 23, 0, INT_MAX, }, + { "min_hair_color", &battle_config.min_hair_color, 0, 0, INT_MAX, }, + { "max_hair_color", &battle_config.max_hair_color, 9, 0, INT_MAX, }, + { "min_cloth_color", &battle_config.min_cloth_color, 0, 0, INT_MAX, }, + { "max_cloth_color", &battle_config.max_cloth_color, 4, 0, INT_MAX, }, + { "pet_hair_style", &battle_config.pet_hair_style, 100, 0, INT_MAX, }, + { "castrate_dex_scale", &battle_config.castrate_dex_scale, 150, 1, INT_MAX, }, + { "vcast_stat_scale", &battle_config.vcast_stat_scale, 530, 1, INT_MAX, }, + { "area_size", &battle_config.area_size, 14, 0, INT_MAX, }, + { "zeny_from_mobs", &battle_config.zeny_from_mobs, 0, 0, 1, }, + { "mobs_level_up", &battle_config.mobs_level_up, 0, 0, 1, }, + { "mobs_level_up_exp_rate", &battle_config.mobs_level_up_exp_rate, 1, 1, INT_MAX, }, + { "pk_min_level", &battle_config.pk_min_level, 55, 1, INT_MAX, }, + { "skill_steal_max_tries", &battle_config.skill_steal_max_tries, 0, 0, UCHAR_MAX, }, + { "motd_type", &battle_config.motd_type, 0, 0, 1, }, + { "finding_ore_rate", &battle_config.finding_ore_rate, 100, 0, INT_MAX, }, + { "exp_calc_type", &battle_config.exp_calc_type, 0, 0, 1, }, + { "exp_bonus_attacker", &battle_config.exp_bonus_attacker, 25, 0, INT_MAX, }, + { "exp_bonus_max_attacker", &battle_config.exp_bonus_max_attacker, 12, 2, INT_MAX, }, + { "min_skill_delay_limit", &battle_config.min_skill_delay_limit, 100, 10, INT_MAX, }, + { "default_walk_delay", &battle_config.default_walk_delay, 300, 0, INT_MAX, }, + { "no_skill_delay", &battle_config.no_skill_delay, BL_MOB, BL_NUL, BL_ALL, }, + { "attack_walk_delay", &battle_config.attack_walk_delay, BL_ALL, BL_NUL, BL_ALL, }, + { "require_glory_guild", &battle_config.require_glory_guild, 0, 0, 1, }, + { "idle_no_share", &battle_config.idle_no_share, 0, 0, INT_MAX, }, + { "party_even_share_bonus", &battle_config.party_even_share_bonus, 0, 0, INT_MAX, }, + { "delay_battle_damage", &battle_config.delay_battle_damage, 1, 0, 1, }, + { "hide_woe_damage", &battle_config.hide_woe_damage, 0, 0, 1, }, + { "display_version", &battle_config.display_version, 1, 0, 1, }, + { "display_hallucination", &battle_config.display_hallucination, 1, 0, 1, }, + { "use_statpoint_table", &battle_config.use_statpoint_table, 1, 0, 1, }, + { "ignore_items_gender", &battle_config.ignore_items_gender, 1, 0, 1, }, + { "copyskill_restrict", &battle_config.copyskill_restrict, 2, 0, 2, }, + { "berserk_cancels_buffs", &battle_config.berserk_cancels_buffs, 0, 0, 1, }, + { "debuff_on_logout", &battle_config.debuff_on_logout, 1|2, 0, 1|2, }, + { "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0x77F, }, + { "hom_setting", &battle_config.hom_setting, 0xFFFF, 0x0000, 0xFFFF, }, + { "dynamic_mobs", &battle_config.dynamic_mobs, 1, 0, 1, }, + { "mob_remove_damaged", &battle_config.mob_remove_damaged, 1, 0, 1, }, + { "show_hp_sp_drain", &battle_config.show_hp_sp_drain, 0, 0, 1, }, + { "show_hp_sp_gain", &battle_config.show_hp_sp_gain, 1, 0, 1, }, + { "mob_npc_event_type", &battle_config.mob_npc_event_type, 1, 0, 1, }, + { "character_size", &battle_config.character_size, 1|2, 0, 1|2, }, + { "mob_max_skilllvl", &battle_config.mob_max_skilllvl, MAX_SKILL_LEVEL, 1, MAX_SKILL_LEVEL, }, + { "retaliate_to_master", &battle_config.retaliate_to_master, 1, 0, 1, }, + { "rare_drop_announce", &battle_config.rare_drop_announce, 0, 0, 10000, }, + { "duel_allow_pvp", &battle_config.duel_allow_pvp, 0, 0, 1, }, + { "duel_allow_gvg", &battle_config.duel_allow_gvg, 0, 0, 1, }, + { "duel_allow_teleport", &battle_config.duel_allow_teleport, 0, 0, 1, }, + { "duel_autoleave_when_die", &battle_config.duel_autoleave_when_die, 1, 0, 1, }, + { "duel_time_interval", &battle_config.duel_time_interval, 60, 0, INT_MAX, }, + { "duel_only_on_same_map", &battle_config.duel_only_on_same_map, 0, 0, 1, }, + { "skip_teleport_lv1_menu", &battle_config.skip_teleport_lv1_menu, 0, 0, 1, }, + { "allow_skill_without_day", &battle_config.allow_skill_without_day, 0, 0, 1, }, + { "allow_es_magic_player", &battle_config.allow_es_magic_pc, 0, 0, 1, }, + { "skill_caster_check", &battle_config.skill_caster_check, 1, 0, 1, }, + { "status_cast_cancel", &battle_config.sc_castcancel, BL_NUL, BL_NUL, BL_ALL, }, + { "pc_status_def_rate", &battle_config.pc_sc_def_rate, 100, 0, INT_MAX, }, + { "mob_status_def_rate", &battle_config.mob_sc_def_rate, 100, 0, INT_MAX, }, + { "pc_luk_status_def", &battle_config.pc_luk_sc_def, 300, 1, INT_MAX, }, + { "mob_luk_status_def", &battle_config.mob_luk_sc_def, 300, 1, INT_MAX, }, + { "pc_max_status_def", &battle_config.pc_max_sc_def, 100, 0, INT_MAX, }, + { "mob_max_status_def", &battle_config.mob_max_sc_def, 100, 0, INT_MAX, }, + { "sg_miracle_skill_ratio", &battle_config.sg_miracle_skill_ratio, 1, 0, 10000, }, + { "sg_angel_skill_ratio", &battle_config.sg_angel_skill_ratio, 10, 0, 10000, }, + { "autospell_stacking", &battle_config.autospell_stacking, 0, 0, 1, }, + { "override_mob_names", &battle_config.override_mob_names, 0, 0, 2, }, + { "min_chat_delay", &battle_config.min_chat_delay, 0, 0, INT_MAX, }, + { "friend_auto_add", &battle_config.friend_auto_add, 1, 0, 1, }, + { "hom_rename", &battle_config.hom_rename, 0, 0, 1, }, + { "homunculus_show_growth", &battle_config.homunculus_show_growth, 0, 0, 1, }, + { "homunculus_friendly_rate", &battle_config.homunculus_friendly_rate, 100, 0, INT_MAX, }, + { "vending_tax", &battle_config.vending_tax, 0, 0, 10000, }, + { "day_duration", &battle_config.day_duration, 0, 0, INT_MAX, }, + { "night_duration", &battle_config.night_duration, 0, 0, INT_MAX, }, + { "mob_remove_delay", &battle_config.mob_remove_delay, 60000, 1000, INT_MAX, }, + { "mob_active_time", &battle_config.mob_active_time, 0, 0, INT_MAX, }, + { "boss_active_time", &battle_config.boss_active_time, 0, 0, INT_MAX, }, + { "sg_miracle_skill_duration", &battle_config.sg_miracle_skill_duration, 3600000, 0, INT_MAX, }, + { "hvan_explosion_intimate", &battle_config.hvan_explosion_intimate, 45000, 0, 100000, }, + { "quest_exp_rate", &battle_config.quest_exp_rate, 100, 0, INT_MAX, }, + { "at_mapflag", &battle_config.autotrade_mapflag, 0, 0, 1, }, + { "at_timeout", &battle_config.at_timeout, 0, 0, INT_MAX, }, + { "homunculus_autoloot", &battle_config.homunculus_autoloot, 0, 0, 1, }, + { "idle_no_autoloot", &battle_config.idle_no_autoloot, 0, 0, INT_MAX, }, + { "max_guild_alliance", &battle_config.max_guild_alliance, 3, 0, 3, }, + { "ksprotection", &battle_config.ksprotection, 5000, 0, INT_MAX, }, + { "auction_feeperhour", &battle_config.auction_feeperhour, 12000, 0, INT_MAX, }, + { "auction_maximumprice", &battle_config.auction_maximumprice, 500000000, 0, MAX_ZENY, }, + { "homunculus_auto_vapor", &battle_config.homunculus_auto_vapor, 1, 0, 1, }, + { "display_status_timers", &battle_config.display_status_timers, 1, 0, 1, }, + { "skill_add_heal_rate", &battle_config.skill_add_heal_rate, 7, 0, INT_MAX, }, + { "eq_single_target_reflectable", &battle_config.eq_single_target_reflectable, 1, 0, 1, }, + { "invincible.nodamage", &battle_config.invincible_nodamage, 0, 0, 1, }, + { "mob_slave_keep_target", &battle_config.mob_slave_keep_target, 0, 0, 1, }, + { "autospell_check_range", &battle_config.autospell_check_range, 0, 0, 1, }, + { "client_reshuffle_dice", &battle_config.client_reshuffle_dice, 0, 0, 1, }, + { "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, }, + { "feature.buying_store", &battle_config.feature_buying_store, 1, 0, 1, }, + { "feature.search_stores", &battle_config.feature_search_stores, 1, 0, 1, }, + { "searchstore_querydelay", &battle_config.searchstore_querydelay, 10, 0, INT_MAX, }, + { "searchstore_maxresults", &battle_config.searchstore_maxresults, 30, 1, INT_MAX, }, + { "display_party_name", &battle_config.display_party_name, 0, 0, 1, }, + { "cashshop_show_points", &battle_config.cashshop_show_points, 0, 0, 1, }, + { "mail_show_status", &battle_config.mail_show_status, 0, 0, 2, }, + { "client_limit_unit_lv", &battle_config.client_limit_unit_lv, 0, 0, BL_ALL, }, +// BattleGround Settings + { "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, }, + { "bg_short_attack_damage_rate", &battle_config.bg_short_damage_rate, 80, 0, INT_MAX, }, + { "bg_long_attack_damage_rate", &battle_config.bg_long_damage_rate, 80, 0, INT_MAX, }, + { "bg_weapon_attack_damage_rate", &battle_config.bg_weapon_damage_rate, 60, 0, INT_MAX, }, + { "bg_magic_attack_damage_rate", &battle_config.bg_magic_damage_rate, 60, 0, INT_MAX, }, + { "bg_misc_attack_damage_rate", &battle_config.bg_misc_damage_rate, 60, 0, INT_MAX, }, + { "bg_flee_penalty", &battle_config.bg_flee_penalty, 20, 0, INT_MAX, }, + /** + * rAthena + **/ + { "max_third_parameter", &battle_config.max_third_parameter, 120, 10, 10000, }, + { "max_baby_third_parameter", &battle_config.max_baby_third_parameter, 108, 10, 10000, }, + { "atcommand_max_stat_bypass", &battle_config.atcommand_max_stat_bypass, 0, 0, 100, }, + { "skill_amotion_leniency", &battle_config.skill_amotion_leniency, 90, 0, 300 }, + { "mvp_tomb_enabled", &battle_config.mvp_tomb_enabled, 1, 0, 1 }, + { "feature.atcommand_suggestions", &battle_config.atcommand_suggestions_enabled, 0, 0, 1 }, + { "min_npc_vending_distance", &battle_config.min_npc_vending_distance, 3, 0, 100 }, + { "atcommand_mobinfo_type", &battle_config.atcommand_mobinfo_type, 0, 0, 1 }, + { "homunculus_max_level", &battle_config.hom_max_level, 99, 0, MAX_LEVEL, }, + { "homunculus_S_max_level", &battle_config.hom_S_max_level, 150, 0, MAX_LEVEL, }, + { "mob_size_influence", &battle_config.mob_size_influence, 0, 0, 1, }, +}; +#ifndef STATS_OPT_OUT +/** + * rAthena anonymous statistic usage report -- packet is built here, and sent to char server to report. + **/ +void rAthena_report(char* date, char *time_c) { + int i, rev = 0, bd_size = ARRAYLENGTH(battle_data); + unsigned int config = 0; + const char* rev_str; + char timestring[25]; + time_t curtime; + char* buf; + + enum config_table { + C_CIRCULAR_AREA = 0x0001, + C_CELLNOSTACK = 0x0002, + C_BETA_THREAD_TEST = 0x0004, + C_SCRIPT_CALLFUNC_CHECK = 0x0008, + C_OFFICIAL_WALKPATH = 0x0010, + C_RENEWAL = 0x0020, + C_RENEWAL_CAST = 0x0040, + C_RENEWAL_DROP = 0x0080, + C_RENEWAL_EXP = 0x0100, + C_RENEWAL_LVDMG = 0x0200, + C_RENEWAL_EDP = 0x0400, + C_RENEWAL_ASPD = 0x0800, + C_SECURE_NPCTIMEOUT = 0x1000, + C_SQL_DBS = 0x2000, + C_SQL_LOGS = 0x4000, + }; + + if( (rev_str = get_svn_revision()) != 0 ) + rev = atoi(rev_str); + + /* we get the current time */ + time(&curtime); + strftime(timestring, 24, "%Y-%m-%d %H:%M:%S", localtime(&curtime)); + + +#ifdef CIRCULAR_AREA + config |= C_CIRCULAR_AREA; +#endif + +#ifdef CELL_NOSTACK + config |= C_CELLNOSTACK; +#endif + +#ifdef BETA_THREAD_TEST + config |= C_BETA_THREAD_TEST; +#endif + +#ifdef SCRIPT_CALLFUNC_CHECK + config |= C_SCRIPT_CALLFUNC_CHECK; +#endif + +#ifdef OFFICIAL_WALKPATH + config |= C_OFFICIAL_WALKPATH; +#endif + +#ifdef RENEWAL + config |= C_RENEWAL; +#endif + +#ifdef RENEWAL_CAST + config |= C_RENEWAL_CAST; +#endif + +#ifdef RENEWAL_DROP + config |= C_RENEWAL_DROP; +#endif + +#ifdef RENEWAL_EXP + config |= C_RENEWAL_EXP; +#endif + +#ifdef RENEWAL_LVDMG + config |= C_RENEWAL_LVDMG; +#endif + +#ifdef RENEWAL_EDP + config |= C_RENEWAL_EDP; +#endif + +#ifdef RENEWAL_ASPD + config |= C_RENEWAL_ASPD; +#endif + +/* not a ifdef because SECURE_NPCTIMEOUT is always defined, but either as 0 or higher */ +#if SECURE_NPCTIMEOUT + config |= C_SECURE_NPCTIMEOUT; +#endif + /* non-define part */ + if( db_use_sqldbs ) + config |= C_SQL_DBS; + + if( log_config.sql_logs ) + config |= C_SQL_LOGS; + +#define BFLAG_LENGTH 35 + + CREATE(buf, char, 6 + 12 + 9 + 24 + 4 + 4 + 4 + 4 + ( bd_size * ( BFLAG_LENGTH + 4 ) ) + 1 ); + + /* build packet */ + + WBUFW(buf,0) = 0x3000; + WBUFW(buf,2) = 6 + 12 + 9 + 24 + 4 + 4 + 4 + 4 + ( bd_size * ( BFLAG_LENGTH + 4 ) ); + WBUFW(buf,4) = 0x9c; + + safestrncpy((char*)WBUFP(buf,6), date, 12); + safestrncpy((char*)WBUFP(buf,6 + 12), time_c, 9); + safestrncpy((char*)WBUFP(buf,6 + 12 + 9), timestring, 24); + + WBUFL(buf,6 + 12 + 9 + 24) = rev; + WBUFL(buf,6 + 12 + 9 + 24 + 4) = map_getusers(); + + WBUFL(buf,6 + 12 + 9 + 24 + 4 + 4) = config; + WBUFL(buf,6 + 12 + 9 + 24 + 4 + 4 + 4) = bd_size; + + for( i = 0; i < bd_size; i++ ) { + safestrncpy((char*)WBUFP(buf,6 + 12 + 9+ 24 + 4 + 4 + 4 + 4 + ( i * ( BFLAG_LENGTH + 4 ) ) ), battle_data[i].str, 35); + WBUFL(buf,6 + 12 + 9 + 24 + 4 + 4 + 4 + 4 + BFLAG_LENGTH + ( i * ( BFLAG_LENGTH + 4 ) ) ) = *battle_data[i].val; + } + + chrif_send_report(buf, 6 + 12 + 9 + 24 + 4 + 4 + 4 + 4 + ( bd_size * ( BFLAG_LENGTH + 4 ) ) ); + + aFree(buf); + +#undef BFLAG_LENGTH +} +static int rAthena_report_timer(int tid, unsigned int tick, int id, intptr_t data) { + if( chrif_isconnected() ) {/* char server relays it, so it must be online. */ + rAthena_report(__DATE__,__TIME__); + } + return 0; +} +#endif + +int battle_set_value(const char* w1, const char* w2) +{ + int val = config_switch(w2); + + int i; + ARR_FIND(0, ARRAYLENGTH(battle_data), i, strcmpi(w1, battle_data[i].str) == 0); + if (i == ARRAYLENGTH(battle_data)) + return 0; // not found + + if (val < battle_data[i].min || val > battle_data[i].max) + { + ShowWarning("Value for setting '%s': %s is invalid (min:%i max:%i)! Defaulting to %i...\n", w1, w2, battle_data[i].min, battle_data[i].max, battle_data[i].defval); + val = battle_data[i].defval; + } + + *battle_data[i].val = val; + return 1; +} + +int battle_get_value(const char* w1) +{ + int i; + ARR_FIND(0, ARRAYLENGTH(battle_data), i, strcmpi(w1, battle_data[i].str) == 0); + if (i == ARRAYLENGTH(battle_data)) + return 0; // not found + else + return *battle_data[i].val; +} + +void battle_set_defaults() +{ + int i; + for (i = 0; i < ARRAYLENGTH(battle_data); i++) + *battle_data[i].val = battle_data[i].defval; +} + +void battle_adjust_conf() +{ + battle_config.monster_max_aspd = 2000 - battle_config.monster_max_aspd*10; + battle_config.max_aspd = 2000 - battle_config.max_aspd*10; + battle_config.max_third_aspd = 2000 - battle_config.max_third_aspd*10; + battle_config.max_walk_speed = 100*DEFAULT_WALK_SPEED/battle_config.max_walk_speed; + battle_config.max_cart_weight *= 10; + + if(battle_config.max_def > 100 && !battle_config.weapon_defense_type) // added by [Skotlex] + battle_config.max_def = 100; + + if(battle_config.min_hitrate > battle_config.max_hitrate) + battle_config.min_hitrate = battle_config.max_hitrate; + + if(battle_config.pet_max_atk1 > battle_config.pet_max_atk2) //Skotlex + battle_config.pet_max_atk1 = battle_config.pet_max_atk2; + + if (battle_config.day_duration && battle_config.day_duration < 60000) // added by [Yor] + battle_config.day_duration = 60000; + if (battle_config.night_duration && battle_config.night_duration < 60000) // added by [Yor] + battle_config.night_duration = 60000; + +#if PACKETVER < 20100427 + if( battle_config.feature_buying_store ) { + ShowWarning("conf/battle/feature.conf buying_store is enabled but it requires PACKETVER 2010-04-27 or newer, disabling...\n"); + battle_config.feature_buying_store = 0; + } +#endif + +#if PACKETVER < 20100803 + if( battle_config.feature_search_stores ) { + ShowWarning("conf/battle/feature.conf search_stores is enabled but it requires PACKETVER 2010-08-03 or newer, disabling...\n"); + battle_config.feature_search_stores = 0; + } +#endif + +#ifndef CELL_NOSTACK + if (battle_config.cell_stack_limit != 1) + ShowWarning("Battle setting 'cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n"); +#endif +} + +int battle_config_read(const char* cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE* fp; + static int count = 0; + + if (count == 0) + battle_set_defaults(); + + count++; + + fp = fopen(cfgName,"r"); + if (fp == NULL) + ShowError("File not found: %s\n", cfgName); + else + { + while(fgets(line, sizeof(line), fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%1023[^:]:%1023s", w1, w2) != 2) + continue; + if (strcmpi(w1, "import") == 0) + battle_config_read(w2); + else + if (battle_set_value(w1, w2) == 0) + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + + fclose(fp); + } + + count--; + + if (count == 0) + battle_adjust_conf(); + + return 0; +} + +void do_init_battle(void) +{ + delay_damage_ers = ers_new(sizeof(struct delay_damage),"battle.c::delay_damage_ers",ERS_OPT_CLEAR); + add_timer_func_list(battle_delay_damage_sub, "battle_delay_damage_sub"); + +#ifndef STATS_OPT_OUT + add_timer_func_list(rAthena_report_timer, "rAthena_report_timer"); + add_timer_interval(gettick()+30000, rAthena_report_timer, 0, 0, 60000 * 30); +#endif + +} + +void do_final_battle(void) +{ + ers_destroy(delay_damage_ers); +} diff --git a/src/map/battle.h b/src/map/battle.h new file mode 100644 index 000000000..12ce62c8b --- /dev/null +++ b/src/map/battle.h @@ -0,0 +1,503 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _BATTLE_H_ +#define _BATTLE_H_ + +// state of a single attack attempt; used in flee/def penalty calculations when mobbed +typedef enum damage_lv { + ATK_NONE, // not an attack + ATK_LUCKY, // attack was lucky-dodged + ATK_FLEE, // attack was dodged + ATK_MISS, // attack missed because of element/race modifier. + ATK_BLOCK, // attack was blocked by some skills. + ATK_DEF // attack connected +} damage_lv; + +// dammage structure +struct Damage { + int damage,damage2; //right, left dmg + int type,div_; //chk clif_damage for type @TODO add an enum ? ; nb of hit + int amotion,dmotion; + int blewcount; //nb of knockback + int flag; //chk BF_* flag, (enum below) + enum damage_lv dmg_lv; //ATK_LUCKY,ATK_FLEE,ATK_DEF +}; + +//(Used in read pc.c,) attribute table (battle_attr_fix) +extern int attr_fix_table[4][10][10]; + +struct map_session_data; +struct mob_data; +struct block_list; + +// Damage Calculation + +struct Damage battle_calc_attack(int attack_type,struct block_list *bl,struct block_list *target,uint16 skill_id,uint16 skill_lv,int count); + +int battle_calc_return_damage(struct block_list *bl, struct block_list *src, int *, int flag, uint16 skill_id); + +void battle_drain(struct map_session_data *sd, struct block_list *tbl, int rdamage, int ldamage, int race, int boss); + +int battle_attr_ratio(int atk_elem,int def_type, int def_lv); +int battle_attr_fix(struct block_list *src, struct block_list *target, int damage,int atk_elem,int def_type, int def_lv); +int battle_calc_cardfix(int attack_type, struct block_list *src, struct block_list *target, int nk, int s_ele, int s_ele_, int damage, int left, int flag); + +// Final calculation Damage +int battle_calc_damage(struct block_list *src,struct block_list *bl,struct Damage *d,int damage,uint16 skill_id,uint16 skill_lv); +int battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int damage,int div_,uint16 skill_id,uint16 skill_lv,int flag); +int battle_calc_bg_damage(struct block_list *src,struct block_list *bl,int damage,int div_,uint16 skill_id,uint16 skill_lv,int flag); + +enum { // Flag of the final calculation + BF_WEAPON = 0x0001, + BF_MAGIC = 0x0002, + BF_MISC = 0x0004, + BF_SHORT = 0x0010, + BF_LONG = 0x0040, + BF_SKILL = 0x0100, + BF_NORMAL = 0x0200, + BF_WEAPONMASK=0x000f, + BF_RANGEMASK= 0x00f0, + BF_SKILLMASK= 0x0f00, +}; + +int battle_delay_damage (unsigned int tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int damage, enum damage_lv dmg_lv, int ddelay); + +// Summary normal attack treatment (basic attack) +enum damage_lv battle_weapon_attack( struct block_list *bl,struct block_list *target,unsigned int tick,int flag); + +// Accessors +struct block_list* battle_get_master(struct block_list *src); +struct block_list* battle_gettargeted(struct block_list *target); +struct block_list* battle_getenemy(struct block_list *target, int type, int range); +int battle_gettarget(struct block_list *bl); +int battle_getcurrentskill(struct block_list *bl); + +enum e_battle_check_target +{//New definitions [Skotlex] + BCT_ENEMY = 0x020000, + BCT_NOENEMY = 0x1d0000, //This should be (~BCT_ENEMY&BCT_ALL) + BCT_PARTY = 0x040000, + BCT_NOPARTY = 0x1b0000, //This should be (~BCT_PARTY&BCT_ALL) + BCT_GUILD = 0x080000, + BCT_NOGUILD = 0x170000, //This should be (~BCT_GUILD&BCT_ALL) + BCT_ALL = 0x1f0000, + BCT_NOONE = 0x000000, + BCT_SELF = 0x010000, + BCT_NEUTRAL = 0x100000, +}; + +#define is_boss(bl) (status_get_mode(bl)&MD_BOSS) // Can refine later [Aru] + +int battle_check_undead(int race,int element); +int battle_check_target(struct block_list *src, struct block_list *target,int flag); +bool battle_check_range(struct block_list *src,struct block_list *bl,int range); + +void battle_consume_ammo(struct map_session_data* sd, int skill, int lv); +// Settings + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +extern struct Battle_Config +{ + int warp_point_debug; + int enable_critical; + int mob_critical_rate; + int critical_rate; + int enable_baseatk; + int enable_perfect_flee; + int cast_rate, delay_rate; + int delay_dependon_dex, delay_dependon_agi; + int sdelay_attack_enable; + int left_cardfix_to_right; + int skill_add_range; + int skill_out_range_consume; + int skill_amotion_leniency; + int skillrange_by_distance; //[Skotlex] + int use_weapon_skill_range; //[Skotlex] + int pc_damage_delay_rate; + int defnotenemy; + int vs_traps_bctall; + int traps_setting; + int summon_flora; //[Skotlex] + int clear_unit_ondeath; //[Skotlex] + int clear_unit_onwarp; //[Skotlex] + int random_monster_checklv; + int attr_recover; + int item_auto_get; + int flooritem_lifetime; + int item_first_get_time; + int item_second_get_time; + int item_third_get_time; + int mvp_item_first_get_time; + int mvp_item_second_get_time; + int mvp_item_third_get_time; + int base_exp_rate,job_exp_rate; + int drop_rate0item; + int death_penalty_type; + int death_penalty_base,death_penalty_job; + int pvp_exp; // [MouseJstr] + int gtb_sc_immunity; + int zeny_penalty; + int restart_hp_rate; + int restart_sp_rate; + int mvp_exp_rate; + int mvp_hp_rate; + int monster_hp_rate; + int monster_max_aspd; + int view_range_rate; + int chase_range_rate; + int atc_spawn_quantity_limit; + int atc_slave_clone_limit; + int partial_name_scan; + int skillfree; + int skillup_limit; + int wp_rate; + int pp_rate; + int monster_active_enable; + int monster_damage_delay_rate; + int monster_loot_type; + int mob_skill_rate; //[Skotlex] + int mob_skill_delay; //[Skotlex] + int mob_count_rate; + int no_spawn_on_player; //[Skotlex] + int force_random_spawn; //[Skotlex] + int mob_spawn_delay, plant_spawn_delay, boss_spawn_delay; // [Skotlex] + int slaves_inherit_mode; + int slaves_inherit_speed; + int summons_trigger_autospells; + int pc_walk_delay_rate; //Adjusts can't walk delay after being hit for players. [Skotlex] + int walk_delay_rate; //Adjusts can't walk delay after being hit. [Skotlex] + int multihit_delay; //Adjusts can't walk delay per hit on multi-hitting skills. [Skotlex] + int quest_skill_learn; + int quest_skill_reset; + int basic_skill_check; + int guild_emperium_check; + int guild_exp_limit; + int guild_max_castles; + int guild_skill_relog_delay; + int emergency_call; + int guild_aura; + int pc_invincible_time; + + int pet_catch_rate; + int pet_rename; + int pet_friendly_rate; + int pet_hungry_delay_rate; + int pet_hungry_friendly_decrease; + int pet_status_support; + int pet_attack_support; + int pet_damage_support; + int pet_support_min_friendly; //[Skotlex] + int pet_equip_min_friendly; + int pet_support_rate; + int pet_attack_exp_to_master; + int pet_attack_exp_rate; + int pet_lv_rate; //[Skotlex] + int pet_max_stats; //[Skotlex] + int pet_max_atk1; //[Skotlex] + int pet_max_atk2; //[Skotlex] + int pet_no_gvg; //Disables pets in gvg. [Skotlex] + int pet_equip_required; + + int skill_min_damage; + int finger_offensive_type; + int heal_exp; + int max_heal_lv; + int max_heal; //Mitternacht + int resurrection_exp; + int shop_exp; + int combo_delay_rate; + int item_check; + int item_use_interval; //[Skotlex] + int cashfood_use_interval; + int wedding_modifydisplay; + int wedding_ignorepalette; //[Skotlex] + int xmas_ignorepalette; // [Valaris] + int summer_ignorepalette; // [Zephyrus] + int natural_healhp_interval; + int natural_healsp_interval; + int natural_heal_skill_interval; + int natural_heal_weight_rate; + int arrow_decrement; + int max_aspd; + int max_walk_speed; //Maximum walking speed after buffs [Skotlex] + int max_hp; + int max_sp; + int max_lv, aura_lv; + int max_parameter, max_baby_parameter; + int max_cart_weight; + int skill_log; + int battle_log; + int etc_log; + int save_clothcolor; + int undead_detect_type; + int auto_counter_type; + int min_hitrate; //[Skotlex] + int max_hitrate; //[Skotlex] + int agi_penalty_target; + int agi_penalty_type; + int agi_penalty_count; + int agi_penalty_num; + int vit_penalty_target; + int vit_penalty_type; + int vit_penalty_count; + int vit_penalty_num; + int weapon_defense_type; + int magic_defense_type; + int skill_reiteration; + int skill_nofootset; + int pc_cloak_check_type; + int monster_cloak_check_type; + int estimation_type; + int gvg_short_damage_rate; + int gvg_long_damage_rate; + int gvg_weapon_damage_rate; + int gvg_magic_damage_rate; + int gvg_misc_damage_rate; + int gvg_flee_penalty; + int pk_short_damage_rate; + int pk_long_damage_rate; + int pk_weapon_damage_rate; + int pk_magic_damage_rate; + int pk_misc_damage_rate; + int mob_changetarget_byskill; + int attack_direction_change; + int land_skill_limit; + int monster_class_change_recover; + int produce_item_name_input; + int display_skill_fail; + int chat_warpportal; + int mob_warp; + int dead_branch_active; + int vending_max_value; + int vending_over_max; + int vending_tax; + int show_steal_in_same_party; + int party_share_type; + int party_hp_mode; + int party_show_share_picker; + int show_picker_item_type; + int attack_attr_none; + int item_rate_mvp, item_rate_common, item_rate_common_boss, item_rate_card, item_rate_card_boss, + item_rate_equip, item_rate_equip_boss, item_rate_heal, item_rate_heal_boss, item_rate_use, + item_rate_use_boss, item_rate_treasure, item_rate_adddrop; + + int logarithmic_drops; + int item_drop_common_min,item_drop_common_max; // Added by TyrNemesis^ + int item_drop_card_min,item_drop_card_max; + int item_drop_equip_min,item_drop_equip_max; + int item_drop_mvp_min,item_drop_mvp_max; // End Addition + int item_drop_heal_min,item_drop_heal_max; // Added by Valatris + int item_drop_use_min,item_drop_use_max; //End + int item_drop_treasure_min,item_drop_treasure_max; //by [Skotlex] + int item_drop_adddrop_min,item_drop_adddrop_max; //[Skotlex] + + int prevent_logout; // Added by RoVeRT + + int alchemist_summon_reward; // [Valaris] + int drops_by_luk; + int drops_by_luk2; + int equip_natural_break_rate; //Base Natural break rate for attacks. + int equip_self_break_rate; //Natural & Penalty skills break rate + int equip_skill_break_rate; //Offensive skills break rate + int multi_level_up; + int max_exp_gain_rate; //Max amount of exp bar % you can get in one go. + int pk_mode; + int pk_level_range; + + int manner_system; // end additions [Valaris] + int show_mob_info; + + int gx_allhit; + int gx_disptype; + int devotion_level_difference; + int player_skill_partner_check; + int invite_request_check; + int skill_removetrap_type; + int disp_experience; + int disp_zeny; + int castle_defense_rate; + int backstab_bow_penalty; + int hp_rate; + int sp_rate; + int bone_drop; + int buyer_name; + int dancing_weaponswitch_fix; + +// eAthena additions + int night_at_start; // added by [Yor] + int day_duration; // added by [Yor] + int night_duration; // added by [Yor] + int ban_hack_trade; // added by [Yor] + int packet_ver_flag; // added by [Yor] + + int min_hair_style; // added by [MouseJstr] + int max_hair_style; // added by [MouseJstr] + int min_hair_color; // added by [MouseJstr] + int max_hair_color; // added by [MouseJstr] + int min_cloth_color; // added by [MouseJstr] + int max_cloth_color; // added by [MouseJstr] + int pet_hair_style; // added by [Skotlex] + + int castrate_dex_scale; // added by [MouseJstr] + int area_size; // added by [MouseJstr] + + int max_def, over_def_bonus; //added by [Skotlex] + + int zeny_from_mobs; // [Valaris] + int mobs_level_up; // [Valaris] + int mobs_level_up_exp_rate; // [Valaris] + int pk_min_level; // [celest] + int skill_steal_max_tries; //max steal skill tries on a mob. if 0, then w/o limit [Lupus] + int motd_type; // [celest] + int finding_ore_rate; // orn + int exp_calc_type; + int exp_bonus_attacker; + int exp_bonus_max_attacker; + int min_skill_delay_limit; + int default_walk_delay; + int no_skill_delay; + int attack_walk_delay; + int require_glory_guild; + int idle_no_share; + int party_update_interval; + int party_even_share_bonus; + int delay_battle_damage; + int hide_woe_damage; + int display_version; + + int display_hallucination; // [Skotlex] + int use_statpoint_table; // [Skotlex] + + int ignore_items_gender; //[Lupus] + + int copyskill_restrict; // [Aru] + int berserk_cancels_buffs; // [Aru] + int debuff_on_logout; // Removes a few "official" negative Scs on logout. [Skotlex] + int mob_ai; //Configures various mob_ai settings to make them smarter or dumber(official). [Skotlex] + int hom_setting; //Configures various homunc settings which make them behave unlike normal characters.. [Skotlex] + int dynamic_mobs; // Dynamic Mobs [Wizputer] - battle_athena flag implemented by [random] + int mob_remove_damaged; // Dynamic Mobs - Remove mobs even if damaged [Wizputer] + int mob_remove_delay; // Dynamic Mobs - delay before removing mobs from a map [Skotlex] + int mob_active_time; //Duration through which mobs execute their Hard AI after players leave their area of sight. + int boss_active_time; + + int show_hp_sp_drain, show_hp_sp_gain; //[Skotlex] + + int mob_npc_event_type; //Determines on who the npc_event is executed. [Skotlex] + + int character_size; // if riders have size=2, and baby class riders size=1 [Lupus] + int mob_max_skilllvl; // Max possible skill level [Lupus] + int rare_drop_announce; // chance <= to show rare drops global announces + + int retaliate_to_master; //Whether when a mob is attacked by another mob, it will retaliate versus the mob or the mob's master. [Skotlex] + + int duel_allow_pvp; // [LuzZza] + int duel_allow_gvg; // [LuzZza] + int duel_allow_teleport; // [LuzZza] + int duel_autoleave_when_die; // [LuzZza] + int duel_time_interval; // [LuzZza] + int duel_only_on_same_map; // [Toms] + + int skip_teleport_lv1_menu; // possibility to disable (skip) Teleport Lv1 menu, that have only two lines `Random` and `Cancel` [LuzZza] + + int allow_skill_without_day; // [Komurka] + int allow_es_magic_pc; // [Skotlex] + int skill_wall_check; // [Skotlex] + int cell_stack_limit; // [Skotlex] + int skill_caster_check; // [Skotlex] + int sc_castcancel; // [Skotlex] + int pc_sc_def_rate; // [Skotlex] + int mob_sc_def_rate; + int pc_luk_sc_def; + int mob_luk_sc_def; + int pc_max_sc_def; + int mob_max_sc_def; + + int sg_angel_skill_ratio; + int sg_miracle_skill_ratio; + int sg_miracle_skill_duration; + int autospell_stacking; //Enables autospell cards to stack. [Skotlex] + int override_mob_names; //Enables overriding spawn mob names with the mob_db names. [Skotlex] + int min_chat_delay; //Minimum time between client messages. [Skotlex] + int friend_auto_add; //When accepting friends, both get friended. [Skotlex] + int hvan_explosion_intimate; // fix [albator] + int hom_rename; + int homunculus_show_growth ; //[orn] + int homunculus_friendly_rate; + int quest_exp_rate; + int autotrade_mapflag; + int at_timeout; + int homunculus_autoloot; + int idle_no_autoloot; + int max_guild_alliance; + int ksprotection; + int auction_feeperhour; + int auction_maximumprice; + int homunculus_auto_vapor; //Keep Homunculus from Vaporizing when master dies. [L0ne_W0lf] + int display_status_timers; //Show or hide skill buff/delay timers in recent clients [Sara] + int skill_add_heal_rate; //skills that bHealPower has effect on [Inkfish] + int eq_single_target_reflectable; + int invincible_nodamage; + int mob_slave_keep_target; + int autospell_check_range; //Enable range check for autospell bonus. [L0ne_W0lf] + int client_reshuffle_dice; // Reshuffle /dice + int client_sort_storage; + int feature_buying_store; + int feature_search_stores; + int searchstore_querydelay; + int searchstore_maxresults; + int display_party_name; + int cashshop_show_points; + int mail_show_status; + int client_limit_unit_lv; + int hom_max_level; + int hom_S_max_level; + + // [BattleGround Settings] + int bg_update_interval; + int bg_short_damage_rate; + int bg_long_damage_rate; + int bg_weapon_damage_rate; + int bg_magic_damage_rate; + int bg_misc_damage_rate; + int bg_flee_penalty; + + // rAthena + int max_third_parameter; + int max_baby_third_parameter; + int atcommand_max_stat_bypass; + int max_third_aspd; + int vcast_stat_scale; + + int mvp_tomb_enabled; + + int atcommand_suggestions_enabled; + int min_npc_vending_distance; + int atcommand_mobinfo_type; + + int mob_size_influence; // Enable modifications on earned experience, drop rates and monster status depending on monster size. [mkbu95] +} battle_config; + +void do_init_battle(void); +void do_final_battle(void); +extern int battle_config_read(const char *cfgName); +extern void battle_validate_conf(void); +extern void battle_set_defaults(void); +int battle_set_value(const char* w1, const char* w2); +int battle_get_value(const char* w1); + +// +struct block_list* battle_getenemyarea(struct block_list *src, int x, int y, int range, int type, int ignore_id); +/** + * Royal Guard + **/ +int battle_damage_area( struct block_list *bl, va_list ap); + +#endif /* _BATTLE_H_ */ diff --git a/src/map/battleground.c b/src/map/battleground.c new file mode 100644 index 000000000..7b605066d --- /dev/null +++ b/src/map/battleground.c @@ -0,0 +1,258 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/strlib.h" + +#include "battleground.h" +#include "battle.h" +#include "clif.h" +#include "map.h" +#include "npc.h" +#include "pc.h" +#include "pet.h" +#include "homunculus.h" +#include "mercenary.h" + +#include <string.h> +#include <stdio.h> + +static DBMap* bg_team_db; // int bg_id -> struct battleground_data* +static unsigned int bg_team_counter = 0; // Next bg_id + +struct battleground_data* bg_team_search(int bg_id) +{ // Search a BG Team using bg_id + if( !bg_id ) return NULL; + return (struct battleground_data *)idb_get(bg_team_db, bg_id); +} + +struct map_session_data* bg_getavailablesd(struct battleground_data *bg) +{ + int i; + nullpo_retr(NULL, bg); + ARR_FIND(0, MAX_BG_MEMBERS, i, bg->members[i].sd != NULL); + return( i < MAX_BG_MEMBERS ) ? bg->members[i].sd : NULL; +} + +int bg_team_delete(int bg_id) +{ // Deletes BG Team from db + int i; + struct map_session_data *sd; + struct battleground_data *bg = bg_team_search(bg_id); + + if( bg == NULL ) return 0; + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL ) + continue; + + bg_send_dot_remove(sd); + sd->bg_id = 0; + } + idb_remove(bg_team_db, bg_id); + return 1; +} + +int bg_team_warp(int bg_id, unsigned short mapindex, short x, short y) +{ // Warps a Team + int i; + struct battleground_data *bg = bg_team_search(bg_id); + if( bg == NULL ) return 0; + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + if( bg->members[i].sd != NULL ) pc_setpos(bg->members[i].sd, mapindex, x, y, CLR_TELEPORT); + return 1; +} + +int bg_send_dot_remove(struct map_session_data *sd) +{ + if( sd && sd->bg_id ) + clif_bg_xy_remove(sd); + return 0; +} + +int bg_team_join(int bg_id, struct map_session_data *sd) +{ // Player joins team + int i; + struct battleground_data *bg = bg_team_search(bg_id); + struct map_session_data *pl_sd; + + if( bg == NULL || sd == NULL || sd->bg_id ) return 0; + + ARR_FIND(0, MAX_BG_MEMBERS, i, bg->members[i].sd == NULL); + if( i == MAX_BG_MEMBERS ) return 0; // No free slots + + sd->bg_id = bg_id; + bg->members[i].sd = sd; + bg->members[i].x = sd->bl.x; + bg->members[i].y = sd->bl.y; + bg->count++; + + guild_send_dot_remove(sd); + + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (pl_sd = bg->members[i].sd) != NULL && pl_sd != sd ) + clif_hpmeter_single(sd->fd, pl_sd->bl.id, pl_sd->battle_status.hp, pl_sd->battle_status.max_hp); + } + + clif_bg_hp(sd); + clif_bg_xy(sd); + return 1; +} + +int bg_team_leave(struct map_session_data *sd, int flag) +{ // Single Player leaves team + int i, bg_id; + struct battleground_data *bg; + char output[128]; + + if( sd == NULL || !sd->bg_id ) + return 0; + + bg_send_dot_remove(sd); + bg_id = sd->bg_id; + sd->bg_id = 0; + + if( (bg = bg_team_search(bg_id)) == NULL ) + return 0; + + ARR_FIND(0, MAX_BG_MEMBERS, i, bg->members[i].sd == sd); + if( i < MAX_BG_MEMBERS ) // Removes member from BG + memset(&bg->members[i], 0, sizeof(bg->members[0])); + bg->count--; + + if( flag ) + sprintf(output, "Server : %s has quit the game...", sd->status.name); + else + sprintf(output, "Server : %s is leaving the battlefield...", sd->status.name); + clif_bg_message(bg, 0, "Server", output, strlen(output) + 1); + + if( bg->logout_event[0] && flag ) + npc_event(sd, bg->logout_event, 0); + + return bg->count; +} + +int bg_member_respawn(struct map_session_data *sd) +{ // Respawn after killed + struct battleground_data *bg; + if( sd == NULL || !pc_isdead(sd) || !sd->bg_id || (bg = bg_team_search(sd->bg_id)) == NULL ) + return 0; + if( bg->mapindex == 0 ) + return 0; // Respawn not handled by Core + pc_setpos(sd, bg->mapindex, bg->x, bg->y, CLR_OUTSIGHT); + status_revive(&sd->bl, 1, 100); + + return 1; // Warped +} + +int bg_create(unsigned short mapindex, short rx, short ry, const char *ev, const char *dev) +{ + struct battleground_data *bg; + bg_team_counter++; + + CREATE(bg, struct battleground_data, 1); + bg->bg_id = bg_team_counter; + bg->count = 0; + bg->mapindex = mapindex; + bg->x = rx; + bg->y = ry; + safestrncpy(bg->logout_event, ev, sizeof(bg->logout_event)); + safestrncpy(bg->die_event, dev, sizeof(bg->die_event)); + + memset(&bg->members, 0, sizeof(bg->members)); + idb_put(bg_team_db, bg_team_counter, bg); + + return bg->bg_id; +} + +int bg_team_get_id(struct block_list *bl) +{ + nullpo_ret(bl); + switch( bl->type ) + { + case BL_PC: + return ((TBL_PC*)bl)->bg_id; + case BL_PET: + if( ((TBL_PET*)bl)->msd ) + return ((TBL_PET*)bl)->msd->bg_id; + break; + case BL_MOB: + { + struct map_session_data *msd; + struct mob_data *md = (TBL_MOB*)bl; + if( md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL ) + return msd->bg_id; + return md->bg_id; + } + case BL_HOM: + if( ((TBL_HOM*)bl)->master ) + return ((TBL_HOM*)bl)->master->bg_id; + break; + case BL_MER: + if( ((TBL_MER*)bl)->master ) + return ((TBL_MER*)bl)->master->bg_id; + break; + case BL_SKILL: + return ((TBL_SKILL*)bl)->group->bg_id; + } + + return 0; +} + +int bg_send_message(struct map_session_data *sd, const char *mes, int len) +{ + struct battleground_data *bg; + + nullpo_ret(sd); + if( sd->bg_id == 0 || (bg = bg_team_search(sd->bg_id)) == NULL ) + return 0; + clif_bg_message(bg, sd->bl.id, sd->status.name, mes, len); + return 0; +} + +/** + * @see DBApply + */ +int bg_send_xy_timer_sub(DBKey key, DBData *data, va_list ap) +{ + struct battleground_data *bg = db_data2ptr(data); + struct map_session_data *sd; + int i; + nullpo_ret(bg); + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL ) + continue; + if( sd->bl.x != bg->members[i].x || sd->bl.y != bg->members[i].y ) + { // xy update + bg->members[i].x = sd->bl.x; + bg->members[i].y = sd->bl.y; + clif_bg_xy(sd); + } + } + return 0; +} + +int bg_send_xy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + bg_team_db->foreach(bg_team_db, bg_send_xy_timer_sub, tick); + return 0; +} + +void do_init_battleground(void) +{ + bg_team_db = idb_alloc(DB_OPT_RELEASE_DATA); + add_timer_func_list(bg_send_xy_timer, "bg_send_xy_timer"); + add_timer_interval(gettick() + battle_config.bg_update_interval, bg_send_xy_timer, 0, 0, battle_config.bg_update_interval); +} + +void do_final_battleground(void) +{ + bg_team_db->destroy(bg_team_db, NULL); +} diff --git a/src/map/battleground.h b/src/map/battleground.h new file mode 100644 index 000000000..c2b74a534 --- /dev/null +++ b/src/map/battleground.h @@ -0,0 +1,45 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _BATTLEGROUND_H_ +#define _BATTLEGROUND_H_ + +#include "../common/mmo.h" // struct party +#include "guild.h" + +#define MAX_BG_MEMBERS 30 + +struct battleground_member_data { + unsigned short x, y; + struct map_session_data *sd; + unsigned afk : 1; +}; + +struct battleground_data { + unsigned int bg_id; + unsigned char count; + struct battleground_member_data members[MAX_BG_MEMBERS]; + // BG Cementery + unsigned short mapindex, x, y; + // Logout Event + char logout_event[EVENT_NAME_LENGTH]; + char die_event[EVENT_NAME_LENGTH]; +}; + +void do_init_battleground(void); +void do_final_battleground(void); + +struct battleground_data* bg_team_search(int bg_id); +int bg_send_dot_remove(struct map_session_data *sd); +int bg_team_get_id(struct block_list *bl); +struct map_session_data* bg_getavailablesd(struct battleground_data *bg); + +int bg_create(unsigned short mapindex, short rx, short ry, const char *ev, const char *dev); +int bg_team_join(int bg_id, struct map_session_data *sd); +int bg_team_delete(int bg_id); +int bg_team_leave(struct map_session_data *sd, int flag); +int bg_team_warp(int bg_id, unsigned short mapindex, short x, short y); +int bg_member_respawn(struct map_session_data *sd); +int bg_send_message(struct map_session_data *sd, const char *mes, int len); + +#endif /* _BATTLEGROUND_H_ */ diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c new file mode 100644 index 000000000..8e3c21bd4 --- /dev/null +++ b/src/map/buyingstore.c @@ -0,0 +1,473 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" // ARR_FIND +#include "../common/showmsg.h" // ShowWarning +#include "../common/socket.h" // RBUF* +#include "../common/strlib.h" // safestrncpy +#include "atcommand.h" // msg_txt +#include "battle.h" // battle_config.* +#include "buyingstore.h" // struct s_buyingstore +#include "clif.h" // clif_buyingstore_* +#include "log.h" // log_pick_pc, log_zeny +#include "pc.h" // struct map_session_data + + +/// constants (client-side restrictions) +#define BUYINGSTORE_MAX_PRICE 99990000 +#define BUYINGSTORE_MAX_AMOUNT 9999 + + +/// failure constants for clif functions +enum e_buyingstore_failure +{ + BUYINGSTORE_CREATE = 1, // "Failed to open buying store." + BUYINGSTORE_CREATE_OVERWEIGHT = 2, // "Total amount of then possessed items exceeds the weight limit by %d. Please re-enter." + BUYINGSTORE_TRADE_BUYER_ZENY = 3, // "All items within the buy limit were purchased." + BUYINGSTORE_TRADE_BUYER_NO_ITEMS = 4, // "All items were purchased." + BUYINGSTORE_TRADE_SELLER_FAILED = 5, // "The deal has failed." + BUYINGSTORE_TRADE_SELLER_COUNT = 6, // "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." + BUYINGSTORE_TRADE_SELLER_ZENY = 7, // "The trade failed, because the buyer is lacking required balance." + BUYINGSTORE_CREATE_NO_INFO = 8, // "No sale (purchase) information available." +}; + + +static unsigned int buyingstore_nextid = 0; +static const short buyingstore_blankslots[MAX_SLOTS] = { 0 }; // used when checking whether or not an item's card slots are blank + + +/// Returns unique buying store id +static unsigned int buyingstore_getuid(void) +{ + return buyingstore_nextid++; +} + + +bool buyingstore_setup(struct map_session_data* sd, unsigned char slots) +{ + if( !battle_config.feature_buying_store || sd->state.vending || sd->state.buyingstore || sd->state.trading || slots == 0 ) + { + return false; + } + + if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) + {// custom: mute limitation + return false; + } + + if( map[sd->bl.m].flag.novending ) + {// custom: no vending maps + clif_displaymessage(sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return false; + } + + if( map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) + {// custom: no vending cells + clif_displaymessage(sd->fd, msg_txt(204)); // "You can't open a shop on this cell." + return false; + } + + if( slots > MAX_BUYINGSTORE_SLOTS ) + { + ShowWarning("buyingstore_setup: Requested %d slots, but server supports only %d slots.\n", (int)slots, MAX_BUYINGSTORE_SLOTS); + slots = MAX_BUYINGSTORE_SLOTS; + } + + sd->buyingstore.slots = slots; + clif_buyingstore_open(sd); + + return true; +} + + +void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count) +{ + unsigned int i, weight, listidx; + struct item_data* id; + + if( !result || count == 0 ) + {// canceled, or no items + return; + } + + if( !battle_config.feature_buying_store || pc_istrading(sd) || sd->buyingstore.slots == 0 || count > sd->buyingstore.slots || zenylimit <= 0 || zenylimit > sd->status.zeny || !storename[0] ) + {// disabled or invalid input + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( !pc_can_give_items(sd) ) + {// custom: GM is not allowed to buy (give zeny) + sd->buyingstore.slots = 0; + clif_displaymessage(sd->fd, msg_txt(246)); + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) + {// custom: mute limitation + return; + } + + if( map[sd->bl.m].flag.novending ) + {// custom: no vending maps + clif_displaymessage(sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return; + } + + if( map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) + {// custom: no vending cells + clif_displaymessage(sd->fd, msg_txt(204)); // "You can't open a shop on this cell." + return; + } + + weight = sd->weight; + + // check item list + for( i = 0; i < count; i++ ) + {// itemlist: <name id>.W <amount>.W <price>.L + unsigned short nameid, amount; + int price, idx; + + nameid = RBUFW(itemlist,i*8+0); + amount = RBUFW(itemlist,i*8+2); + price = RBUFL(itemlist,i*8+4); + + if( ( id = itemdb_exists(nameid) ) == NULL || amount == 0 ) + {// invalid input + break; + } + + if( price <= 0 || price > BUYINGSTORE_MAX_PRICE ) + {// invalid price: unlike vending, items cannot be bought at 0 Zeny + break; + } + + if( !id->flag.buyingstore || !itemdb_cantrade_sub(id, pc_get_group_level(sd), pc_get_group_level(sd)) || ( idx = pc_search_inventory(sd, nameid) ) == -1 ) + {// restrictions: allowed, no character-bound items and at least one must be owned + break; + } + + if( sd->status.inventory[idx].amount+amount > BUYINGSTORE_MAX_AMOUNT ) + {// too many items of same kind + break; + } + + if( i ) + {// duplicate check. as the client does this too, only malicious intent should be caught here + ARR_FIND( 0, i, listidx, sd->buyingstore.items[listidx].nameid == nameid ); + if( listidx != i ) + {// duplicate + ShowWarning("buyingstore_create: Found duplicate item on buying list (nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", nameid, amount, sd->status.account_id, sd->status.char_id); + break; + } + } + + weight+= id->weight*amount; + sd->buyingstore.items[i].nameid = nameid; + sd->buyingstore.items[i].amount = amount; + sd->buyingstore.items[i].price = price; + } + + if( i != count ) + {// invalid item/amount/price + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( (sd->max_weight*90)/100 < weight ) + {// not able to carry all wanted items without getting overweight (90%) + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE_OVERWEIGHT, weight); + return; + } + + // success + sd->state.buyingstore = true; + sd->buyer_id = buyingstore_getuid(); + sd->buyingstore.zenylimit = zenylimit; + sd->buyingstore.slots = i; // store actual amount of items + safestrncpy(sd->message, storename, sizeof(sd->message)); + clif_buyingstore_myitemlist(sd); + clif_buyingstore_entry(sd); +} + + +void buyingstore_close(struct map_session_data* sd) +{ + if( sd->state.buyingstore ) + { + // invalidate data + sd->state.buyingstore = false; + memset(&sd->buyingstore, 0, sizeof(sd->buyingstore)); + + // notify other players + clif_buyingstore_disappear_entry(sd); + } +} + + +void buyingstore_open(struct map_session_data* sd, int account_id) +{ + struct map_session_data* pl_sd; + + if( !battle_config.feature_buying_store || pc_istrading(sd) ) + {// not allowed to sell + return; + } + + if( !pc_can_give_items(sd) ) + {// custom: GM is not allowed to sell + clif_displaymessage(sd->fd, msg_txt(246)); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore ) + {// not online or not buying + return; + } + + if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) ) + {// out of view range + return; + } + + // success + clif_buyingstore_itemlist(sd, pl_sd); +} + + +void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count) +{ + int zeny = 0; + unsigned int i, weight, listidx, k; + struct map_session_data* pl_sd; + + if( count == 0 ) + {// nothing to do + return; + } + + if( !battle_config.feature_buying_store || pc_istrading(sd) ) + {// not allowed to sell + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( !pc_can_give_items(sd) ) + {// custom: GM is not allowed to sell + clif_displaymessage(sd->fd, msg_txt(246)); + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id ) + {// not online, not buying or not same store + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) ) + {// out of view range + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + searchstore_clearremote(sd); + + if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit ) + {// buyer lost zeny in the mean time? fix the limit + pl_sd->buyingstore.zenylimit = pl_sd->status.zeny; + } + weight = pl_sd->weight; + + // check item list + for( i = 0; i < count; i++ ) + {// itemlist: <index>.W <name id>.W <amount>.W + unsigned short nameid, amount; + int index; + + index = RBUFW(itemlist,i*6+0)-2; + nameid = RBUFW(itemlist,i*6+2); + amount = RBUFW(itemlist,i*6+4); + + if( i ) + {// duplicate check. as the client does this too, only malicious intent should be caught here + ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index ); + if( k != i ) + {// duplicate + ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", + RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id); + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + } + + if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount ) + {// invalid input + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_get_group_level(sd), pc_get_group_level(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore_blankslots, sizeof(buyingstore_blankslots)) ) + {// non-tradable item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); + if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 ) + {// there is no such item or the buyer has already bought all of them + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( pl_sd->buyingstore.items[listidx].amount < amount ) + {// buyer does not need that much of the item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid); + return; + } + + if( pc_checkadditem(pl_sd, nameid, amount) == ADDITEM_OVERAMOUNT ) + {// buyer does not have enough space for this item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight ) + {// normally this is not supposed to happen, as the total weight is + // checked upon creation, but the buyer could have gained items + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + weight+= amount*sd->inventory_data[index]->weight; + + if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny ) + {// buyer does not have enough zeny + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid); + return; + } + zeny+= amount*pl_sd->buyingstore.items[listidx].price; + } + + // process item list + for( i = 0; i < count; i++ ) + {// itemlist: <index>.W <name id>.W <amount>.W + unsigned short nameid, amount; + int index; + + index = RBUFW(itemlist,i*6+0)-2; + nameid = RBUFW(itemlist,i*6+2); + amount = RBUFW(itemlist,i*6+4); + + ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); + zeny = amount*pl_sd->buyingstore.items[listidx].price; + + // move item + pc_additem(pl_sd, &sd->status.inventory[index], amount, LOG_TYPE_BUYING_STORE); + pc_delitem(sd, index, amount, 1, 0, LOG_TYPE_BUYING_STORE); + pl_sd->buyingstore.items[listidx].amount-= amount; + + // pay up + pc_payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd); + pc_getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd); + pl_sd->buyingstore.zenylimit-= zeny; + + // notify clients + clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price); + clif_buyingstore_update_item(pl_sd, nameid, amount); + } + + // check whether or not there is still something to buy + ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 ); + if( i == pl_sd->buyingstore.slots ) + {// everything was bought + clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS); + } + else if( pl_sd->buyingstore.zenylimit == 0 ) + {// zeny limit reached + clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY); + } + else + {// continue buying + return; + } + + // cannot continue buying + buyingstore_close(pl_sd); + + // remove auto-trader + if( pl_sd->state.autotrade ) + { + map_quit(pl_sd); + } +} + + +/// Checks if an item is being bought in given player's buying store. +bool buyingstore_search(struct map_session_data* sd, unsigned short nameid) +{ + unsigned int i; + + if( !sd->state.buyingstore ) + {// not buying + return false; + } + + ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == nameid && sd->buyingstore.items[i].amount ); + if( i == sd->buyingstore.slots ) + {// not found + return false; + } + + return true; +} + + +/// Searches for all items in a buyingstore, that match given ids, price and possible cards. +/// @return Whether or not the search should be continued. +bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s) +{ + unsigned int i, idx; + struct s_buyingstore_item* it; + + if( !sd->state.buyingstore ) + {// not buying + return true; + } + + for( idx = 0; idx < s->item_count; idx++ ) + { + ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == s->itemlist[idx] && sd->buyingstore.items[i].amount ); + if( i == sd->buyingstore.slots ) + {// not found + continue; + } + it = &sd->buyingstore.items[i]; + + if( s->min_price && s->min_price > (unsigned int)it->price ) + {// too low price + continue; + } + + if( s->max_price && s->max_price < (unsigned int)it->price ) + {// too high price + continue; + } + + if( s->card_count ) + {// ignore cards, as there cannot be any + ; + } + + if( !searchstore_result(s->search_sd, sd->buyer_id, sd->status.account_id, sd->message, it->nameid, it->amount, it->price, buyingstore_blankslots, 0) ) + {// result set full + return false; + } + } + + return true; +} diff --git a/src/map/buyingstore.h b/src/map/buyingstore.h new file mode 100644 index 000000000..0ed6e5457 --- /dev/null +++ b/src/map/buyingstore.h @@ -0,0 +1,33 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _BUYINGSTORE_H_ +#define _BUYINGSTORE_H_ + +struct s_search_store_search; + +#define MAX_BUYINGSTORE_SLOTS 5 + +struct s_buyingstore_item +{ + int price; + unsigned short amount; + unsigned short nameid; +}; + +struct s_buyingstore +{ + struct s_buyingstore_item items[MAX_BUYINGSTORE_SLOTS]; + int zenylimit; + unsigned char slots; +}; + +bool buyingstore_setup(struct map_session_data* sd, unsigned char slots); +void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count); +void buyingstore_close(struct map_session_data* sd); +void buyingstore_open(struct map_session_data* sd, int account_id); +void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count); +bool buyingstore_search(struct map_session_data* sd, unsigned short nameid); +bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s); + +#endif // _BUYINGSTORE_H_ diff --git a/src/map/chat.c b/src/map/chat.c new file mode 100644 index 000000000..dfeb16cad --- /dev/null +++ b/src/map/chat.c @@ -0,0 +1,425 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/mmo.h" +#include "atcommand.h" // msg_txt() +#include "battle.h" // struct battle_config +#include "clif.h" +#include "map.h" +#include "npc.h" // npc_event_do() +#include "pc.h" +#include "skill.h" // ext_skill_unit_onplace() +#include "chat.h" + +#include <stdio.h> +#include <string.h> + + +int chat_triggerevent(struct chat_data *cd); // forward declaration + +/// Initializes a chatroom object (common functionality for both pc and npc chatrooms). +/// Returns a chatroom object on success, or NULL on failure. +static struct chat_data* chat_createchat(struct block_list* bl, const char* title, const char* pass, int limit, bool pub, int trigger, const char* ev, int zeny, int minLvl, int maxLvl) +{ + struct chat_data* cd; + nullpo_retr(NULL, bl); + + cd = (struct chat_data *) aMalloc(sizeof(struct chat_data)); + + safestrncpy(cd->title, title, sizeof(cd->title)); + safestrncpy(cd->pass, pass, sizeof(cd->pass)); + cd->pub = pub; + cd->users = 0; + cd->limit = min(limit, ARRAYLENGTH(cd->usersd)); + cd->trigger = trigger; + cd->zeny = zeny; + cd->minLvl = minLvl; + cd->maxLvl = maxLvl; + memset(cd->usersd, 0, sizeof(cd->usersd)); + cd->owner = bl; + safestrncpy(cd->npc_event, ev, sizeof(cd->npc_event)); + + cd->bl.id = map_get_new_object_id(); + cd->bl.m = bl->m; + cd->bl.x = bl->x; + cd->bl.y = bl->y; + cd->bl.type = BL_CHAT; + cd->bl.next = cd->bl.prev = NULL; + + if( cd->bl.id == 0 ) + { + aFree(cd); + cd = NULL; + } + + map_addiddb(&cd->bl); + + if( bl->type != BL_NPC ) + cd->kick_list = idb_alloc(DB_OPT_BASE); + + return cd; +} + +/*========================================== + * player chatroom creation + *------------------------------------------*/ +int chat_createpcchat(struct map_session_data* sd, const char* title, const char* pass, int limit, bool pub) +{ + struct chat_data* cd; + nullpo_ret(sd); + + if( sd->chatID ) + return 0; //Prevent people abusing the chat system by creating multiple chats, as pointed out by End of Exam. [Skotlex] + + if( sd->state.vending || sd->state.buyingstore ) + {// not chat, when you already have a store open + return 0; + } + + if( map[sd->bl.m].flag.nochat ) + { + clif_displaymessage(sd->fd, msg_txt(281)); + return 0; //Can't create chatrooms on this map. + } + + if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNOCHAT) ) + { + clif_displaymessage (sd->fd, msg_txt(665)); + return 0; + } + + pc_stop_walking(sd,1); + + cd = chat_createchat(&sd->bl, title, pass, limit, pub, 0, "", 0, 1, MAX_LEVEL); + if( cd ) + { + cd->users = 1; + cd->usersd[0] = sd; + pc_setchatid(sd,cd->bl.id); + clif_createchat(sd,0); + clif_dispchat(cd,0); + } + else + clif_createchat(sd,1); + + return 0; +} + +/*========================================== + * join an existing chatroom + *------------------------------------------*/ +int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass) +{ + struct chat_data* cd; + + nullpo_ret(sd); + cd = (struct chat_data*)map_id2bl(chatid); + + if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->state.vending || sd->state.buyingstore || sd->chatID || ((cd->owner->type == BL_NPC) ? cd->users+1 : cd->users) >= cd->limit ) + { + clif_joinchatfail(sd,0); + return 0; + } + + if( !cd->pub && strncmp(pass, cd->pass, sizeof(cd->pass)) != 0 && !pc_has_permission(sd, PC_PERM_JOIN_ALL_CHAT) ) + { + clif_joinchatfail(sd,1); + return 0; + } + + if( sd->status.base_level < cd->minLvl || sd->status.base_level > cd->maxLvl ) { + if(sd->status.base_level < cd->minLvl) + clif_joinchatfail(sd,5); + else + clif_joinchatfail(sd,6); + + return 0; + } + + if( sd->status.zeny < cd->zeny ) { + clif_joinchatfail(sd,4); + return 0; + } + + if( cd->owner->type != BL_NPC && idb_exists(cd->kick_list,sd->status.char_id) ) { + clif_joinchatfail(sd,2);//You have been kicked out of the room. + return 0; + } + + pc_stop_walking(sd,1); + cd->usersd[cd->users] = sd; + cd->users++; + + pc_setchatid(sd,cd->bl.id); + + clif_joinchatok(sd, cd); //To the person who newly joined the list of all + clif_addchat(cd, sd); //Reports To the person who already in the chat + clif_dispchat(cd, 0); //Reported number of changes to the people around + + chat_triggerevent(cd); //Event + + return 0; +} + + +/*========================================== + * leave a chatroom + *------------------------------------------*/ +int chat_leavechat(struct map_session_data* sd, bool kicked) +{ + struct chat_data* cd; + int i; + int leavechar; + + nullpo_retr(1, sd); + + cd = (struct chat_data*)map_id2bl(sd->chatID); + if( cd == NULL ) + { + pc_setchatid(sd, 0); + return 1; + } + + ARR_FIND( 0, cd->users, i, cd->usersd[i] == sd ); + if ( i == cd->users ) + { // Not found in the chatroom? + pc_setchatid(sd, 0); + return -1; + } + + clif_leavechat(cd, sd, kicked); + pc_setchatid(sd, 0); + cd->users--; + + leavechar = i; + + for( i = leavechar; i < cd->users; i++ ) + cd->usersd[i] = cd->usersd[i+1]; + + + if( cd->users == 0 && cd->owner->type == BL_PC ) { // Delete empty chatroom + struct skill_unit* unit = NULL; + struct skill_unit_group* group = NULL; + + clif_clearchat(cd, 0); + db_destroy(cd->kick_list); + map_deliddb(&cd->bl); + map_delblock(&cd->bl); + map_freeblock(&cd->bl); + + unit = map_find_skill_unit_oncell(&sd->bl, sd->bl.x, sd->bl.y, AL_WARP, NULL, 0); + group = (unit != NULL) ? unit->group : NULL; + if (group != NULL) + ext_skill_unit_onplace(unit, &sd->bl, group->tick); + + return 1; + } + + if( leavechar == 0 && cd->owner->type == BL_PC ) + { // Set and announce new owner + cd->owner = (struct block_list*) cd->usersd[0]; + clif_changechatowner(cd, cd->usersd[0]); + clif_clearchat(cd, 0); + + //Adjust Chat location after owner has been changed. + map_delblock( &cd->bl ); + cd->bl.x=cd->usersd[0]->bl.x; + cd->bl.y=cd->usersd[0]->bl.y; + map_addblock( &cd->bl ); + + clif_dispchat(cd,0); + } + else + clif_dispchat(cd,0); // refresh chatroom + + return 0; +} + +/*========================================== + * change a chatroom's owner + *------------------------------------------*/ +int chat_changechatowner(struct map_session_data* sd, const char* nextownername) +{ + struct chat_data* cd; + struct map_session_data* tmpsd; + int i; + + nullpo_retr(1, sd); + + cd = (struct chat_data*)map_id2bl(sd->chatID); + if( cd == NULL || (struct block_list*) sd != cd->owner ) + return 1; + + ARR_FIND( 1, cd->users, i, strncmp(cd->usersd[i]->status.name, nextownername, NAME_LENGTH) == 0 ); + if( i == cd->users ) + return -1; // name not found + + // erase temporarily + clif_clearchat(cd,0); + + // set new owner + cd->owner = (struct block_list*) cd->usersd[i]; + clif_changechatowner(cd,cd->usersd[i]); + + // swap the old and new owners' positions + tmpsd = cd->usersd[i]; + cd->usersd[i] = cd->usersd[0]; + cd->usersd[0] = tmpsd; + + // set the new chatroom position + map_delblock( &cd->bl ); + cd->bl.x = cd->owner->x; + cd->bl.y = cd->owner->y; + map_addblock( &cd->bl ); + + // and display again + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * change a chatroom's status (title, etc) + *------------------------------------------*/ +int chat_changechatstatus(struct map_session_data* sd, const char* title, const char* pass, int limit, bool pub) +{ + struct chat_data* cd; + + nullpo_retr(1, sd); + + cd = (struct chat_data*)map_id2bl(sd->chatID); + if( cd==NULL || (struct block_list *)sd != cd->owner ) + return 1; + + safestrncpy(cd->title, title, CHATROOM_TITLE_SIZE); + safestrncpy(cd->pass, pass, CHATROOM_PASS_SIZE); + cd->limit = min(limit, ARRAYLENGTH(cd->usersd)); + cd->pub = pub; + + clif_changechatstatus(cd); + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * kick an user from a chatroom + *------------------------------------------*/ +int chat_kickchat(struct map_session_data* sd, const char* kickusername) +{ + struct chat_data* cd; + int i; + + nullpo_retr(1, sd); + + cd = (struct chat_data *)map_id2bl(sd->chatID); + + if( cd==NULL || (struct block_list *)sd != cd->owner ) + return -1; + + ARR_FIND( 0, cd->users, i, strncmp(cd->usersd[i]->status.name, kickusername, NAME_LENGTH) == 0 ); + if( i == cd->users ) + return -1; + + if (pc_has_permission(cd->usersd[i], PC_PERM_NO_CHAT_KICK)) + return 0; //gm kick protection [Valaris] + + idb_put(cd->kick_list,cd->usersd[i]->status.char_id,(void*)1); + + chat_leavechat(cd->usersd[i],1); + return 0; +} + +/// Creates a chat room for the npc. +int chat_createnpcchat(struct npc_data* nd, const char* title, int limit, bool pub, int trigger, const char* ev, int zeny, int minLvl, int maxLvl) +{ + struct chat_data* cd; + nullpo_ret(nd); + + if( nd->chat_id ) { + ShowError("chat_createnpcchat: npc '%s' already has a chatroom, cannot create new one!\n", nd->exname); + return 0; + } + + if( zeny > MAX_ZENY || maxLvl > MAX_LEVEL ) { + ShowError("chat_createnpcchat: npc '%s' has a required lvl or amount of zeny over the max limit!\n", nd->exname); + return 0; + } + + cd = chat_createchat(&nd->bl, title, "", limit, pub, trigger, ev, zeny, minLvl, maxLvl); + + if( cd ) { + nd->chat_id = cd->bl.id; + clif_dispchat(cd,0); + } + + return 0; +} + +/// Removes the chatroom from the npc. +int chat_deletenpcchat(struct npc_data* nd) +{ + struct chat_data *cd; + nullpo_ret(nd); + + cd = (struct chat_data*)map_id2bl(nd->chat_id); + if( cd == NULL ) + return 0; + + chat_npckickall(cd); + clif_clearchat(cd, 0); + map_deliddb(&cd->bl); + map_delblock(&cd->bl); + map_freeblock(&cd->bl); + nd->chat_id = 0; + + return 0; +} + +/*========================================== + * Trigger npc event when we enter the chatroom + *------------------------------------------*/ +int chat_triggerevent(struct chat_data *cd) +{ + nullpo_ret(cd); + + if( cd->users >= cd->trigger && cd->npc_event[0] ) + npc_event_do(cd->npc_event); + return 0; +} + +/// Enables the event of the chat room. +/// At most, 127 users are needed to trigger the event. +int chat_enableevent(struct chat_data* cd) +{ + nullpo_ret(cd); + + cd->trigger &= 0x7f; + chat_triggerevent(cd); + return 0; +} + +/// Disables the event of the chat room +int chat_disableevent(struct chat_data* cd) +{ + nullpo_ret(cd); + + cd->trigger |= 0x80; + return 0; +} + +/// Kicks all the users from the chat room. +int chat_npckickall(struct chat_data* cd) +{ + nullpo_ret(cd); + + while( cd->users > 0 ) + chat_leavechat(cd->usersd[cd->users-1],0); + + return 0; +} diff --git a/src/map/chat.h b/src/map/chat.h new file mode 100644 index 000000000..cb2e6ecd9 --- /dev/null +++ b/src/map/chat.h @@ -0,0 +1,43 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CHAT_H_ +#define _CHAT_H_ + +#include "map.h" // struct block_list, CHATROOM_TITLE_SIZE +struct map_session_data; +struct chat_data; + + +struct chat_data { + struct block_list bl; // data for this map object + char title[CHATROOM_TITLE_SIZE]; // room title + char pass[CHATROOM_PASS_SIZE]; // password + bool pub; // private/public flag + uint8 users; // current user count + uint8 limit; // join limit + uint8 trigger; // number of users needed to trigger event + uint32 zeny; // required zeny to join + uint32 minLvl; // minimum base level to join + uint32 maxLvl; // maximum base level allowed to join + struct map_session_data* usersd[20]; + struct block_list* owner; + char npc_event[EVENT_NAME_LENGTH]; + DBMap* kick_list; //DBMap of users who were kicked from this chat +}; + + +int chat_createpcchat(struct map_session_data* sd, const char* title, const char* pass, int limit, bool pub); +int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass); +int chat_leavechat(struct map_session_data* sd, bool kicked); +int chat_changechatowner(struct map_session_data* sd, const char* nextownername); +int chat_changechatstatus(struct map_session_data* sd, const char* title, const char* pass, int limit, bool pub); +int chat_kickchat(struct map_session_data* sd, const char* kickusername); + +int chat_createnpcchat(struct npc_data* nd, const char* title, int limit, bool pub, int trigger, const char* ev, int zeny, int minLvl, int maxLvl); +int chat_deletenpcchat(struct npc_data* nd); +int chat_enableevent(struct chat_data* cd); +int chat_disableevent(struct chat_data* cd); +int chat_npckickall(struct chat_data* cd); + +#endif /* _CHAT_H_ */ diff --git a/src/map/chrif.c b/src/map/chrif.c new file mode 100644 index 000000000..1575e11f4 --- /dev/null +++ b/src/map/chrif.c @@ -0,0 +1,1623 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/ers.h" + +#include "map.h" +#include "battle.h" +#include "clif.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "pet.h" +#include "skill.h" +#include "status.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "chrif.h" +#include "quest.h" +#include "storage.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <time.h> + +static int check_connect_char_server(int tid, unsigned int tick, int id, intptr_t data); + +static struct eri *auth_db_ers; //For reutilizing player login structures. +static DBMap* auth_db; // int id -> struct auth_node* + +static const int packet_len_table[0x3d] = { // U - used, F - free + 60, 3,-1,27,10,-1, 6,-1, // 2af8-2aff: U->2af8, U->2af9, U->2afa, U->2afb, U->2afc, U->2afd, U->2afe, U->2aff + 6,-1,18, 7,-1,39,30, 10, // 2b00-2b07: U->2b00, U->2b01, U->2b02, U->2b03, U->2b04, U->2b05, U->2b06, U->2b07 + 6,30, 0, 0,86, 7,44,34, // 2b08-2b0f: U->2b08, U->2b09, F->2b0a, F->2b0b, U->2b0c, U->2b0d, U->2b0e, U->2b0f + 11,10,10, 0,11, 0,266,10, // 2b10-2b17: U->2b10, U->2b11, U->2b12, F->2b13, U->2b14, F->2b15, U->2b16, U->2b17 + 2,10, 2,-1,-1,-1, 2, 7, // 2b18-2b1f: U->2b18, U->2b19, U->2b1a, U->2b1b, U->2b1c, U->2b1d, U->2b1e, U->2b1f + -1,10, 8, 2, 2,14,19,19, // 2b20-2b27: U->2b20, U->2b21, U->2b22, U->2b23, U->2b24, U->2b25, U->2b26, U->2b27 +}; + +//Used Packets: +//2af8: Outgoing, chrif_connect -> 'connect to charserver / auth @ charserver' +//2af9: Incoming, chrif_connectack -> 'answer of the 2af8 login(ok / fail)' +//2afa: Outgoing, chrif_sendmap -> 'sending our maps' +//2afb: Incoming, chrif_sendmapack -> 'Maps received successfully / or not ..' +//2afc: Outgoing, chrif_scdata_request -> request sc_data for pc_authok'ed char. <- new command reuses previous one. +//2afd: Incoming, chrif_authok -> 'client authentication ok' +//2afe: Outgoing, send_usercount_tochar -> 'sends player count of this map server to charserver' +//2aff: Outgoing, send_users_tochar -> 'sends all actual connected character ids to charserver' +//2b00: Incoming, map_setusers -> 'set the actual usercount? PACKET.2B COUNT.L.. ?' (not sure) +//2b01: Outgoing, chrif_save -> 'charsave of char XY account XY (complete struct)' +//2b02: Outgoing, chrif_charselectreq -> 'player returns from ingame to charserver to select another char.., this packets includes sessid etc' ? (not 100% sure) +//2b03: Incoming, clif_charselectok -> '' (i think its the packet after enterworld?) (not sure) +//2b04: Incoming, chrif_recvmap -> 'getting maps from charserver of other mapserver's' +//2b05: Outgoing, chrif_changemapserver -> 'Tell the charserver the mapchange / quest for ok...' +//2b06: Incoming, chrif_changemapserverack -> 'awnser of 2b05, ok/fail, data: dunno^^' +//2b07: Outgoing, chrif_removefriend -> 'Tell charserver to remove friend_id from char_id friend list' +//2b08: Outgoing, chrif_searchcharid -> '...' +//2b09: Incoming, map_addchariddb -> 'Adds a name to the nick db' +//2b0a: FREE +//2b0b: FREE +//2b0c: Outgoing, chrif_changeemail -> 'change mail address ...' +//2b0d: Incoming, chrif_changedsex -> 'Change sex of acc XY' +//2b0e: Outgoing, chrif_char_ask_name -> 'Do some operations (change sex, ban / unban etc)' +//2b0f: Incoming, chrif_char_ask_name_answer -> 'answer of the 2b0e' +//2b10: Outgoing, chrif_updatefamelist -> 'Update the fame ranking lists and send them' +//2b11: Outgoing, chrif_divorce -> 'tell the charserver to do divorce' +//2b12: Incoming, chrif_divorceack -> 'divorce chars +//2b13: FREE +//2b14: Incoming, chrif_accountban -> 'not sure: kick the player with message XY' +//2b15: FREE +//2b16: Outgoing, chrif_ragsrvinfo -> 'sends base / job / drop rates ....' +//2b17: Outgoing, chrif_char_offline -> 'tell the charserver that the char is now offline' +//2b18: Outgoing, chrif_char_reset_offline -> 'set all players OFF!' +//2b19: Outgoing, chrif_char_online -> 'tell the charserver that the char .. is online' +//2b1a: Outgoing, chrif_buildfamelist -> 'Build the fame ranking lists and send them' +//2b1b: Incoming, chrif_recvfamelist -> 'Receive fame ranking lists' +//2b1c: Outgoing, chrif_save_scdata -> 'Send sc_data of player for saving.' +//2b1d: Incoming, chrif_load_scdata -> 'received sc_data of player for loading.' +//2b1e: Incoming, chrif_update_ip -> 'Reqest forwarded from char-server for interserver IP sync.' [Lance] +//2b1f: Incoming, chrif_disconnectplayer -> 'disconnects a player (aid X) with the message XY ... 0x81 ..' [Sirius] +//2b20: Incoming, chrif_removemap -> 'remove maps of a server (sample: its going offline)' [Sirius] +//2b21: Incoming, chrif_save_ack. Returned after a character has been "final saved" on the char-server. [Skotlex] +//2b22: Incoming, chrif_updatefamelist_ack. Updated one position in the fame list. +//2b23: Outgoing, chrif_keepalive. charserver ping. +//2b24: Incoming, chrif_keepalive_ack. charserver ping reply. +//2b25: Incoming, chrif_deadopt -> 'Removes baby from Father ID and Mother ID' +//2b26: Outgoing, chrif_authreq -> 'client authentication request' +//2b27: Incoming, chrif_authfail -> 'client authentication failed' + +int chrif_connected = 0; +int char_fd = -1; +int srvinfo; +static char char_ip_str[128]; +static uint32 char_ip = 0; +static uint16 char_port = 6121; +static char userid[NAME_LENGTH], passwd[NAME_LENGTH]; +static int chrif_state = 0; +int other_mapserver_count=0; //Holds count of how many other map servers are online (apart of this instance) [Skotlex] + +//Interval at which map server updates online listing. [Valaris] +#define CHECK_INTERVAL 3600000 +//Interval at which map server sends number of connected users. [Skotlex] +#define UPDATE_INTERVAL 10000 +//This define should spare writing the check in every function. [Skotlex] +#define chrif_check(a) { if(!chrif_isconnected()) return a; } + + +/// Resets all the data. +void chrif_reset(void) { + // TODO kick everyone out and reset everything [FlavioJS] + exit(EXIT_FAILURE); +} + + +/// Checks the conditions for the server to stop. +/// Releases the cookie when all characters are saved. +/// If all the conditions are met, it stops the core loop. +void chrif_check_shutdown(void) { + if( runflag != MAPSERVER_ST_SHUTDOWN ) + return; + if( auth_db->size(auth_db) > 0 ) + return; + runflag = CORE_ST_STOP; +} + +struct auth_node* chrif_search(int account_id) { + return (struct auth_node*)idb_get(auth_db, account_id); +} + +struct auth_node* chrif_auth_check(int account_id, int char_id, enum sd_state state) { + struct auth_node *node = chrif_search(account_id); + + return ( node && node->char_id == char_id && node->state == state ) ? node : NULL; +} + +bool chrif_auth_delete(int account_id, int char_id, enum sd_state state) { + struct auth_node *node; + + if ( (node = chrif_auth_check(account_id, char_id, state) ) ) { + int fd = node->sd ? node->sd->fd : node->fd; + + if ( session[fd] && session[fd]->session_data == node->sd ) + session[fd]->session_data = NULL; + + if ( node->char_dat ) + aFree(node->char_dat); + + if ( node->sd ) + aFree(node->sd); + + ers_free(auth_db_ers, node); + idb_remove(auth_db,account_id); + + return true; + } + return false; +} + +//Moves the sd character to the auth_db structure. +static bool chrif_sd_to_auth(TBL_PC* sd, enum sd_state state) { + struct auth_node *node; + + if ( chrif_search(sd->status.account_id) ) + return false; //Already exists? + + node = ers_alloc(auth_db_ers, struct auth_node); + + memset(node, 0, sizeof(struct auth_node)); + + node->account_id = sd->status.account_id; + node->char_id = sd->status.char_id; + node->login_id1 = sd->login_id1; + node->login_id2 = sd->login_id2; + node->sex = sd->status.sex; + node->fd = sd->fd; + node->sd = sd; //Data from logged on char. + node->node_created = gettick(); //timestamp for node timeouts + node->state = state; + + sd->state.active = 0; + + idb_put(auth_db, node->account_id, node); + + return true; +} + +static bool chrif_auth_logout(TBL_PC* sd, enum sd_state state) { + + if(sd->fd && state == ST_LOGOUT) { //Disassociate player, and free it after saving ack returns. [Skotlex] + //fd info must not be lost for ST_MAPCHANGE as a final packet needs to be sent to the player. + if ( session[sd->fd] ) + session[sd->fd]->session_data = NULL; + sd->fd = 0; + } + + return chrif_sd_to_auth(sd, state); +} + +bool chrif_auth_finished(TBL_PC* sd) { + struct auth_node *node= chrif_search(sd->status.account_id); + + if ( node && node->sd == sd && node->state == ST_LOGIN ) { + node->sd = NULL; + + return chrif_auth_delete(node->account_id, node->char_id, ST_LOGIN); + } + + return false; +} +// sets char-server's user id +void chrif_setuserid(char *id) { + memcpy(userid, id, NAME_LENGTH); +} + +// sets char-server's password +void chrif_setpasswd(char *pwd) { + memcpy(passwd, pwd, NAME_LENGTH); +} + +// security check, prints warning if using default password +void chrif_checkdefaultlogin(void) { + if (strcmp(userid, "s1")==0 && strcmp(passwd, "p1")==0) { + ShowWarning("Using the default user/password s1/p1 is NOT RECOMMENDED.\n"); + ShowNotice("Please edit your 'login' table to create a proper inter-server user/password (gender 'S')\n"); + ShowNotice("and then edit your user/password in conf/map_athena.conf (or conf/import/map_conf.txt)\n"); + } +} + +// sets char-server's ip address +int chrif_setip(const char* ip) { + char ip_str[16]; + + if ( !( char_ip = host2ip(ip) ) ) { + ShowWarning("Failed to Resolve Char Server Address! (%s)\n", ip); + + return 0; + } + + strncpy(char_ip_str, ip, sizeof(char_ip_str)); + + ShowInfo("Char Server IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(char_ip, ip_str)); + + return 1; +} + +// sets char-server's port number +void chrif_setport(uint16 port) { + char_port = port; +} + +// says whether the char-server is connected or not +int chrif_isconnected(void) { + return (char_fd > 0 && session[char_fd] != NULL && chrif_state == 2); +} + +/*========================================== + * Saves character data. + * Flag = 1: Character is quitting + * Flag = 2: Character is changing map-servers + *------------------------------------------*/ +int chrif_save(struct map_session_data *sd, int flag) { + nullpo_retr(-1, sd); + + pc_makesavestatus(sd); + + if (flag && sd->state.active) { //Store player data which is quitting + //FIXME: SC are lost if there's no connection at save-time because of the way its related data is cleared immediately after this function. [Skotlex] + if ( chrif_isconnected() ) + chrif_save_scdata(sd); + if ( !chrif_auth_logout(sd,flag == 1 ? ST_LOGOUT : ST_MAPCHANGE) ) + ShowError("chrif_save: Failed to set up player %d:%d for proper quitting!\n", sd->status.account_id, sd->status.char_id); + } + + chrif_check(-1); //Character is saved on reconnect. + + //For data sync + if (sd->state.storage_flag == 2) + storage_guild_storagesave(sd->status.account_id, sd->status.guild_id, flag); + + if (flag) + sd->state.storage_flag = 0; //Force close it. + + //Saving of registry values. + if (sd->state.reg_dirty&4) + intif_saveregistry(sd, 3); //Save char regs + if (sd->state.reg_dirty&2) + intif_saveregistry(sd, 2); //Save account regs + if (sd->state.reg_dirty&1) + intif_saveregistry(sd, 1); //Save account2 regs + + WFIFOHEAD(char_fd, sizeof(sd->status) + 13); + WFIFOW(char_fd,0) = 0x2b01; + WFIFOW(char_fd,2) = sizeof(sd->status) + 13; + WFIFOL(char_fd,4) = sd->status.account_id; + WFIFOL(char_fd,8) = sd->status.char_id; + WFIFOB(char_fd,12) = (flag==1)?1:0; //Flag to tell char-server this character is quitting. + memcpy(WFIFOP(char_fd,13), &sd->status, sizeof(sd->status)); + WFIFOSET(char_fd, WFIFOW(char_fd,2)); + + if( sd->status.pet_id > 0 && sd->pd ) + intif_save_petdata(sd->status.account_id,&sd->pd->pet); + if( sd->hd && merc_is_hom_active(sd->hd) ) + merc_save(sd->hd); + if( sd->md && mercenary_get_lifetime(sd->md) > 0 ) + mercenary_save(sd->md); + if( sd->ed && elemental_get_lifetime(sd->ed) > 0 ) + elemental_save(sd->ed); + if( sd->save_quest ) + intif_quest_save(sd); + + return 0; +} + +// connects to char-server (plaintext) +int chrif_connect(int fd) { + ShowStatus("Logging in to char server...\n", char_fd); + WFIFOHEAD(fd,60); + WFIFOW(fd,0) = 0x2af8; + memcpy(WFIFOP(fd,2), userid, NAME_LENGTH); + memcpy(WFIFOP(fd,26), passwd, NAME_LENGTH); + WFIFOL(fd,50) = 0; + WFIFOL(fd,54) = htonl(clif_getip()); + WFIFOW(fd,58) = htons(clif_getport()); + WFIFOSET(fd,60); + + return 0; +} + +// sends maps to char-server +int chrif_sendmap(int fd) { + int i; + + ShowStatus("Sending maps to char server...\n"); + + // Sending normal maps, not instances + WFIFOHEAD(fd, 4 + instance_start * 4); + WFIFOW(fd,0) = 0x2afa; + for(i = 0; i < instance_start; i++) + WFIFOW(fd,4+i*4) = map[i].index; + WFIFOW(fd,2) = 4 + i * 4; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +// receive maps from some other map-server (relayed via char-server) +int chrif_recvmap(int fd) { + int i, j; + uint32 ip = ntohl(RFIFOL(fd,4)); + uint16 port = ntohs(RFIFOW(fd,8)); + + for(i = 10, j = 0; i < RFIFOW(fd,2); i += 4, j++) { + map_setipport(RFIFOW(fd,i), ip, port); + } + + if (battle_config.etc_log) + ShowStatus("Received maps from %d.%d.%d.%d:%d (%d maps)\n", CONVIP(ip), port, j); + + other_mapserver_count++; + + return 0; +} + +// remove specified maps (used when some other map-server disconnects) +int chrif_removemap(int fd) { + int i, j; + uint32 ip = RFIFOL(fd,4); + uint16 port = RFIFOW(fd,8); + + for(i = 10, j = 0; i < RFIFOW(fd, 2); i += 4, j++) + map_eraseipport(RFIFOW(fd, i), ip, port); + + other_mapserver_count--; + + if(battle_config.etc_log) + ShowStatus("remove map of server %d.%d.%d.%d:%d (%d maps)\n", CONVIP(ip), port, j); + + return 0; +} + +// received after a character has been "final saved" on the char-server +static void chrif_save_ack(int fd) { + chrif_auth_delete(RFIFOL(fd,2), RFIFOL(fd,6), ST_LOGOUT); + chrif_check_shutdown(); +} + +// request to move a character between mapservers +int chrif_changemapserver(struct map_session_data* sd, uint32 ip, uint16 port) { + nullpo_retr(-1, sd); + + if (other_mapserver_count < 1) {//No other map servers are online! + clif_authfail_fd(sd->fd, 0); + return -1; + } + + chrif_check(-1); + + WFIFOHEAD(char_fd,35); + WFIFOW(char_fd, 0) = 0x2b05; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = sd->status.char_id; + WFIFOW(char_fd,18) = sd->mapindex; + WFIFOW(char_fd,20) = sd->bl.x; + WFIFOW(char_fd,22) = sd->bl.y; + WFIFOL(char_fd,24) = htonl(ip); + WFIFOW(char_fd,28) = htons(port); + WFIFOB(char_fd,30) = sd->status.sex; + WFIFOL(char_fd,31) = htonl(session[sd->fd]->client_addr); + WFIFOL(char_fd,35) = sd->group_id; + WFIFOSET(char_fd,39); + + return 0; +} + +/// map-server change request acknowledgement (positive or negative) +/// R 2b06 <account_id>.L <login_id1>.L <login_id2>.L <char_id>.L <map_index>.W <x>.W <y>.W <ip>.L <port>.W +int chrif_changemapserverack(int account_id, int login_id1, int login_id2, int char_id, short map_index, short x, short y, uint32 ip, uint16 port) { + struct auth_node *node; + + if ( !( node = chrif_auth_check(account_id, char_id, ST_MAPCHANGE) ) ) + return -1; + + if ( !login_id1 ) { + ShowError("map server change failed.\n"); + clif_authfail_fd(node->fd, 0); + } else + clif_changemapserver(node->sd, map_index, x, y, ntohl(ip), ntohs(port)); + + //Player has been saved already, remove him from memory. [Skotlex] + chrif_auth_delete(account_id, char_id, ST_MAPCHANGE); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int chrif_connectack(int fd) { + static bool char_init_done = false; + + if (RFIFOB(fd,2)) { + ShowFatalError("Connection to char-server failed %d.\n", RFIFOB(fd,2)); + exit(EXIT_FAILURE); + } + + ShowStatus("Successfully logged on to Char Server (Connection: '"CL_WHITE"%d"CL_RESET"').\n",fd); + chrif_state = 1; + chrif_connected = 1; + + chrif_sendmap(fd); + + ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInit")); + if( !char_init_done ) { + char_init_done = true; + ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInitOnce")); + guild_castle_map_init(); + } + + return 0; +} + +/** + * @see DBApply + */ +static int chrif_reconnect(DBKey key, DBData *data, va_list ap) { + struct auth_node *node = db_data2ptr(data); + + switch (node->state) { + case ST_LOGIN: + if ( node->sd && node->char_dat == NULL ) {//Since there is no way to request the char auth, make it fail. + pc_authfail(node->sd); + chrif_char_offline(node->sd); + chrif_auth_delete(node->account_id, node->char_id, ST_LOGIN); + } + break; + case ST_LOGOUT: + //Re-send final save + chrif_save(node->sd, 1); + break; + case ST_MAPCHANGE: { //Re-send map-change request. + struct map_session_data *sd = node->sd; + uint32 ip; + uint16 port; + + if( map_mapname2ipport(sd->mapindex,&ip,&port) == 0 ) + chrif_changemapserver(sd, ip, port); + else //too much lag/timeout is the closest explanation for this error. + clif_authfail_fd(sd->fd, 3); + + break; + } + } + + return 0; +} + + +/// Called when all the connection steps are completed. +void chrif_on_ready(void) { + ShowStatus("Map Server is now online.\n"); + + chrif_state = 2; + + chrif_check_shutdown(); + + //If there are players online, send them to the char-server. [Skotlex] + send_users_tochar(); + + //Auth db reconnect handling + auth_db->foreach(auth_db,chrif_reconnect); + + //Re-save any storages that were modified in the disconnection time. [Skotlex] + do_reconnect_storage(); + + //Re-save any guild castles that were modified in the disconnection time. + guild_castle_reconnect(-1, 0, 0); +} + + +/*========================================== + * + *------------------------------------------*/ +int chrif_sendmapack(int fd) { + + if (RFIFOB(fd,2)) { + ShowFatalError("chrif : send map list to char server failed %d\n", RFIFOB(fd,2)); + exit(EXIT_FAILURE); + } + + memcpy(wisp_server_name, RFIFOP(fd,3), NAME_LENGTH); + + chrif_on_ready(); + + return 0; +} + +/*========================================== + * Request sc_data from charserver [Skotlex] + *------------------------------------------*/ +int chrif_scdata_request(int account_id, int char_id) { + +#ifdef ENABLE_SC_SAVING + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2afc; + WFIFOL(char_fd,2) = account_id; + WFIFOL(char_fd,6) = char_id; + WFIFOSET(char_fd,10); +#endif + + return 0; +} + +/*========================================== + * Request auth confirmation + *------------------------------------------*/ +void chrif_authreq(struct map_session_data *sd) { + struct auth_node *node= chrif_search(sd->bl.id); + + if( node != NULL || !chrif_isconnected() ) { + set_eof(sd->fd); + return; + } + + WFIFOHEAD(char_fd,19); + WFIFOW(char_fd,0) = 0x2b26; + WFIFOL(char_fd,2) = sd->status.account_id; + WFIFOL(char_fd,6) = sd->status.char_id; + WFIFOL(char_fd,10) = sd->login_id1; + WFIFOB(char_fd,14) = sd->status.sex; + WFIFOL(char_fd,15) = htonl(session[sd->fd]->client_addr); + WFIFOSET(char_fd,19); + chrif_sd_to_auth(sd, ST_LOGIN); +} + +/*========================================== + * Auth confirmation ack + *------------------------------------------*/ +void chrif_authok(int fd) { + int account_id, group_id, char_id; + uint32 login_id1,login_id2; + time_t expiration_time; + struct mmo_charstatus* status; + struct auth_node *node; + bool changing_mapservers; + TBL_PC* sd; + + //Check if both servers agree on the struct's size + if( RFIFOW(fd,2) - 25 != sizeof(struct mmo_charstatus) ) { + ShowError("chrif_authok: Data size mismatch! %d != %d\n", RFIFOW(fd,2) - 25, sizeof(struct mmo_charstatus)); + return; + } + + account_id = RFIFOL(fd,4); + login_id1 = RFIFOL(fd,8); + login_id2 = RFIFOL(fd,12); + expiration_time = (time_t)(int32)RFIFOL(fd,16); + group_id = RFIFOL(fd,20); + changing_mapservers = (RFIFOB(fd,24)); + status = (struct mmo_charstatus*)RFIFOP(fd,25); + char_id = status->char_id; + + //Check if we don't already have player data in our server + //Causes problems if the currently connected player tries to quit or this data belongs to an already connected player which is trying to re-auth. + if ( ( sd = map_id2sd(account_id) ) != NULL ) + return; + + if ( ( node = chrif_search(account_id) ) == NULL ) + return; // should not happen + + if ( node->state != ST_LOGIN ) + return; //character in logout phase, do not touch that data. + + if ( node->sd == NULL ) { + /* + //When we receive double login info and the client has not connected yet, + //discard the older one and keep the new one. + chrif_auth_delete(node->account_id, node->char_id, ST_LOGIN); + */ + return; // should not happen + } + + sd = node->sd; + + if( runflag == MAPSERVER_ST_RUNNING && + node->char_dat == NULL && + node->account_id == account_id && + node->char_id == char_id && + node->login_id1 == login_id1 ) + { //Auth Ok + if (pc_authok(sd, login_id2, expiration_time, group_id, status, changing_mapservers)) + return; + } else { //Auth Failed + pc_authfail(sd); + } + + chrif_char_offline(sd); //Set him offline, the char server likely has it set as online already. + chrif_auth_delete(account_id, char_id, ST_LOGIN); +} + +// client authentication failed +void chrif_authfail(int fd) {/* HELLO WORLD. ip in RFIFOL 15 is not being used (but is available) */ + int account_id, char_id; + uint32 login_id1; + char sex; + struct auth_node* node; + + account_id = RFIFOL(fd,2); + char_id = RFIFOL(fd,6); + login_id1 = RFIFOL(fd,10); + sex = RFIFOB(fd,14); + + node = chrif_search(account_id); + + if( node != NULL && + node->account_id == account_id && + node->char_id == char_id && + node->login_id1 == login_id1 && + node->sex == sex && + node->state == ST_LOGIN ) + {// found a match + clif_authfail_fd(node->fd, 0); + chrif_auth_delete(account_id, char_id, ST_LOGIN); + } +} + + +/** + * This can still happen (client times out while waiting for char to confirm auth data) + * @see DBApply + */ +int auth_db_cleanup_sub(DBKey key, DBData *data, va_list ap) { + struct auth_node *node = db_data2ptr(data); + const char* states[] = { "Login", "Logout", "Map change" }; + + if(DIFF_TICK(gettick(),node->node_created)>60000) { + switch (node->state) { + case ST_LOGOUT: + //Re-save attempt (->sd should never be null here). + node->node_created = gettick(); //Refresh tick (avoid char-server load if connection is really bad) + chrif_save(node->sd, 1); + break; + default: + //Clear data. any connected players should have timed out by now. + ShowInfo("auth_db: Node (state %s) timed out for %d:%d\n", states[node->state], node->account_id, node->char_id); + chrif_char_offline_nsd(node->account_id, node->char_id); + chrif_auth_delete(node->account_id, node->char_id, node->state); + break; + } + return 1; + } + return 0; +} + +int auth_db_cleanup(int tid, unsigned int tick, int id, intptr_t data) { + chrif_check(0); + auth_db->foreach(auth_db, auth_db_cleanup_sub); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int chrif_charselectreq(struct map_session_data* sd, uint32 s_ip) { + nullpo_retr(-1, sd); + + if( !sd || !sd->bl.id || !sd->login_id1 ) + return -1; + + chrif_check(-1); + + WFIFOHEAD(char_fd,18); + WFIFOW(char_fd, 0) = 0x2b02; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = htonl(s_ip); + WFIFOSET(char_fd,18); + + return 0; +} + +/*========================================== + * Search Char trough id on char serv + *------------------------------------------*/ +int chrif_searchcharid(int char_id) { + + if( !char_id ) + return -1; + + chrif_check(-1); + + WFIFOHEAD(char_fd,6); + WFIFOW(char_fd,0) = 0x2b08; + WFIFOL(char_fd,2) = char_id; + WFIFOSET(char_fd,6); + + return 0; +} + +/*========================================== + * Change Email + *------------------------------------------*/ +int chrif_changeemail(int id, const char *actual_email, const char *new_email) { + + if (battle_config.etc_log) + ShowInfo("chrif_changeemail: account: %d, actual_email: '%s', new_email: '%s'.\n", id, actual_email, new_email); + + chrif_check(-1); + + WFIFOHEAD(char_fd,86); + WFIFOW(char_fd,0) = 0x2b0c; + WFIFOL(char_fd,2) = id; + memcpy(WFIFOP(char_fd,6), actual_email, 40); + memcpy(WFIFOP(char_fd,46), new_email, 40); + WFIFOSET(char_fd,86); + + return 0; +} + +/*========================================== + * S 2b0e <accid>.l <name>.24B <type>.w { <year>.w <month>.w <day>.w <hour>.w <minute>.w <second>.w } + * Send an account modification request to the login server (via char server). + * type of operation: + * 1: block, 2: ban, 3: unblock, 4: unban, 5: changesex (use next function for 5) + *------------------------------------------*/ +int chrif_char_ask_name(int acc, const char* character_name, unsigned short operation_type, int year, int month, int day, int hour, int minute, int second) { + + chrif_check(-1); + + WFIFOHEAD(char_fd,44); + WFIFOW(char_fd,0) = 0x2b0e; + WFIFOL(char_fd,2) = acc; + safestrncpy((char*)WFIFOP(char_fd,6), character_name, NAME_LENGTH); + WFIFOW(char_fd,30) = operation_type; + + if ( operation_type == 2 ) { + WFIFOW(char_fd,32) = year; + WFIFOW(char_fd,34) = month; + WFIFOW(char_fd,36) = day; + WFIFOW(char_fd,38) = hour; + WFIFOW(char_fd,40) = minute; + WFIFOW(char_fd,42) = second; + } + + WFIFOSET(char_fd,44); + return 0; +} + +int chrif_changesex(struct map_session_data *sd) { + chrif_check(-1); + + WFIFOHEAD(char_fd,44); + WFIFOW(char_fd,0) = 0x2b0e; + WFIFOL(char_fd,2) = sd->status.account_id; + safestrncpy((char*)WFIFOP(char_fd,6), sd->status.name, NAME_LENGTH); + WFIFOW(char_fd,30) = 5; + WFIFOSET(char_fd,44); + + clif_displaymessage(sd->fd, msg_txt(408)); //"Need disconnection to perform change-sex request..." + + if (sd->fd) + clif_authfail_fd(sd->fd, 15); + else + map_quit(sd); + return 0; +} + +/*========================================== + * R 2b0f <accid>.l <name>.24B <type>.w <answer>.w + * Processing a reply to chrif_char_ask_name() (request to modify an account). + * type of operation: + * 1: block, 2: ban, 3: unblock, 4: unban, 5: changesex + * type of answer: + * 0: login-server request done + * 1: player not found + * 2: gm level too low + * 3: login-server offline + *------------------------------------------*/ +static void chrif_char_ask_name_answer(int acc, const char* player_name, uint16 type, uint16 answer) { + struct map_session_data* sd; + char action[25]; + char output[256]; + + sd = map_id2sd(acc); + + if( acc < 0 || sd == NULL ) { + ShowError("chrif_char_ask_name_answer failed - player not online.\n"); + return; + } + + if( type > 0 && type <= 5 ) + snprintf(action,25,"%s",msg_txt(427+type)); //block|ban|unblock|unban|change the sex of + else + snprintf(action,25,"???"); + + switch( answer ) { + case 0 : sprintf(output, msg_txt(424), action, NAME_LENGTH, player_name); break; + case 1 : sprintf(output, msg_txt(425), NAME_LENGTH, player_name); break; + case 2 : sprintf(output, msg_txt(426), action, NAME_LENGTH, player_name); break; + case 3 : sprintf(output, msg_txt(427), action, NAME_LENGTH, player_name); break; + default: output[0] = '\0'; break; + } + + clif_displaymessage(sd->fd, output); +} + +/*========================================== + * Request char server to change sex of char (modified by Yor) + *------------------------------------------*/ +int chrif_changedsex(int fd) { + int acc, sex, i; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + sex = RFIFOL(fd,6); + + if ( battle_config.etc_log ) + ShowNotice("chrif_changedsex %d.\n", acc); + + sd = map_id2sd(acc); + if ( sd ) { //Normally there should not be a char logged on right now! + if ( sd->status.sex == sex ) + return 0; //Do nothing? Likely safe. + sd->status.sex = !sd->status.sex; + + // reset skill of some job + if ((sd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER) { + // remove specifical skills of Bard classes + for(i = 315; i <= 322; i++) { + if (sd->status.skill[i].id > 0 && sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + // remove specifical skills of Dancer classes + for(i = 323; i <= 330; i++) { + if (sd->status.skill[i].id > 0 && sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + clif_updatestatus(sd, SP_SKILLPOINT); + // change job if necessary + if (sd->status.sex) //Changed from Dancer + sd->status.class_ -= 1; + else //Changed from Bard + sd->status.class_ += 1; + //sd->class_ needs not be updated as both Dancer/Bard are the same. + } + // save character + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) + clif_displaymessage(sd->fd, msg_txt(409)); //"Your sex has been changed (need disconnection by the server)..." + set_eof(sd->fd); // forced to disconnect for the change + map_quit(sd); // Remove leftovers (e.g. autotrading) [Paradox924X] + } + return 0; +} +/*========================================== + * Request Char Server to Divorce Players + *------------------------------------------*/ +int chrif_divorce(int partner_id1, int partner_id2) { + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2b11; + WFIFOL(char_fd,2) = partner_id1; + WFIFOL(char_fd,6) = partner_id2; + WFIFOSET(char_fd,10); + + return 0; +} + +/*========================================== + * Divorce players + * only used if 'partner_id' is offline + *------------------------------------------*/ +int chrif_divorceack(int char_id, int partner_id) { + struct map_session_data* sd; + int i; + + if( !char_id || !partner_id ) + return 0; + + if( ( sd = map_charid2sd(char_id) ) != NULL && sd->status.partner_id == partner_id ) { + sd->status.partner_id = 0; + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); + } + + if( ( sd = map_charid2sd(partner_id) ) != NULL && sd->status.partner_id == char_id ) { + sd->status.partner_id = 0; + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); + } + + return 0; +} +/*========================================== + * Removes Baby from parents + *------------------------------------------*/ +int chrif_deadopt(int father_id, int mother_id, int child_id) { + struct map_session_data* sd; + + if( father_id && ( sd = map_charid2sd(father_id) ) != NULL && sd->status.child == child_id ) { + sd->status.child = 0; + sd->status.skill[WE_CALLBABY].id = 0; + sd->status.skill[WE_CALLBABY].lv = 0; + sd->status.skill[WE_CALLBABY].flag = 0; + clif_deleteskill(sd,WE_CALLBABY); + } + + if( mother_id && ( sd = map_charid2sd(mother_id) ) != NULL && sd->status.child == child_id ) { + sd->status.child = 0; + sd->status.skill[WE_CALLBABY].id = 0; + sd->status.skill[WE_CALLBABY].lv = 0; + sd->status.skill[WE_CALLBABY].flag = 0; + clif_deleteskill(sd,WE_CALLBABY); + } + + return 0; +} + +/*========================================== + * Disconnection of a player (account has been banned of has a status, from login-server) by [Yor] + *------------------------------------------*/ +int chrif_accountban(int fd) { + int acc; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + + if ( battle_config.etc_log ) + ShowNotice("chrif_accountban %d.\n", acc); + + sd = map_id2sd(acc); + + if ( acc < 0 || sd == NULL ) { + ShowError("chrif_accountban failed - player not online.\n"); + return 0; + } + + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + if (RFIFOB(fd,6) == 0) { // 0: change of statut, 1: ban + int ret_status = RFIFOL(fd,7); // status or final date of a banishment + if(0<ret_status && ret_status<=9) + clif_displaymessage(sd->fd, msg_txt(411+ret_status)); + else if(ret_status==100) + clif_displaymessage(sd->fd, msg_txt(421)); + else + clif_displaymessage(sd->fd, msg_txt(420)); //"Your account has not more authorised." + } else if (RFIFOB(fd,6) == 1) { // 0: change of statut, 1: ban + time_t timestamp; + char tmpstr[2048]; + timestamp = (time_t)RFIFOL(fd,7); // status or final date of a banishment + strcpy(tmpstr, msg_txt(423)); //"Your account has been banished until " + strftime(tmpstr + strlen(tmpstr), 24, "%d-%m-%Y %H:%M:%S", localtime(×tamp)); + clif_displaymessage(sd->fd, tmpstr); + } + + set_eof(sd->fd); // forced to disconnect for the change + map_quit(sd); // Remove leftovers (e.g. autotrading) [Paradox924X] + return 0; +} + +//Disconnect the player out of the game, simple packet +//packet.w AID.L WHY.B 2+4+1 = 7byte +int chrif_disconnectplayer(int fd) { + struct map_session_data* sd; + int account_id = RFIFOL(fd, 2); + + sd = map_id2sd(account_id); + if( sd == NULL ) { + struct auth_node* auth = chrif_search(account_id); + + if( auth != NULL && chrif_auth_delete(account_id, auth->char_id, ST_LOGIN) ) + return 0; + + return -1; + } + + if (!sd->fd) { //No connection + if (sd->state.autotrade) + map_quit(sd); //Remove it. + //Else we don't remove it because the char should have a timer to remove the player because it force-quit before, + //and we don't want them kicking their previous instance before the 10 secs penalty time passes. [Skotlex] + return 0; + } + + switch(RFIFOB(fd, 6)) { + case 1: clif_authfail_fd(sd->fd, 1); break; //server closed + case 2: clif_authfail_fd(sd->fd, 2); break; //someone else logged in + case 3: clif_authfail_fd(sd->fd, 4); break; //server overpopulated + case 4: clif_authfail_fd(sd->fd, 10); break; //out of available time paid for + case 5: clif_authfail_fd(sd->fd, 15); break; //forced to dc by gm + } + return 0; +} + +/*========================================== + * Request/Receive top 10 Fame character list + *------------------------------------------*/ +int chrif_updatefamelist(struct map_session_data* sd) { + char type; + + chrif_check(-1); + + switch(sd->class_ & MAPID_UPPERMASK) { + case MAPID_BLACKSMITH: type = 1; break; + case MAPID_ALCHEMIST: type = 2; break; + case MAPID_TAEKWON: type = 3; break; + default: + return 0; + } + + WFIFOHEAD(char_fd, 11); + WFIFOW(char_fd,0) = 0x2b10; + WFIFOL(char_fd,2) = sd->status.char_id; + WFIFOL(char_fd,6) = sd->status.fame; + WFIFOB(char_fd,10) = type; + WFIFOSET(char_fd,11); + + return 0; +} + +int chrif_buildfamelist(void) { + chrif_check(-1); + + WFIFOHEAD(char_fd,2); + WFIFOW(char_fd,0) = 0x2b1a; + WFIFOSET(char_fd,2); + + return 0; +} + +int chrif_recvfamelist(int fd) { + int num, size; + int total = 0, len = 8; + + 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)); + + size = RFIFOW(fd, 6); //Blacksmith block size + + for (num = 0; len < size && num < MAX_FAME_LIST; num++) { + memcpy(&smith_fame_list[num], RFIFOP(fd,len), sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + + total += num; + + size = RFIFOW(fd, 4); //Alchemist block size + + for (num = 0; len < size && num < MAX_FAME_LIST; num++) { + memcpy(&chemist_fame_list[num], RFIFOP(fd,len), sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + + total += num; + + size = RFIFOW(fd, 2); //Total packet length + + for (num = 0; len < size && num < MAX_FAME_LIST; num++) { + memcpy(&taekwon_fame_list[num], RFIFOP(fd,len), sizeof(struct fame_list)); + len += sizeof(struct fame_list); + } + + total += num; + + ShowInfo("Received Fame List of '"CL_WHITE"%d"CL_RESET"' characters.\n", total); + + return 0; +} + +/// fame ranking update confirmation +/// R 2b22 <table>.B <index>.B <value>.L +int chrif_updatefamelist_ack(int fd) { + struct fame_list* list; + uint8 index; + + switch (RFIFOB(fd,2)) { + case 1: list = smith_fame_list; break; + case 2: list = chemist_fame_list; break; + case 3: list = taekwon_fame_list; break; + default: return 0; + } + + index = RFIFOB(fd, 3); + + if (index >= MAX_FAME_LIST) + return 0; + + list[index].fame = RFIFOL(fd,4); + + return 1; +} + +int chrif_save_scdata(struct map_session_data *sd) { //parses the sc_data of the player and sends it to the char-server for saving. [Skotlex] + +#ifdef ENABLE_SC_SAVING + int i, count=0; + unsigned int tick; + struct status_change_data data; + struct status_change *sc = &sd->sc; + const struct TimerData *timer; + + chrif_check(-1); + tick = gettick(); + + WFIFOHEAD(char_fd, 14 + SC_MAX*sizeof(struct status_change_data)); + WFIFOW(char_fd,0) = 0x2b1c; + WFIFOL(char_fd,4) = sd->status.account_id; + WFIFOL(char_fd,8) = sd->status.char_id; + + for (i = 0; i < SC_MAX; i++) { + if (!sc->data[i]) + continue; + if (sc->data[i]->timer != INVALID_TIMER) { + timer = get_timer(sc->data[i]->timer); + if (timer == NULL || timer->func != status_change_timer || DIFF_TICK(timer->tick,tick) < 0) + continue; + data.tick = DIFF_TICK(timer->tick,tick); //Duration that is left before ending. + } else + data.tick = -1; //Infinite duration + data.type = i; + data.val1 = sc->data[i]->val1; + data.val2 = sc->data[i]->val2; + data.val3 = sc->data[i]->val3; + data.val4 = sc->data[i]->val4; + memcpy(WFIFOP(char_fd,14 +count*sizeof(struct status_change_data)), + &data, sizeof(struct status_change_data)); + count++; + } + + if (count == 0) + return 0; //Nothing to save. + + WFIFOW(char_fd,12) = count; + WFIFOW(char_fd,2) = 14 +count*sizeof(struct status_change_data); //Total packet size + WFIFOSET(char_fd,WFIFOW(char_fd,2)); +#endif + + return 0; +} + +//Retrieve and load sc_data for a player. [Skotlex] +int chrif_load_scdata(int fd) { + +#ifdef ENABLE_SC_SAVING + struct map_session_data *sd; + struct status_change_data *data; + int aid, cid, i, count; + + aid = RFIFOL(fd,4); //Player Account ID + cid = RFIFOL(fd,8); //Player Char ID + + sd = map_id2sd(aid); + + if ( !sd ) { + ShowError("chrif_load_scdata: Player of AID %d not found!\n", aid); + return -1; + } + + if ( sd->status.char_id != cid ) { + ShowError("chrif_load_scdata: Receiving data for account %d, char id does not matches (%d != %d)!\n", aid, sd->status.char_id, cid); + return -1; + } + + count = RFIFOW(fd,12); //sc_count + + for (i = 0; i < count; i++) { + data = (struct status_change_data*)RFIFOP(fd,14 + i*sizeof(struct status_change_data)); + status_change_start(&sd->bl, (sc_type)data->type, 10000, data->val1, data->val2, data->val3, data->val4, data->tick, 15); + } +#endif + + return 0; +} + +/*========================================== + * Send rates and motd to char server [Wizputer] + * S 2b16 <base rate>.L <job rate>.L <drop rate>.L + *------------------------------------------*/ +int chrif_ragsrvinfo(int base_rate, int job_rate, int drop_rate) { + chrif_check(-1); + + WFIFOHEAD(char_fd,14); + WFIFOW(char_fd,0) = 0x2b16; + WFIFOL(char_fd,2) = base_rate; + WFIFOL(char_fd,6) = job_rate; + WFIFOL(char_fd,10) = drop_rate; + WFIFOSET(char_fd,14); + + return 0; +} + + +/*========================================= + * Tell char-server charcter disconnected [Wizputer] + *-----------------------------------------*/ +int chrif_char_offline(struct map_session_data *sd) { + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2b17; + WFIFOL(char_fd,2) = sd->status.char_id; + WFIFOL(char_fd,6) = sd->status.account_id; + WFIFOSET(char_fd,10); + + return 0; +} +int chrif_char_offline_nsd(int account_id, int char_id) { + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2b17; + WFIFOL(char_fd,2) = char_id; + WFIFOL(char_fd,6) = account_id; + WFIFOSET(char_fd,10); + + return 0; +} + +/*========================================= + * Tell char-server to reset all chars offline [Wizputer] + *-----------------------------------------*/ +int chrif_flush_fifo(void) { + chrif_check(-1); + + set_nonblocking(char_fd, 0); + flush_fifos(); + set_nonblocking(char_fd, 1); + + return 0; +} + +/*========================================= + * Tell char-server to reset all chars offline [Wizputer] + *-----------------------------------------*/ +int chrif_char_reset_offline(void) { + chrif_check(-1); + + WFIFOHEAD(char_fd,2); + WFIFOW(char_fd,0) = 0x2b18; + WFIFOSET(char_fd,2); + + return 0; +} + +/*========================================= + * Tell char-server charcter is online [Wizputer] + *-----------------------------------------*/ + +int chrif_char_online(struct map_session_data *sd) { + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2b19; + WFIFOL(char_fd,2) = sd->status.char_id; + WFIFOL(char_fd,6) = sd->status.account_id; + WFIFOSET(char_fd,10); + + return 0; +} + + +/// Called when the connection to Char Server is disconnected. +void chrif_on_disconnect(void) { + if( chrif_connected != 1 ) + ShowWarning("Connection to Char Server lost.\n\n"); + chrif_connected = 0; + + other_mapserver_count = 0; //Reset counter. We receive ALL maps from all map-servers on reconnect. + map_eraseallipport(); + + //Attempt to reconnect in a second. [Skotlex] + add_timer(gettick() + 1000, check_connect_char_server, 0, 0); +} + + +void chrif_update_ip(int fd) { + uint32 new_ip; + + WFIFOHEAD(fd,6); + + new_ip = host2ip(char_ip_str); + + if (new_ip && new_ip != char_ip) + char_ip = new_ip; //Update char_ip + + new_ip = clif_refresh_ip(); + + if (!new_ip) + return; //No change + + WFIFOW(fd,0) = 0x2736; + WFIFOL(fd,2) = htonl(new_ip); + WFIFOSET(fd,6); +} + +// pings the charserver +void chrif_keepalive(int fd) { + WFIFOHEAD(fd,2); + WFIFOW(fd,0) = 0x2b23; + WFIFOSET(fd,2); +} +void chrif_keepalive_ack(int fd) { + session[fd]->flag.ping = 0;/* reset ping state, we received a packet */ +} +/*========================================== + * + *------------------------------------------*/ +int chrif_parse(int fd) { + int packet_len, cmd; + + // only process data from the char-server + if ( fd != char_fd ) { + ShowDebug("chrif_parse: Disconnecting invalid session #%d (is not the char-server)\n", fd); + do_close(fd); + return 0; + } + + if ( session[fd]->flag.eof ) { + do_close(fd); + char_fd = -1; + chrif_on_disconnect(); + return 0; + } else if ( session[fd]->flag.ping ) {/* we've reached stall time */ + if( DIFF_TICK(last_tick, session[fd]->rdata_tick) > (stall_time * 2) ) {/* we can't wait any longer */ + set_eof(fd); + return 0; + } else if( session[fd]->flag.ping != 2 ) { /* we haven't sent ping out yet */ + chrif_keepalive(fd); + session[fd]->flag.ping = 2; + } + } + + while ( RFIFOREST(fd) >= 2 ) { + cmd = RFIFOW(fd,0); + if (cmd < 0x2af8 || cmd >= 0x2af8 + ARRAYLENGTH(packet_len_table) || packet_len_table[cmd-0x2af8] == 0) { + int r = intif_parse(fd); // Passed on to the intif + + if (r == 1) continue; // Treated in intif + if (r == 2) return 0; // Didn't have enough data (len==-1) + + ShowWarning("chrif_parse: session #%d, intif_parse failed (unrecognized command 0x%.4x).\n", fd, cmd); + set_eof(fd); + return 0; + } + + if ( ( packet_len = packet_len_table[cmd-0x2af8] ) == -1) { // dynamic-length packet, second WORD holds the length + if (RFIFOREST(fd) < 4) + return 0; + packet_len = RFIFOW(fd,2); + } + + if ((int)RFIFOREST(fd) < packet_len) + return 0; + + //ShowDebug("Received packet 0x%4x (%d bytes) from char-server (connection %d)\n", RFIFOW(fd,0), packet_len, fd); + + switch(cmd) { + case 0x2af9: chrif_connectack(fd); break; + case 0x2afb: chrif_sendmapack(fd); break; + case 0x2afd: chrif_authok(fd); break; + case 0x2b00: map_setusers(RFIFOL(fd,2)); chrif_keepalive(fd); break; + case 0x2b03: clif_charselectok(RFIFOL(fd,2), RFIFOB(fd,6)); break; + case 0x2b04: chrif_recvmap(fd); break; + case 0x2b06: chrif_changemapserverack(RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOW(fd,18), RFIFOW(fd,20), RFIFOW(fd,22), RFIFOL(fd,24), RFIFOW(fd,28)); break; + case 0x2b09: map_addnickdb(RFIFOL(fd,2), (char*)RFIFOP(fd,6)); break; + case 0x2b0d: chrif_changedsex(fd); break; + case 0x2b0f: chrif_char_ask_name_answer(RFIFOL(fd,2), (char*)RFIFOP(fd,6), RFIFOW(fd,30), RFIFOW(fd,32)); break; + case 0x2b12: chrif_divorceack(RFIFOL(fd,2), RFIFOL(fd,6)); break; + case 0x2b14: chrif_accountban(fd); break; + case 0x2b1b: chrif_recvfamelist(fd); break; + case 0x2b1d: chrif_load_scdata(fd); break; + case 0x2b1e: chrif_update_ip(fd); break; + case 0x2b1f: chrif_disconnectplayer(fd); break; + case 0x2b20: chrif_removemap(fd); break; + case 0x2b21: chrif_save_ack(fd); break; + case 0x2b22: chrif_updatefamelist_ack(fd); break; + case 0x2b24: chrif_keepalive_ack(fd); break; + case 0x2b25: chrif_deadopt(RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x2b27: chrif_authfail(fd); break; + default: + ShowError("chrif_parse : unknown packet (session #%d): 0x%x. Disconnecting.\n", fd, cmd); + set_eof(fd); + return 0; + } + if ( fd == char_fd ) //There's the slight chance we lost the connection during parse, in which case this would segfault if not checked [Skotlex] + RFIFOSKIP(fd, packet_len); + } + + return 0; +} + +// unused +int send_usercount_tochar(int tid, unsigned int tick, int id, intptr_t data) { + chrif_check(-1); + + WFIFOHEAD(char_fd,4); + WFIFOW(char_fd,0) = 0x2afe; + WFIFOW(char_fd,2) = map_usercount(); + WFIFOSET(char_fd,4); + return 0; +} + +/*========================================== + * timerFunction + * Send to char the number of client connected to map + *------------------------------------------*/ +int send_users_tochar(void) { + int users = 0, i = 0; + struct map_session_data* sd; + struct s_mapiterator* iter; + + chrif_check(-1); + + users = map_usercount(); + + WFIFOHEAD(char_fd, 6+8*users); + WFIFOW(char_fd,0) = 0x2aff; + + iter = mapit_getallusers(); + + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) { + WFIFOL(char_fd,6+8*i) = sd->status.account_id; + WFIFOL(char_fd,6+8*i+4) = sd->status.char_id; + i++; + } + + mapit_free(iter); + + WFIFOW(char_fd,2) = 6 + 8*users; + WFIFOW(char_fd,4) = users; + WFIFOSET(char_fd, 6+8*users); + + return 0; +} + +/*========================================== + * timerFunction + * Chk the connection to char server, (if it down) + *------------------------------------------*/ +static int check_connect_char_server(int tid, unsigned int tick, int id, intptr_t data) { + static int displayed = 0; + if ( char_fd <= 0 || session[char_fd] == NULL ) { + if ( !displayed ) { + ShowStatus("Attempting to connect to Char Server. Please wait.\n"); + displayed = 1; + } + + chrif_state = 0; + char_fd = make_connection(char_ip, char_port,false); + + if (char_fd == -1)//Attempt to connect later. [Skotlex] + return 0; + + session[char_fd]->func_parse = chrif_parse; + session[char_fd]->flag.server = 1; + realloc_fifo(char_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + chrif_connect(char_fd); + chrif_connected = (chrif_state == 2); + srvinfo = 0; + } else { + if (srvinfo == 0) { + chrif_ragsrvinfo(battle_config.base_exp_rate, battle_config.job_exp_rate, battle_config.item_rate_common); + srvinfo = 1; + } + } + if ( chrif_isconnected() ) + displayed = 0; + return 0; +} + +/*========================================== + * Asks char server to remove friend_id from the friend list of char_id + *------------------------------------------*/ +int chrif_removefriend(int char_id, int friend_id) { + + chrif_check(-1); + + WFIFOHEAD(char_fd,10); + WFIFOW(char_fd,0) = 0x2b07; + WFIFOL(char_fd,2) = char_id; + WFIFOL(char_fd,6) = friend_id; + WFIFOSET(char_fd,10); + + return 0; +} + +void chrif_send_report(char* buf, int len) { + +#ifndef STATS_OPT_OUT + WFIFOHEAD(char_fd,len + 2); + + WFIFOW(char_fd,0) = 0x3008; + + memcpy(WFIFOP(char_fd,2), buf, len); + + WFIFOSET(char_fd,len + 2); + + flush_fifo(char_fd); /* ensure it's sent now. */ +#endif + +} + +/** + * @see DBApply + */ +int auth_db_final(DBKey key, DBData *data, va_list ap) { + struct auth_node *node = db_data2ptr(data); + + if (node->char_dat) + aFree(node->char_dat); + + if (node->sd) + aFree(node->sd); + + ers_free(auth_db_ers, node); + + return 0; +} + +/*========================================== + * Destructor + *------------------------------------------*/ +int do_final_chrif(void) { + + if( char_fd != -1 ) { + do_close(char_fd); + char_fd = -1; + } + + auth_db->destroy(auth_db, auth_db_final); + + ers_destroy(auth_db_ers); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int do_init_chrif(void) { + + auth_db = idb_alloc(DB_OPT_BASE); + auth_db_ers = ers_new(sizeof(struct auth_node),"chrif.c::auth_db_ers",ERS_OPT_NONE); + + add_timer_func_list(check_connect_char_server, "check_connect_char_server"); + add_timer_func_list(auth_db_cleanup, "auth_db_cleanup"); + + // establish map-char connection if not present + add_timer_interval(gettick() + 1000, check_connect_char_server, 0, 0, 10 * 1000); + + // wipe stale data for timed-out client connection requests + add_timer_interval(gettick() + 1000, auth_db_cleanup, 0, 0, 30 * 1000); + + // send the user count every 10 seconds, to hide the charserver's online counting problem + add_timer_interval(gettick() + 1000, send_usercount_tochar, 0, 0, UPDATE_INTERVAL); + + return 0; +} diff --git a/src/map/chrif.h b/src/map/chrif.h new file mode 100644 index 000000000..0aadb1a7b --- /dev/null +++ b/src/map/chrif.h @@ -0,0 +1,69 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CHRIF_H_ +#define _CHRIF_H_ + +#include "../common/cbasetypes.h" +#include <time.h> + +enum sd_state { ST_LOGIN, ST_LOGOUT, ST_MAPCHANGE }; +struct auth_node { + int account_id, char_id; + int login_id1, login_id2, sex, fd; + time_t expiration_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + struct map_session_data *sd; //Data from logged on char. + struct mmo_charstatus *char_dat; //Data from char server. + unsigned int node_created; //timestamp for node timeouts + enum sd_state state; //To track whether player was login in/out or changing maps. +}; + +void chrif_setuserid(char* id); +void chrif_setpasswd(char* pwd); +void chrif_checkdefaultlogin(void); +int chrif_setip(const char* ip); +void chrif_setport(uint16 port); + +int chrif_isconnected(void); +void chrif_check_shutdown(void); + +extern int chrif_connected; +extern int other_mapserver_count; + +struct auth_node* chrif_search(int account_id); +struct auth_node* chrif_auth_check(int account_id, int char_id, enum sd_state state); +bool chrif_auth_delete(int account_id, int char_id, enum sd_state state); +bool chrif_auth_finished(struct map_session_data* sd); + +void chrif_authreq(struct map_session_data* sd); +void chrif_authok(int fd); +int chrif_scdata_request(int account_id, int char_id); +int chrif_save(struct map_session_data* sd, int flag); +int chrif_charselectreq(struct map_session_data* sd, uint32 s_ip); +int chrif_changemapserver(struct map_session_data* sd, uint32 ip, uint16 port); + +int chrif_searchcharid(int char_id); +int chrif_changeemail(int id, const char *actual_email, const char *new_email); +int chrif_char_ask_name(int acc, const char* character_name, unsigned short operation_type, int year, int month, int day, int hour, int minute, int second); +int chrif_updatefamelist(struct map_session_data *sd); +int chrif_buildfamelist(void); +int chrif_save_scdata(struct map_session_data *sd); +int chrif_ragsrvinfo(int base_rate,int job_rate, int drop_rate); +int chrif_char_offline(struct map_session_data *sd); +int chrif_char_offline_nsd(int account_id, int char_id); +int chrif_char_reset_offline(void); +int send_users_tochar(void); +int chrif_char_online(struct map_session_data *sd); +int chrif_changesex(struct map_session_data *sd); +int chrif_chardisconnect(struct map_session_data *sd); +int chrif_divorce(int partner_id1, int partner_id2); + +int chrif_removefriend(int char_id, int friend_id); +void chrif_send_report(char* buf, int len); + +int do_final_chrif(void); +int do_init_chrif(void); + +int chrif_flush_fifo(void); + +#endif /* _CHRIF_H_ */ diff --git a/src/map/clif.c b/src/map/clif.c new file mode 100644 index 000000000..06c74a5f8 --- /dev/null +++ b/src/map/clif.c @@ -0,0 +1,17128 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" + +#include "map.h" +#include "chrif.h" +#include "pc.h" +#include "status.h" +#include "npc.h" +#include "itemdb.h" +#include "chat.h" +#include "trade.h" +#include "storage.h" +#include "script.h" +#include "skill.h" +#include "atcommand.h" +#include "intif.h" +#include "battle.h" +#include "battleground.h" +#include "mob.h" +#include "party.h" +#include "unit.h" +#include "guild.h" +#include "vending.h" +#include "pet.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "log.h" +#include "clif.h" +#include "mail.h" +#include "quest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +/* for clif_clearunit_delayed */ +static struct eri *delay_clearunit_ers; + +//#define DUMP_UNKNOWN_PACKET +//#define DUMP_INVALID_PACKET + +struct Clif_Config { + int packet_db_ver; //Preferred packet version. + int connect_cmd[MAX_PACKET_VER + 1]; //Store the connect command for all versions. [Skotlex] +} clif_config; + +struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1]; + +//Converts item type in case of pet eggs. +static inline int itemtype(int type) +{ + return ( type == IT_PETEGG ) ? IT_WEAPON : type; +} + + +static inline void WBUFPOS(uint8* p, unsigned short pos, short x, short y, unsigned char dir) +{ + p += pos; + p[0] = (uint8)(x>>2); + p[1] = (uint8)((x<<6) | ((y>>4)&0x3f)); + p[2] = (uint8)((y<<4) | (dir&0xf)); +} + + +// client-side: x0+=sx0*0.0625-0.5 and y0+=sy0*0.0625-0.5 +static inline void WBUFPOS2(uint8* p, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) +{ + p += pos; + p[0] = (uint8)(x0>>2); + p[1] = (uint8)((x0<<6) | ((y0>>4)&0x3f)); + p[2] = (uint8)((y0<<4) | ((x1>>6)&0x0f)); + p[3] = (uint8)((x1<<2) | ((y1>>8)&0x03)); + p[4] = (uint8)y1; + p[5] = (uint8)((sx0<<4) | (sy0&0x0f)); +} + + +static inline void WFIFOPOS(int fd, unsigned short pos, short x, short y, unsigned char dir) +{ + WBUFPOS(WFIFOP(fd,pos), 0, x, y, dir); +} + + +static inline void WFIFOPOS2(int fd, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) +{ + WBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); +} + + +static inline void RBUFPOS(const uint8* p, unsigned short pos, short* x, short* y, unsigned char* dir) +{ + p += pos; + + if( x ) { + x[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); + } + + if( y ) { + y[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); + } + + if( dir ) { + dir[0] = ( p[2] & 0x0f ); + } +} + + +static inline void RBUFPOS2(const uint8* p, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) +{ + p += pos; + + if( x0 ) { + x0[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); + } + + if( y0 ) { + y0[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); + } + + if( x1 ) { + x1[0] = ( ( p[2] & 0x0f ) << 6 ) | ( p[3] >> 2 ); + } + + if( y1 ) { + y1[0] = ( ( p[3] & 0x03 ) << 8 ) | ( p[4] >> 0 ); + } + + if( sx0 ) { + sx0[0] = ( p[5] & 0xf0 ) >> 4; + } + + if( sy0 ) { + sy0[0] = ( p[5] & 0x0f ) >> 0; + } +} + + +static inline void RFIFOPOS(int fd, unsigned short pos, short* x, short* y, unsigned char* dir) +{ + RBUFPOS(RFIFOP(fd,pos), 0, x, y, dir); +} + + +static inline void RFIFOPOS2(int fd, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) +{ + RBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); +} + + +//To idenfity disguised characters. +static inline bool disguised(struct block_list* bl) +{ + return (bool)( bl->type == BL_PC && ((TBL_PC*)bl)->disguise ); +} + + +//Guarantees that the given string does not exceeds the allowed size, as well as making sure it's null terminated. [Skotlex] +static inline unsigned int mes_len_check(char* mes, unsigned int len, unsigned int max) +{ + if( len > max ) + len = max; + + mes[len-1] = '\0'; + + return len; +} + + +static char map_ip_str[128]; +static uint32 map_ip; +static uint32 bind_ip = INADDR_ANY; +static uint16 map_port = 5121; +int map_fd; + +static int clif_parse (int fd); + +/*========================================== + * Ip setting of map-server + *------------------------------------------*/ +int clif_setip(const char* ip) +{ + char ip_str[16]; + map_ip = host2ip(ip); + if (!map_ip) { + ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); + return 0; + } + + strncpy(map_ip_str, ip, sizeof(map_ip_str)); + ShowInfo("Map Server IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(map_ip, ip_str)); + return 1; +} + +void clif_setbindip(const char* ip) +{ + char ip_str[16]; + bind_ip = host2ip(ip); + if (bind_ip) { + ShowInfo("Map Server Bind IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(bind_ip, ip_str)); + } else { + ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); + } +} + +/*========================================== + * Sets map port to 'port' + * is run from map.c upon loading map server configuration + *------------------------------------------*/ +void clif_setport(uint16 port) +{ + map_port = port; +} + +/*========================================== + * Returns map server IP + *------------------------------------------*/ +uint32 clif_getip(void) +{ + return map_ip; +} + +//Refreshes map_server ip, returns the new ip if the ip changed, otherwise it returns 0. +uint32 clif_refresh_ip(void) +{ + uint32 new_ip; + + new_ip = host2ip(map_ip_str); + if (new_ip && new_ip != map_ip) { + map_ip = new_ip; + ShowInfo("Updating IP resolution of [%s].\n", map_ip_str); + return map_ip; + } + return 0; +} + +/*========================================== + * Returns map port which is set by clif_setport() + *------------------------------------------*/ +uint16 clif_getport(void) +{ + return map_port; +} + +#if PACKETVER >= 20071106 +static inline unsigned char clif_bl_type(struct block_list *bl) { + switch (bl->type) { + case BL_PC: return disguised(bl)?0x1:0x0; //PC_TYPE + case BL_ITEM: return 0x2; //ITEM_TYPE + case BL_SKILL: return 0x3; //SKILL_TYPE + case BL_CHAT: return 0x4; //UNKNOWN_TYPE + case BL_MOB: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x5; //NPC_MOB_TYPE + case BL_NPC: return 0x6; //NPC_EVT_TYPE + case BL_PET: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x7; //NPC_PET_TYPE + case BL_HOM: return 0x8; //NPC_HOM_TYPE + case BL_MER: return 0x9; //NPC_MERSOL_TYPE + case BL_ELEM: return 0xa; //NPC_ELEMENTAL_TYPE + default: return 0x1; //NPC_TYPE + } +} +#endif + +/*========================================== + * sub process of clif_send + * Called from a map_foreachinarea (grabs all players in specific area and subjects them to this function) + * In order to send area-wise packets, such as: + * - AREA : everyone nearby your area + * - AREA_WOSC (AREA WITHOUT SAME CHAT) : Not run for people in the same chat as yours + * - AREA_WOC (AREA WITHOUT CHAT) : Not run for people inside a chat + * - AREA_WOS (AREA WITHOUT SELF) : Not run for self + * - AREA_CHAT_WOC : Everyone in the area of your chat without a chat + *------------------------------------------*/ +static int clif_send_sub(struct block_list *bl, va_list ap) +{ + struct block_list *src_bl; + struct map_session_data *sd; + unsigned char *buf; + int len, type, fd; + + nullpo_ret(bl); + nullpo_ret(sd = (struct map_session_data *)bl); + + fd = sd->fd; + if (!fd) //Don't send to disconnected clients. + return 0; + + buf = va_arg(ap,unsigned char*); + len = va_arg(ap,int); + nullpo_ret(src_bl = va_arg(ap,struct block_list*)); + type = va_arg(ap,int); + + switch(type) + { + case AREA_WOS: + if (bl == src_bl) + return 0; + break; + case AREA_WOC: + if (sd->chatID || bl == src_bl) + return 0; + break; + case AREA_WOSC: + { + if(src_bl->type == BL_PC){ + struct map_session_data *ssd = (struct map_session_data *)src_bl; + if (ssd && sd->chatID && (sd->chatID == ssd->chatID)) + return 0; + } + else if(src_bl->type == BL_NPC) { + struct npc_data *nd = (struct npc_data *)src_bl; + if (nd && sd->chatID && (sd->chatID == nd->chat_id)) + return 0; + } + } + break; + } + + if (session[fd] == NULL) + return 0; + + WFIFOHEAD(fd, len); + if (WFIFOP(fd,0) == buf) { + ShowError("WARNING: Invalid use of clif_send function\n"); + ShowError(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0)); + ShowError(" Please correct your code.\n"); + // don't send to not move the pointer of the packet for next sessions in the loop + //WFIFOSET(fd,0);//## TODO is this ok? + //NO. It is not ok. There is the chance WFIFOSET actually sends the buffer data, and shifts elements around, which will corrupt the buffer. + return 0; + } + + if (packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + + return 0; +} + +/*========================================== + * Packet Delegation (called on all packets that require data to be sent to more than one client) + * functions that are sent solely to one use whose ID it posses use WFIFOSET + *------------------------------------------*/ +int clif_send(const uint8* buf, int len, struct block_list* bl, enum send_target type) +{ + int i; + struct map_session_data *sd, *tsd; + struct party_data *p = NULL; + struct guild *g = NULL; + struct battleground_data *bg = NULL; + int x0 = 0, x1 = 0, y0 = 0, y1 = 0, fd; + struct s_mapiterator* iter; + + if( type != ALL_CLIENT && type != CHAT_MAINCHAT ) + nullpo_ret(bl); + + sd = BL_CAST(BL_PC, bl); + + switch(type) { + + case ALL_CLIENT: //All player clients. + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case ALL_SAMEMAP: //All players on the same map + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( bl->m == tsd->bl.m && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case AREA: + case AREA_WOSC: + if (sd && bl->prev == NULL) //Otherwise source misses the packet.[Skotlex] + clif_send (buf, len, bl, SELF); + case AREA_WOC: + case AREA_WOS: + map_foreachinarea(clif_send_sub, bl->m, bl->x-AREA_SIZE, bl->y-AREA_SIZE, bl->x+AREA_SIZE, bl->y+AREA_SIZE, + BL_PC, buf, len, bl, type); + break; + case AREA_CHAT_WOC: + map_foreachinarea(clif_send_sub, bl->m, bl->x-(AREA_SIZE-5), bl->y-(AREA_SIZE-5), + bl->x+(AREA_SIZE-5), bl->y+(AREA_SIZE-5), BL_PC, buf, len, bl, AREA_WOC); + break; + + case CHAT: + case CHAT_WOS: + { + struct chat_data *cd; + if (sd) { + cd = (struct chat_data*)map_id2bl(sd->chatID); + } else if (bl->type == BL_CHAT) { + cd = (struct chat_data*)bl; + } else break; + if (cd == NULL) + break; + for(i = 0; i < cd->users; i++) { + if (type == CHAT_WOS && cd->usersd[i] == sd) + continue; + if (packet_db[cd->usersd[i]->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + if ((fd=cd->usersd[i]->fd) >0 && session[fd]) // Added check to see if session exists [PoW] + { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + } + break; + + case CHAT_MAINCHAT: //[LuzZza] + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->state.mainchat && tsd->chatID == 0 && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case PARTY_AREA: + case PARTY_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case PARTY: + case PARTY_WOS: + case PARTY_SAMEMAP: + case PARTY_SAMEMAP_WOS: + if (sd && sd->status.party_id) + p = party_search(sd->status.party_id); + + if (p) { + for(i=0;i<MAX_PARTY;i++){ + if( (sd = p->data[i].sd) == NULL ) + continue; + + if( !(fd=sd->fd) ) + continue; + + if( sd->bl.id == bl->id && (type == PARTY_WOS || type == PARTY_SAMEMAP_WOS || type == PARTY_AREA_WOS) ) + continue; + + if( type != PARTY && type != PARTY_WOS && bl->m != sd->bl.m ) + continue; + + if( (type == PARTY_AREA || type == PARTY_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) ) + continue; + + if( packet_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + if (!enable_spy) //Skip unnecessary parsing. [Skotlex] + break; + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->partyspy == p->party.party_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + } + break; + + case DUEL: + case DUEL_WOS: + if (!sd || !sd->duel_group) break; //Invalid usage. + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( type == DUEL_WOS && bl->id == tsd->bl.id ) + continue; + if( sd->duel_group == tsd->duel_group && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case SELF: + if (sd && (fd=sd->fd) && packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + break; + + // New definitions for guilds [Valaris] - Cleaned up and reorganized by [Skotlex] + case GUILD_AREA: + case GUILD_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case GUILD_SAMEMAP: + case GUILD_SAMEMAP_WOS: + case GUILD: + case GUILD_WOS: + case GUILD_NOBG: + if (sd && sd->status.guild_id) + g = guild_search(sd->status.guild_id); + + if (g) { + for(i = 0; i < g->max_member; i++) { + if( (sd = g->member[i].sd) != NULL ) + { + if( !(fd=sd->fd) ) + continue; + + if( type == GUILD_NOBG && sd->bg_id ) + continue; + + if( sd->bl.id == bl->id && (type == GUILD_WOS || type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS) ) + continue; + + if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && sd->bl.m != bl->m ) + continue; + + if( (type == GUILD_AREA || type == GUILD_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) ) + continue; + + if( packet_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + if (!enable_spy) //Skip unnecessary parsing. [Skotlex] + break; + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->guildspy == g->guild_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + } + break; + + case BG_AREA: + case BG_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case BG_SAMEMAP: + case BG_SAMEMAP_WOS: + case BG: + case BG_WOS: + if( sd && sd->bg_id && (bg = bg_team_search(sd->bg_id)) != NULL ) + { + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL || !(fd = sd->fd) ) + continue; + if( sd->bl.id == bl->id && (type == BG_WOS || type == BG_SAMEMAP_WOS || type == BG_AREA_WOS) ) + continue; + if( type != BG && type != BG_WOS && sd->bl.m != bl->m ) + continue; + if( (type == BG_AREA || type == BG_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) ) + continue; + if( packet_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + break; + + default: + ShowError("clif_send: Unrecognized type %d\n",type); + return -1; + } + + return 0; +} + + +/// Notifies the client, that it's connection attempt was accepted. +/// 0073 <start time>.L <position>.3B <x size>.B <y size>.B (ZC_ACCEPT_ENTER) +/// 02eb <start time>.L <position>.3B <x size>.B <y size>.B <font>.W (ZC_ACCEPT_ENTER2) +void clif_authok(struct map_session_data *sd) +{ +#if PACKETVER < 20080102 + const int cmd = 0x73; +#else + const int cmd = 0x2eb; +#endif + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd, 0) = cmd; + WFIFOL(fd, 2) = gettick(); + WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y, sd->ud.dir); + WFIFOB(fd, 9) = 5; // ignored + WFIFOB(fd,10) = 5; // ignored +#if PACKETVER >= 20080102 + WFIFOW(fd,11) = sd->user_font; // FIXME: Font is currently not saved. +#endif + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client, that it's connection attempt was refused (ZC_REFUSE_ENTER). +/// 0074 <error code>.B +/// error code: +/// 0 = client type mismatch +/// 1 = ID mismatch +/// 2 = mobile - out of available time +/// 3 = mobile - already logged in +/// 4 = mobile - waiting state +void clif_authrefuse(int fd, uint8 error_code) +{ + WFIFOHEAD(fd,packet_len(0x74)); + WFIFOW(fd,0) = 0x74; + WFIFOB(fd,2) = error_code; + WFIFOSET(fd,packet_len(0x74)); +} + + +/// Notifies the client of a ban or forced disconnect (SC_NOTIFY_BAN). +/// 0081 <error code>.B +/// error code: +/// 0 = BAN_UNFAIR +/// 1 = server closed -> MsgStringTable[4] +/// 2 = ID already logged in -> MsgStringTable[5] +/// 3 = timeout/too much lag -> MsgStringTable[241] +/// 4 = server full -> MsgStringTable[264] +/// 5 = underaged -> MsgStringTable[305] +/// 8 = Server sill recognizes last connection -> MsgStringTable[441] +/// 9 = too many connections from this ip -> MsgStringTable[529] +/// 10 = out of available time paid for -> MsgStringTable[530] +/// 11 = BAN_PAY_SUSPEND +/// 12 = BAN_PAY_CHANGE +/// 13 = BAN_PAY_WRONGIP +/// 14 = BAN_PAY_PNGAMEROOM +/// 15 = disconnected by a GM -> if( servicetype == taiwan ) MsgStringTable[579] +/// 16 = BAN_JAPAN_REFUSE1 +/// 17 = BAN_JAPAN_REFUSE2 +/// 18 = BAN_INFORMATION_REMAINED_ANOTHER_ACCOUNT +/// 100 = BAN_PC_IP_UNFAIR +/// 101 = BAN_PC_IP_COUNT_ALL +/// 102 = BAN_PC_IP_COUNT +/// 103 = BAN_GRAVITY_MEM_AGREE +/// 104 = BAN_GAME_MEM_AGREE +/// 105 = BAN_HAN_VALID +/// 106 = BAN_PC_IP_LIMIT_ACCESS +/// 107 = BAN_OVER_CHARACTER_LIST +/// 108 = BAN_IP_BLOCK +/// 109 = BAN_INVALID_PWD_CNT +/// 110 = BAN_NOT_ALLOWED_JOBCLASS +/// ? = disconnected -> MsgStringTable[3] +void clif_authfail_fd(int fd, int type) +{ + if (!fd || !session[fd] || session[fd]->func_parse != clif_parse) //clif_authfail should only be invoked on players! + return; + + WFIFOHEAD(fd, packet_len(0x81)); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = type; + WFIFOSET(fd,packet_len(0x81)); + set_eof(fd); +} + + +/// Notifies the client, whether it can disconnect and change servers (ZC_RESTART_ACK). +/// 00b3 <type>.B +/// type: +/// 1 = disconnect, char-select +/// ? = nothing +void clif_charselectok(int id, uint8 ok) +{ + struct map_session_data* sd; + int fd; + + if ((sd = map_id2sd(id)) == NULL || !sd->fd) + return; + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xb3)); + WFIFOW(fd,0) = 0xb3; + WFIFOB(fd,2) = ok; + WFIFOSET(fd,packet_len(0xb3)); +} + +/// Makes an item appear on the ground. +/// 009e <id>.L <name id>.W <identified>.B <x>.W <y>.W <subX>.B <subY>.B <amount>.W (ZC_ITEM_FALL_ENTRY) +/// 084b (ZC_ITEM_FALL_ENTRY4) +void clif_dropflooritem(struct flooritem_data* fitem) +{ + uint8 buf[17]; + int view; + + nullpo_retv(fitem); + + if (fitem->item_data.nameid <= 0) + return; + + WBUFW(buf, 0) = 0x9e; + WBUFL(buf, 2) = fitem->bl.id; + WBUFW(buf, 6) = ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) ? view : fitem->item_data.nameid; + WBUFB(buf, 8) = fitem->item_data.identify; + WBUFW(buf, 9) = fitem->bl.x; + WBUFW(buf,11) = fitem->bl.y; + WBUFB(buf,13) = fitem->subx; + WBUFB(buf,14) = fitem->suby; + WBUFW(buf,15) = fitem->item_data.amount; + + clif_send(buf, packet_len(0x9e), &fitem->bl, AREA); +} + + + +/// Makes an item disappear from the ground. +/// 00a1 <id>.L (ZC_ITEM_DISAPPEAR) +void clif_clearflooritem(struct flooritem_data *fitem, int fd) +{ + unsigned char buf[16]; + + nullpo_retv(fitem); + + WBUFW(buf,0) = 0xa1; + WBUFL(buf,2) = fitem->bl.id; + + if (fd == 0) { + clif_send(buf, packet_len(0xa1), &fitem->bl, AREA); + } else { + WFIFOHEAD(fd,packet_len(0xa1)); + memcpy(WFIFOP(fd,0), buf, packet_len(0xa1)); + WFIFOSET(fd,packet_len(0xa1)); + } +} + + +/// Makes a unit (char, npc, mob, homun) disappear to one client (ZC_NOTIFY_VANISH). +/// 0080 <id>.L <type>.B +/// type: +/// 0 = out of sight +/// 1 = died +/// 2 = logged out +/// 3 = teleport +/// 4 = trickdead +void clif_clearunit_single(int id, clr_type type, int fd) +{ + WFIFOHEAD(fd, packet_len(0x80)); + WFIFOW(fd,0) = 0x80; + WFIFOL(fd,2) = id; + WFIFOB(fd,6) = type; + WFIFOSET(fd, packet_len(0x80)); +} + +/// Makes a unit (char, npc, mob, homun) disappear to all clients in area (ZC_NOTIFY_VANISH). +/// 0080 <id>.L <type>.B +/// type: +/// 0 = out of sight +/// 1 = died +/// 2 = logged out +/// 3 = teleport +/// 4 = trickdead +void clif_clearunit_area(struct block_list* bl, clr_type type) +{ + unsigned char buf[8]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = bl->id; + WBUFB(buf,6) = type; + + clif_send(buf, packet_len(0x80), bl, type == CLR_DEAD ? AREA : AREA_WOS); + + if(disguised(bl)) { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x80), bl, SELF); + } +} + + +/// Used to make monsters with player-sprites disappear after dying +/// like normal monsters, because the client does not remove those +/// automatically. +static int clif_clearunit_delayed_sub(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl = (struct block_list *)data; + clif_clearunit_area(bl, (clr_type) id); + ers_free(delay_clearunit_ers,bl); + return 0; +} +void clif_clearunit_delayed(struct block_list* bl, clr_type type, unsigned int tick) +{ + struct block_list *tbl = ers_alloc(delay_clearunit_ers, struct block_list); + memcpy (tbl, bl, sizeof (struct block_list)); + add_timer(tick, clif_clearunit_delayed_sub, (int)type, (intptr_t)tbl); +} + +void clif_get_weapon_view(struct map_session_data* sd, unsigned short *rhand, unsigned short *lhand) +{ + if(sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER)) + { + *rhand = *lhand = 0; + return; + } + +#if PACKETVER < 4 + *rhand = sd->status.weapon; + *lhand = sd->status.shield; +#else + if (sd->equip_index[EQI_HAND_R] >= 0 && + sd->inventory_data[sd->equip_index[EQI_HAND_R]]) + { + struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_R]]; + if (id->view_id > 0) + *rhand = id->view_id; + else + *rhand = id->nameid; + } else + *rhand = 0; + + if (sd->equip_index[EQI_HAND_L] >= 0 && + sd->equip_index[EQI_HAND_L] != sd->equip_index[EQI_HAND_R] && + sd->inventory_data[sd->equip_index[EQI_HAND_L]]) + { + struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; + if (id->view_id > 0) + *lhand = id->view_id; + else + *lhand = id->nameid; + } else + *lhand = 0; +#endif +} + +//To make the assignation of the level based on limits clearer/easier. [Skotlex] +static int clif_setlevel_sub(int lv) +{ + if( lv < battle_config.max_lv ) + { + ; + } + else if( lv < battle_config.aura_lv ) + { + lv = battle_config.max_lv - 1; + } + else + { + lv = battle_config.max_lv; + } + + return lv; +} + +static int clif_setlevel(struct block_list* bl) +{ + int lv = status_get_lv(bl); + if( battle_config.client_limit_unit_lv&bl->type ) + return clif_setlevel_sub(lv); + switch( bl->type ) + { + case BL_NPC: + case BL_PET: + // npcs and pets do not have level + return 0; + } + return lv; +} + +/*========================================== + * Prepares 'unit standing/spawning' packet + *------------------------------------------*/ +static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn) +{ + struct map_session_data* sd; + struct status_change* sc = status_get_sc(bl); + struct view_data* vd = status_get_viewdata(bl); + unsigned char *buf = WBUFP(buffer,0); +#if PACKETVER < 20091103 + bool type = !pcdb_checkid(vd->class_); +#endif + unsigned short offset = 0; +#if PACKETVER >= 20091103 + const char *name; +#endif + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER < 20091103 + if(type) + WBUFW(buf,0) = spawn?0x7c:0x78; + else +#endif +#if PACKETVER < 4 + WBUFW(buf,0) = spawn?0x79:0x78; +#elif PACKETVER < 7 + WBUFW(buf,0) = spawn?0x1d9:0x1d8; +#elif PACKETVER < 20080102 + WBUFW(buf,0) = spawn?0x22b:0x22a; +#elif PACKETVER < 20091103 + WBUFW(buf,0) = spawn?0x2ed:0x2ee; +#elif PACKETVER < 20101124 + WBUFW(buf,0) = spawn?0x7f8:0x7f9; +#else + WBUFW(buf,0) = spawn?0x858:0x857; +#endif + +#if PACKETVER >= 20091103 + name = status_get_name(bl); +#if PACKETVER < 20110111 + WBUFW(buf,2) = (spawn?62:63)+strlen(name); +#else + WBUFW(buf,2) = (spawn?64:65)+strlen(name); +#endif + WBUFB(buf,4) = clif_bl_type(bl); + offset+=3; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 20071106 + if (type) { //Non-player packets + WBUFB(buf,2) = clif_bl_type(bl); + offset++; + buf = WBUFP(buffer,offset); + } +#endif + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = status_get_speed(bl); + WBUFW(buf, 8) = (sc)? sc->opt1 : 0; + WBUFW(buf,10) = (sc)? sc->opt2 : 0; +#if PACKETVER < 20091103 + if (type&&spawn) { //uses an older and different packet structure + WBUFW(buf,12) = (sc)? sc->option : 0; + WBUFW(buf,14) = vd->hair_style; + WBUFW(buf,16) = vd->weapon; + WBUFW(buf,18) = vd->head_bottom; + WBUFW(buf,20) = vd->class_; //Pet armor (ignored by client) + WBUFW(buf,22) = vd->shield; + } else { +#endif +#if PACKETVER >= 20091103 + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 7 + if (!type) { + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; + buf = WBUFP(buffer,offset); + } else + WBUFW(buf,12) = (sc)? sc->option : 0; +#else + WBUFW(buf,12) = (sc)? sc->option : 0; +#endif + WBUFW(buf,14) = vd->class_; + WBUFW(buf,16) = vd->hair_style; + WBUFW(buf,18) = vd->weapon; +#if PACKETVER < 4 + WBUFW(buf,20) = vd->head_bottom; + WBUFW(buf,22) = vd->shield; +#else + WBUFW(buf,20) = vd->shield; + WBUFW(buf,22) = vd->head_bottom; +#endif +#if PACKETVER < 20091103 + } +#endif + WBUFW(buf,24) = vd->head_top; + WBUFW(buf,26) = vd->head_mid; + + if( bl->type == BL_NPC && vd->class_ == FLAG_CLASS ) + { //The hell, why flags work like this? + WBUFW(buf,22) = status_get_emblem_id(bl); + WBUFW(buf,24) = GetWord(status_get_guild_id(bl), 1); + WBUFW(buf,26) = GetWord(status_get_guild_id(bl), 0); + } + + WBUFW(buf,28) = vd->hair_color; + WBUFW(buf,30) = vd->cloth_color; + WBUFW(buf,32) = (sd)? sd->head_dir : 0; +#if PACKETVER < 20091103 + if (type&&spawn) { //End of packet 0x7c + WBUFB(buf,34) = (sd)?sd->status.karma:0; // karma + WBUFB(buf,35) = vd->sex; + WBUFPOS(buf,36,bl->x,bl->y,unit_getdir(bl)); + WBUFB(buf,39) = 0; + WBUFB(buf,40) = 0; + return packet_len(0x7c); + } +#endif +#if PACKETVER >= 20110111 + WBUFW(buf,34) = vd->robe; + offset+= 2; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf,34) = status_get_guild_id(bl); + WBUFW(buf,38) = status_get_emblem_id(bl); + WBUFW(buf,40) = (sd)? sd->status.manner : 0; +#if PACKETVER >= 20091103 + WBUFL(buf,42) = (sc)? sc->opt3 : 0; + offset+=2; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 7 + if (!type) { + WBUFL(buf,42) = (sc)? sc->opt3 : 0; + offset+=2; + buf = WBUFP(buffer,offset); + } else + WBUFW(buf,42) = (sc)? sc->opt3 : 0; +#else + WBUFW(buf,42) = (sc)? sc->opt3 : 0; +#endif + WBUFB(buf,44) = (sd)? sd->status.karma : 0; + WBUFB(buf,45) = vd->sex; + WBUFPOS(buf,46,bl->x,bl->y,unit_getdir(bl)); + WBUFB(buf,49) = (sd)? 5 : 0; + WBUFB(buf,50) = (sd)? 5 : 0; + if (!spawn) { + WBUFB(buf,51) = vd->dead_sit; + offset++; + buf = WBUFP(buffer,offset); + } + WBUFW(buf,51) = clif_setlevel(bl); +#if PACKETVER < 20091103 + if (type) //End for non-player packet + return packet_len(WBUFW(buffer,0)); +#endif +#if PACKETVER >= 20080102 + WBUFW(buf,53) = sd?sd->user_font:0; +#endif +#if PACKETVER >= 20091103 + memcpy((char*)WBUFP(buf,55), name, NAME_LENGTH); + return WBUFW(buffer,2); +#else + return packet_len(WBUFW(buffer,0)); +#endif +} + +/*========================================== + * Prepares 'unit walking' packet + *------------------------------------------*/ +static int clif_set_unit_walking(struct block_list* bl, struct unit_data* ud, unsigned char* buffer) +{ + struct map_session_data* sd; + struct status_change* sc = status_get_sc(bl); + struct view_data* vd = status_get_viewdata(bl); + unsigned char* buf = WBUFP(buffer,0); +#if PACKETVER >= 7 + unsigned short offset = 0; +#endif +#if PACKETVER >= 20091103 + const char *name; +#endif + + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER < 4 + WBUFW(buf, 0) = 0x7b; +#elif PACKETVER < 7 + WBUFW(buf, 0) = 0x1da; +#elif PACKETVER < 20080102 + WBUFW(buf, 0) = 0x22c; +#elif PACKETVER < 20091103 + WBUFW(buf, 0) = 0x2ec; +#elif PACKETVER < 20101124 + WBUFW(buf, 0) = 0x7f7; +#else + WBUFW(buf, 0) = 0x856; +#endif + +#if PACKETVER >= 20091103 + name = status_get_name(bl); +#if PACKETVER < 20110111 + WBUFW(buf, 2) = 69+strlen(name); +#else + WBUFW(buf, 2) = 71+strlen(name); +#endif + offset+=2; + buf = WBUFP(buffer,offset); +#endif +#if PACKETVER >= 20071106 + WBUFB(buf, 2) = clif_bl_type(bl); + offset++; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = status_get_speed(bl); + WBUFW(buf, 8) = (sc)? sc->opt1 : 0; + WBUFW(buf,10) = (sc)? sc->opt2 : 0; +#if PACKETVER < 7 + WBUFW(buf,12) = (sc)? sc->option : 0; +#else + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; //Shift the rest of elements by 2 bytes. + buf = WBUFP(buffer,offset); +#endif + WBUFW(buf,14) = vd->class_; + WBUFW(buf,16) = vd->hair_style; + WBUFW(buf,18) = vd->weapon; +#if PACKETVER < 4 + WBUFW(buf,20) = vd->head_bottom; + WBUFL(buf,22) = gettick(); + WBUFW(buf,26) = vd->shield; +#else + WBUFW(buf,20) = vd->shield; + WBUFW(buf,22) = vd->head_bottom; + WBUFL(buf,24) = gettick(); +#endif + WBUFW(buf,28) = vd->head_top; + WBUFW(buf,30) = vd->head_mid; + WBUFW(buf,32) = vd->hair_color; + WBUFW(buf,34) = vd->cloth_color; + WBUFW(buf,36) = (sd)? sd->head_dir : 0; +#if PACKETVER >= 20110111 + WBUFW(buf,38) = vd->robe; + offset+= 2; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf,38) = status_get_guild_id(bl); + WBUFW(buf,42) = status_get_emblem_id(bl); + WBUFW(buf,44) = (sd)? sd->status.manner : 0; +#if PACKETVER < 7 + WBUFW(buf,46) = (sc)? sc->opt3 : 0; +#else + WBUFL(buf,46) = (sc)? sc->opt3 : 0; + offset+=2; //Shift the rest of elements by 2 bytes. + buf = WBUFP(buffer,offset); +#endif + WBUFB(buf,48) = (sd)? sd->status.karma : 0; + WBUFB(buf,49) = vd->sex; + WBUFPOS2(buf,50,bl->x,bl->y,ud->to_x,ud->to_y,8,8); + WBUFB(buf,56) = (sd)? 5 : 0; + WBUFB(buf,57) = (sd)? 5 : 0; + WBUFW(buf,58) = clif_setlevel(bl); +#if PACKETVER >= 20080102 + WBUFW(buf,60) = sd?sd->user_font:0; +#endif +#if PACKETVER >= 20091103 + memcpy((char*)WBUFP(buf,62), name, NAME_LENGTH); + return WBUFW(buffer,2); +#else + return packet_len(WBUFW(buffer,0)); +#endif +} + +//Modifies the buffer for disguise characters and sends it to self. +//Used for spawn/walk packets, where the ID offset changes for packetver >=9 +static void clif_setdisguise(struct block_list *bl, unsigned char *buf,int len) +{ +#if PACKETVER >= 20091103 + WBUFB(buf,4)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE + WBUFL(buf,5)=-bl->id; +#elif PACKETVER >= 20071106 + WBUFB(buf,2)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE + WBUFL(buf,3)=-bl->id; +#else + WBUFL(buf,2)=-bl->id; +#endif + clif_send(buf, len, bl, SELF); +} + + +/// Changes sprite of an NPC object (ZC_NPCSPRITE_CHANGE). +/// 01b0 <id>.L <type>.B <value>.L +/// type: +/// unused +void clif_class_change(struct block_list *bl,int class_,int type) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + if(!pcdb_checkid(class_)) + {// player classes yield missing sprites + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFL(buf,7)=class_; + clif_send(buf,packet_len(0x1b0),bl,AREA); + } +} + + +/// Notifies the client of an object's spirits. +/// 01d0 <id>.L <amount>.W (ZC_SPIRITS) +/// 01e1 <id>.L <amount>.W (ZC_SPIRITS2) +static void clif_spiritball_single(int fd, struct map_session_data *sd) +{ + WFIFOHEAD(fd, packet_len(0x1e1)); + WFIFOW(fd,0)=0x1e1; + WFIFOL(fd,2)=sd->bl.id; + WFIFOW(fd,6)=sd->spiritball; + WFIFOSET(fd, packet_len(0x1e1)); +} + +/*========================================== + * Kagerou/Oboro amulet spirit + *------------------------------------------*/ +static void clif_talisman_single(int fd, struct map_session_data *sd, short type) +{ + WFIFOHEAD(fd, packet_len(0x08cf)); + WFIFOW(fd,0)=0x08cf; + WFIFOL(fd,2)=sd->bl.id; + WFIFOW(fd,6)=type; + WFIFOW(fd,8)=sd->talisman[type]; + WFIFOSET(fd, packet_len(0x08cf)); +} + +/*========================================== + * Run when player changes map / refreshes + * Tells its client to display all weather settings being used by this map + *------------------------------------------*/ +static void clif_weather_check(struct map_session_data *sd) +{ + int16 m = sd->bl.m; + int fd = sd->fd; + + if (map[m].flag.snow + || map[m].flag.clouds + || map[m].flag.fog + || map[m].flag.fireworks + || map[m].flag.sakura + || map[m].flag.leaves + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //|| map[m].flag.rain + || map[m].flag.clouds2) + { + if (map[m].flag.snow) + clif_specialeffect_single(&sd->bl, 162, fd); + if (map[m].flag.clouds) + clif_specialeffect_single(&sd->bl, 233, fd); + if (map[m].flag.clouds2) + clif_specialeffect_single(&sd->bl, 516, fd); + if (map[m].flag.fog) + clif_specialeffect_single(&sd->bl, 515, fd); + if (map[m].flag.fireworks) { + clif_specialeffect_single(&sd->bl, 297, fd); + clif_specialeffect_single(&sd->bl, 299, fd); + clif_specialeffect_single(&sd->bl, 301, fd); + } + if (map[m].flag.sakura) + clif_specialeffect_single(&sd->bl, 163, fd); + if (map[m].flag.leaves) + clif_specialeffect_single(&sd->bl, 333, fd); + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //if (map[m].flag.rain) + // clif_specialeffect_single(&sd->bl, 161, fd); + } +} +/** + * Run when the weather on a map changes, throws all players in map id 'm' to clif_weather_check function + **/ +void clif_weather(int16 m) +{ + struct s_mapiterator* iter; + struct map_session_data *sd=NULL; + + iter = mapit_getallusers(); + for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) + { + if( sd->bl.m == m ) + clif_weather_check(sd); + } + mapit_free(iter); +} +/** + * Main function to spawn a unit on the client (player/mob/pet/etc) + **/ +int clif_spawn(struct block_list *bl) +{ + unsigned char buf[128]; + struct view_data *vd; + int len; + + vd = status_get_viewdata(bl); + if( !vd || vd->class_ == INVISIBLE_CLASS ) + return 0; + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return 0; + + len = clif_set_unit_idle(bl, buf,true); + clif_send(buf, len, bl, AREA_WOS); + if (disguised(bl)) + clif_setdisguise(bl, buf, len); + + if (vd->cloth_color) + clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); + + switch (bl->type) + { + case BL_PC: + { + TBL_PC *sd = ((TBL_PC*)bl); + int i; + if (sd->spiritball > 0) + clif_spiritball(&sd->bl); + if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect(bl,423,AREA); + else if(sd->state.size==SZ_MEDIUM) + clif_specialeffect(bl,421,AREA); + if( sd->bg_id && map[sd->bl.m].flag.battleground ) + clif_sendbgemblem_area(sd); + if( sd->sc.option&OPTION_MOUNTING ) { + //New Mounts are not complaint to the original method, so we gotta tell this guy that he is mounting. + clif_status_load_notick(&sd->bl,SI_ALL_RIDING,2,1,0,0); + } + for(i = 1; i < 5; i++){ + if( sd->talisman[i] > 0 ) + clif_talisman(sd, i); + } + #ifdef NEW_CARTS + if( sd->sc.data[SC_PUSH_CART] ) + clif_status_load_notick(&sd->bl, SI_ON_PUSH_CART, 2, sd->sc.data[SC_PUSH_CART]->val1, 0, 0); + #endif + #if PACKETVER <= 20120207 + if (sd->status.robe) + clif_refreshlook(bl,bl->id,LOOK_ROBE,sd->status.robe,AREA); + #endif + } + break; + case BL_MOB: + { + TBL_MOB *md = ((TBL_MOB*)bl); + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect(&md->bl,423,AREA); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect(&md->bl,421,AREA); + } + break; + case BL_NPC: + { + TBL_NPC *nd = ((TBL_NPC*)bl); + if( nd->size == SZ_BIG ) + clif_specialeffect(&nd->bl,423,AREA); + else if( nd->size == SZ_MEDIUM ) + clif_specialeffect(&nd->bl,421,AREA); + } + break; + case BL_PET: + if (vd->head_bottom) + clif_pet_equip_area((TBL_PET*)bl); // needed to display pet equip properly + break; + } + return 0; +} + +/// Sends information about owned homunculus to the client (ZC_PROPERTY_HOMUN). [orn] +/// 022e <name>.24B <modified>.B <level>.W <hunger>.W <intimacy>.W <equip id>.W <atk>.W <matk>.W <hit>.W <crit>.W <def>.W <mdef>.W <flee>.W <aspd>.W <hp>.W <max hp>.W <sp>.W <max sp>.W <exp>.L <max exp>.L <skill points>.W <atk range>.W +void clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag) +{ + struct status_data *status; + unsigned char buf[128]; + int m_class; + + nullpo_retv(hd); + + status = &hd->battle_status; + m_class = hom_class2mapid(hd->homunculus.class_); + + memset(buf,0,packet_len(0x22e)); + WBUFW(buf,0)=0x22e; + memcpy(WBUFP(buf,2),hd->homunculus.name,NAME_LENGTH); + // Bit field, bit 0 : rename_flag (1 = already renamed), bit 1 : homunc vaporized (1 = true), bit 2 : homunc dead (1 = true) + WBUFB(buf,26)=(battle_config.hom_rename?0:hd->homunculus.rename_flag) | (hd->homunculus.vaporize << 1) | (hd->homunculus.hp?0:4); + WBUFW(buf,27)=hd->homunculus.level; + WBUFW(buf,29)=hd->homunculus.hunger; + WBUFW(buf,31)=(unsigned short) (hd->homunculus.intimacy / 100) ; + WBUFW(buf,33)=0; // equip id + WBUFW(buf,35)=cap_value(status->rhw.atk2+status->batk, 0, INT16_MAX); + WBUFW(buf,37)=cap_value(status->matk_max, 0, INT16_MAX); + WBUFW(buf,39)=status->hit; + if (battle_config.hom_setting&0x10) + WBUFW(buf,41)=status->luk/3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious] + else + WBUFW(buf,41)=status->cri/10; + WBUFW(buf,43)=status->def + status->vit ; + WBUFW(buf,45)=status->mdef; + WBUFW(buf,47)=status->flee; + WBUFW(buf,49)=(flag)?0:status->amotion; + if (status->max_hp > INT16_MAX) { + WBUFW(buf,51) = status->hp/(status->max_hp/100); + WBUFW(buf,53) = 100; + } else { + WBUFW(buf,51)=status->hp; + WBUFW(buf,53)=status->max_hp; + } + if (status->max_sp > INT16_MAX) { + WBUFW(buf,55) = status->sp/(status->max_sp/100); + WBUFW(buf,57) = 100; + } else { + WBUFW(buf,55)=status->sp; + WBUFW(buf,57)=status->max_sp; + } + WBUFL(buf,59)=hd->homunculus.exp; + if( ((m_class&HOM_REG) && hd->homunculus.level >= battle_config.hom_max_level) || ((m_class&HOM_S) && hd->homunculus.level >= battle_config.hom_S_max_level) ) + WBUFL(buf,63)=0; + else + WBUFL(buf,63)=hd->exp_next; + WBUFW(buf,67)=hd->homunculus.skillpts; + WBUFW(buf,69)=status_get_range(&hd->bl); + clif_send(buf,packet_len(0x22e),&sd->bl,SELF); +} + + +/// Notification about a change in homunuculus' state (ZC_CHANGESTATE_MER). +/// 0230 <type>.B <state>.B <id>.L <data>.L +/// type: +/// unused +/// state: +/// 0 = pre-init +/// 1 = intimacy +/// 2 = hunger +/// 3 = accessory? +/// ? = ignored +void clif_send_homdata(struct map_session_data *sd, int state, int param) +{ //[orn] + int fd = sd->fd; + + if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) ) + merc_hom_calc_skilltree(sd->hd, 0); + + WFIFOHEAD(fd, packet_len(0x230)); + WFIFOW(fd,0)=0x230; + WFIFOB(fd,2)=0; + WFIFOB(fd,3)=state; + WFIFOL(fd,4)=sd->hd->bl.id; + WFIFOL(fd,8)=param; + WFIFOSET(fd,packet_len(0x230)); +} + + +int clif_homskillinfoblock(struct map_session_data *sd) +{ //[orn] + struct homun_data *hd; + int fd = sd->fd; + int i,j,len=4,id; + WFIFOHEAD(fd, 4+37*MAX_HOMUNSKILL); + + hd = sd->hd; + if ( !hd ) + return 0 ; + + WFIFOW(fd,0)=0x235; + for ( i = 0; i < MAX_HOMUNSKILL; i++){ + if( (id = hd->homunculus.hskill[i].id) != 0 ){ + j = id - HM_SKILLBASE; + WFIFOW(fd,len ) = id; + WFIFOW(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+4) = 0; + WFIFOW(fd,len+6) = hd->homunculus.hskill[j].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,hd->homunculus.hskill[j].lv); + WFIFOW(fd,len+10)= skill_get_range2(&sd->hd->bl, id,hd->homunculus.hskill[j].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,len+36) = (hd->homunculus.hskill[j].lv < merc_skill_tree_get_max(id, hd->homunculus.class_))?1:0; + len+=37; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + return 0; +} + +void clif_homskillup(struct map_session_data *sd, uint16 skill_id) +{ //[orn] + struct homun_data *hd; + int fd, idx; + nullpo_retv(sd); + idx = skill_id - HM_SKILLBASE; + + fd=sd->fd; + hd=sd->hd; + + WFIFOHEAD(fd, packet_len(0x239)); + WFIFOW(fd,0) = 0x239; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = hd->homunculus.hskill[idx].lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,hd->homunculus.hskill[idx].lv); + WFIFOW(fd,8) = skill_get_range2(&hd->bl, skill_id,hd->homunculus.hskill[idx].lv); + WFIFOB(fd,10) = (hd->homunculus.hskill[idx].lv < skill_get_max(hd->homunculus.hskill[idx].id)) ? 1 : 0; + WFIFOSET(fd,packet_len(0x239)); +} + +int clif_hom_food(struct map_session_data *sd,int foodid,int fail) //[orn] +{ + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x22f)); + WFIFOW(fd,0)=0x22f; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len(0x22f)); + + return 0; +} + + +/// Notifies the client, that it is walking (ZC_NOTIFY_PLAYERMOVE). +/// 0087 <walk start time>.L <walk data>.6B +void clif_walkok(struct map_session_data *sd) +{ + int fd=sd->fd; + + WFIFOHEAD(fd, packet_len(0x87)); + WFIFOW(fd,0)=0x87; + WFIFOL(fd,2)=gettick(); + WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->ud.to_x,sd->ud.to_y,8,8); + WFIFOSET(fd,packet_len(0x87)); +} + + +static void clif_move2(struct block_list *bl, struct view_data *vd, struct unit_data *ud) +{ + uint8 buf[128]; + int len; + + len = clif_set_unit_walking(bl,ud,buf); + clif_send(buf,len,bl,AREA_WOS); + if (disguised(bl)) + clif_setdisguise(bl, buf, len); + + if(vd->cloth_color) + clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); + + switch(bl->type) + { + case BL_PC: + { + TBL_PC *sd = ((TBL_PC*)bl); +// clif_movepc(sd); + if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect(&sd->bl,423,AREA); + else if(sd->state.size==SZ_MEDIUM) + clif_specialeffect(&sd->bl,421,AREA); + } + break; + case BL_MOB: + { + TBL_MOB *md = ((TBL_MOB*)bl); + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect(&md->bl,423,AREA); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect(&md->bl,421,AREA); + } + break; + case BL_PET: + if( vd->head_bottom ) + {// needed to display pet equip properly + clif_pet_equip_area((TBL_PET*)bl); + } + break; + } +} + + +/// Notifies clients in an area, that an other visible object is walking (ZC_NOTIFY_PLAYERMOVE). +/// 0086 <id>.L <walk data>.6B <walk start time>.L +/// Note: unit must not be self +void clif_move(struct unit_data *ud) +{ + unsigned char buf[16]; + struct view_data* vd; + struct block_list* bl = ud->bl; + + vd = status_get_viewdata(bl); + if (!vd || vd->class_ == INVISIBLE_CLASS) + return; //This performance check is needed to keep GM-hidden objects from being notified to bots. + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return; + + if (ud->state.speed_changed) { + // Since we don't know how to update the speed of other objects, + // use the old walk packet to update the data. + ud->state.speed_changed = 0; + clif_move2(bl, vd, ud); + return; + } + + WBUFW(buf,0)=0x86; + WBUFL(buf,2)=bl->id; + WBUFPOS2(buf,6,bl->x,bl->y,ud->to_x,ud->to_y,8,8); + WBUFL(buf,12)=gettick(); + clif_send(buf, packet_len(0x86), bl, AREA_WOS); + if (disguised(bl)) + { + WBUFL(buf,2)=-bl->id; + clif_send(buf, packet_len(0x86), bl, SELF); + } +} + + +/*========================================== + * Delays the map_quit of a player after they are disconnected. [Skotlex] + *------------------------------------------*/ +static int clif_delayquit(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = NULL; + + //Remove player from map server + if ((sd = map_id2sd(id)) != NULL && sd->fd == 0) //Should be a disconnected player. + map_quit(sd); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +void clif_quitsave(int fd,struct map_session_data *sd) +{ + if (!battle_config.prevent_logout || + DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) + map_quit(sd); + else if (sd->fd) + { //Disassociate session from player (session is deleted after this function was called) + //And set a timer to make him quit later. + session[sd->fd]->session_data = NULL; + sd->fd = 0; + add_timer(gettick() + 10000, clif_delayquit, sd->bl.id, 0); + } +} + +/// Notifies the client of a position change to coordinates on given map (ZC_NPCACK_MAPMOVE). +/// 0091 <map name>.16B <x>.W <y>.W +void clif_changemap(struct map_session_data *sd, short map, int x, int y) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x91)); + WFIFOW(fd,0) = 0x91; + mapindex_getmapname_ext(mapindex_id2name(map), (char*)WFIFOP(fd,2)); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOSET(fd,packet_len(0x91)); +} + + +/// Notifies the client of a position change to coordinates on given map, which is on another map-server (ZC_NPCACK_SERVERMOVE). +/// 0092 <map name>.16B <x>.W <y>.W <ip>.L <port>.W +void clif_changemapserver(struct map_session_data* sd, unsigned short map_index, int x, int y, uint32 ip, uint16 port) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x92)); + WFIFOW(fd,0) = 0x92; + mapindex_getmapname_ext(mapindex_id2name(map_index), (char*)WFIFOP(fd,2)); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOL(fd,22) = htonl(ip); + WFIFOW(fd,26) = ntows(htons(port)); // [!] LE byte order here [!] + WFIFOSET(fd,packet_len(0x92)); +} + + +void clif_blown(struct block_list *bl) +{ +//Aegis packets says fixpos, but it's unsure whether slide works better or not. +// clif_fixpos(bl); + clif_slide(bl, bl->x, bl->y); +} + + +/// Visually moves(slides) a character to x,y. If the target cell +/// isn't walkable, the char doesn't move at all. If the char is +/// sitting it will stand up (ZC_STOPMOVE). +/// 0088 <id>.L <x>.W <y>.W +void clif_fixpos(struct block_list *bl) +{ + unsigned char buf[10]; + nullpo_retv(bl); + + WBUFW(buf,0) = 0x88; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = bl->x; + WBUFW(buf,8) = bl->y; + clif_send(buf, packet_len(0x88), bl, AREA); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x88), bl, SELF); + } +} + + +/// Displays the buy/sell dialog of an NPC shop (ZC_SELECT_DEALTYPE). +/// 00c4 <shop id>.L +void clif_npcbuysell(struct map_session_data* sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xc4)); + WFIFOW(fd,0)=0xc4; + WFIFOL(fd,2)=id; + WFIFOSET(fd,packet_len(0xc4)); +} + + +/// Presents list of items, that can be bought in an NPC shop (ZC_PC_PURCHASE_ITEMLIST). +/// 00c6 <packet len>.W { <price>.L <discount price>.L <item type>.B <name id>.W }* +void clif_buylist(struct map_session_data *sd, struct npc_data *nd) +{ + int fd,i,c; + + nullpo_retv(sd); + nullpo_retv(nd); + + fd = sd->fd; + WFIFOHEAD(fd, 4 + nd->u.shop.count * 11); + WFIFOW(fd,0) = 0xc6; + + c = 0; + for( i = 0; i < nd->u.shop.count; i++ ) + { + struct item_data* id = itemdb_exists(nd->u.shop.shop_item[i].nameid); + int val = nd->u.shop.shop_item[i].value; + if( id == NULL ) + continue; + WFIFOL(fd, 4+c*11) = val; + WFIFOL(fd, 8+c*11) = pc_modifybuyvalue(sd,val); + WFIFOB(fd,12+c*11) = itemtype(id->type); + WFIFOW(fd,13+c*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + c++; + } + + WFIFOW(fd,2) = 4 + c*11; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Presents list of items, that can be sold to an NPC shop (ZC_PC_SELL_ITEMLIST). +/// 00c7 <packet len>.W { <index>.W <price>.L <overcharge price>.L }* +void clif_selllist(struct map_session_data *sd) +{ + int fd,i,c=0,val; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4); + WFIFOW(fd,0)=0xc7; + for( i = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] ) + { + if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) ) + continue; + + if( sd->status.inventory[i].expire_time ) + continue; // Cannot Sell Rental Items + + val=sd->inventory_data[i]->value_sell; + if( val < 0 ) + continue; + WFIFOW(fd,4+c*10)=i+2; + WFIFOL(fd,6+c*10)=val; + WFIFOL(fd,10+c*10)=pc_modifysellvalue(sd,val); + c++; + } + } + WFIFOW(fd,2)=c*10+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Displays an NPC dialog message (ZC_SAY_DIALOG). +/// 00b4 <packet len>.W <npc id>.L <message>.?B +/// Client behavior (dialog window): +/// - disable mouse targeting +/// - open the dialog window +/// - set npcid of dialog window (0 by default) +/// - if set to clear on next mes, clear contents +/// - append this text +void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes) +{ + int fd = sd->fd; + int slen = strlen(mes) + 9; + + WFIFOHEAD(fd, slen); + WFIFOW(fd,0)=0xb4; + WFIFOW(fd,2)=slen; + WFIFOL(fd,4)=npcid; + memcpy((char*)WFIFOP(fd,8), mes, slen-8); + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Adds a 'next' button to an NPC dialog (ZC_WAIT_DIALOG). +/// 00b5 <npc id>.L +/// Client behavior (dialog window): +/// - disable mouse targeting +/// - open the dialog window +/// - add 'next' button +/// When 'next' is pressed: +/// - 00B9 <npcid of dialog window>.L +/// - set to clear on next mes +/// - remove 'next' button +void clif_scriptnext(struct map_session_data *sd,int npcid) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xb5)); + WFIFOW(fd,0)=0xb5; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0xb5)); +} + + +/// Adds a 'close' button to an NPC dialog (ZC_CLOSE_DIALOG). +/// 00b6 <npc id>.L +/// Client behavior: +/// - if dialog window is open: +/// - remove 'next' button +/// - add 'close' button +/// - else: +/// - enable mouse targeting +/// - close the dialog window +/// - close the menu window +/// When 'close' is pressed: +/// - enable mouse targeting +/// - close the dialog window +/// - close the menu window +/// - 0146 <npcid of dialog window>.L +void clif_scriptclose(struct map_session_data *sd, int npcid) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xb6)); + WFIFOW(fd,0)=0xb6; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0xb6)); +} + +/*========================================== + * + *------------------------------------------*/ +void clif_sendfakenpc(struct map_session_data *sd, int npcid) +{ + unsigned char *buf; + int fd = sd->fd; + sd->state.using_fake_npc = 1; + + WFIFOHEAD(fd, packet_len(0x78)); + buf = WFIFOP(fd,0); + memset(WBUFP(buf,0), 0, packet_len(0x78)); + WBUFW(buf,0)=0x78; +#if PACKETVER >= 20071106 + WBUFB(buf,2) = 0; // object type + buf = WFIFOP(fd,1); +#endif + WBUFL(buf,2)=npcid; + WBUFW(buf,14)=111; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y,sd->ud.dir); + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WFIFOSET(fd, packet_len(0x78)); +} + + +/// Displays an NPC dialog menu (ZC_MENU_LIST). +/// 00b7 <packet len>.W <npc id>.L <menu items>.?B +/// Client behavior: +/// - disable mouse targeting +/// - close the menu window +/// - open the menu window +/// - add options to the menu (separated in the text by ":") +/// - set npcid of menu window +/// - if dialog window is open: +/// - remove 'next' button +/// When 'ok' is pressed: +/// - 00B8 <npcid of menu window>.L <selected option>.B +/// - close the menu window +/// When 'cancel' is pressed: +/// - 00B8 <npcid of menu window>.L <-1>.B +/// - enable mouse targeting +/// - close a bunch of windows... +/// WARNING: the 'cancel' button closes other windows besides the dialog window and the menu window. +/// Which suggests their have intertwined behavior. (probably the mouse targeting) +/// TODO investigate behavior of other windows [FlavioJS] +void clif_scriptmenu(struct map_session_data* sd, int npcid, const char* mes) +{ + int fd = sd->fd; + int slen = strlen(mes) + 9; + struct block_list *bl = NULL; + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + WFIFOHEAD(fd, slen); + WFIFOW(fd,0)=0xb7; + WFIFOW(fd,2)=slen; + WFIFOL(fd,4)=npcid; + memcpy((char*)WFIFOP(fd,8), mes, slen-8); + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLG). +/// 0142 <npc id>.L +/// Client behavior (inputnum window): +/// - if npcid exists in the client: +/// - open the inputnum window +/// - set npcid of inputnum window +/// When 'ok' is pressed: +/// - if inputnum window has text: +/// - if npcid exists in the client: +/// - 0143 <npcid of inputnum window>.L <atoi(text)>.L +/// - close inputnum window +void clif_scriptinput(struct map_session_data *sd, int npcid) +{ + int fd; + struct block_list *bl = NULL; + + nullpo_retv(sd); + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x142)); + WFIFOW(fd,0)=0x142; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0x142)); +} + + +/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLGSTR). +/// 01d4 <npc id>.L +/// Client behavior (inputstr window): +/// - if npcid is 0 or npcid exists in the client: +/// - open the inputstr window +/// - set npcid of inputstr window +/// When 'ok' is pressed: +/// - if inputstr window has text and isn't an insult(manner.txt): +/// - if npcid is 0 or npcid exists in the client: +/// - 01d5 <packetlen>.W <npcid of inputstr window>.L <text>.?B +/// - close inputstr window +void clif_scriptinputstr(struct map_session_data *sd, int npcid) +{ + int fd; + struct block_list *bl = NULL; + + nullpo_retv(sd); + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x1d4)); + WFIFOW(fd,0)=0x1d4; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0x1d4)); +} + + +/// Marks a position on client's minimap (ZC_COMPASS). +/// 0144 <npc id>.L <type>.L <x>.L <y>.L <id>.B <color>.L +/// npc id: +/// is ignored in the client +/// type: +/// 0 = display mark for 15 seconds +/// 1 = display mark until dead or teleported +/// 2 = remove mark +/// color: +/// 0x00RRGGBB +void clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x144)); + WFIFOW(fd,0)=0x144; + WFIFOL(fd,2)=npc_id; + WFIFOL(fd,6)=type; + WFIFOL(fd,10)=x; + WFIFOL(fd,14)=y; + WFIFOB(fd,18)=id; + WFIFOL(fd,19)=color; + WFIFOSET(fd,packet_len(0x144)); +} + + +/// Displays an illustration image. +/// 0145 <image name>.16B <type>.B (ZC_SHOW_IMAGE) +/// 01b3 <image name>.64B <type>.B (ZC_SHOW_IMAGE2) +/// type: +/// 0 = bottom left corner +/// 1 = bottom middle +/// 2 = bottom right corner +/// 3 = middle of screen, inside a movable window +/// 4 = middle of screen, movable with a close button, chrome-less +void clif_cutin(struct map_session_data* sd, const char* image, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x1b3)); + WFIFOW(fd,0)=0x1b3; + strncpy((char*)WFIFOP(fd,2),image,64); + WFIFOB(fd,66)=type; + WFIFOSET(fd,packet_len(0x1b3)); +} + + +/*========================================== + * Fills in card data from the given item and into the buffer. [Skotlex] + *------------------------------------------*/ +static void clif_addcards(unsigned char* buf, struct item* item) +{ + int i=0,j; + if( item == NULL ) { //Blank data + WBUFW(buf,0) = 0; + WBUFW(buf,2) = 0; + WBUFW(buf,4) = 0; + WBUFW(buf,6) = 0; + return; + } + if( item->card[0] == CARD0_PET ) { //pet eggs + WBUFW(buf,0) = 0; + WBUFW(buf,2) = 0; + WBUFW(buf,4) = 0; + WBUFW(buf,6) = item->card[3]; //Pet renamed flag. + return; + } + if( item->card[0] == CARD0_FORGE || item->card[0] == CARD0_CREATE ) { //Forged/created items + WBUFW(buf,0) = item->card[0]; + WBUFW(buf,2) = item->card[1]; + WBUFW(buf,4) = item->card[2]; + WBUFW(buf,6) = item->card[3]; + return; + } + //Client only receives four cards.. so randomly send them a set of cards. [Skotlex] + if( MAX_SLOTS > 4 && (j = itemdb_slot(item->nameid)) > 4 ) + i = rnd()%(j-3); //eg: 6 slots, possible i values: 0->3, 1->4, 2->5 => i = rnd()%3; + + //Normal items. + if( item->card[i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,0) = j; + else + WBUFW(buf,0) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,2) = j; + else + WBUFW(buf,2) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,4) = j; + else + WBUFW(buf,4) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,6) = j; + else + WBUFW(buf,6) = item->card[i]; +} + + +/// Notifies the client, about a received inventory item or the result of a pick-up request. +/// 00a0 <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B (ZC_ITEM_PICKUP_ACK) +/// 029a <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B <expire time>.L (ZC_ITEM_PICKUP_ACK2) +/// 02d4 <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B <expire time>.L <bindOnEquipType>.W (ZC_ITEM_PICKUP_ACK3) +void clif_additem(struct map_session_data *sd, int n, int amount, int fail) +{ + int fd; +#if PACKETVER < 20061218 + const int cmd = 0xa0; +#elif PACKETVER < 20071002 + const int cmd = 0x29a; +#else + const int cmd = 0x2d4; +#endif + nullpo_retv(sd); + + fd = sd->fd; + if( !session_isActive(fd) ) //Sasuke- + return; + + WFIFOHEAD(fd,packet_len(cmd)); + if( fail ) + { + WFIFOW(fd,0)=cmd; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + WFIFOW(fd,6)=0; + WFIFOB(fd,8)=0; + WFIFOB(fd,9)=0; + WFIFOB(fd,10)=0; + WFIFOW(fd,11)=0; + WFIFOW(fd,13)=0; + WFIFOW(fd,15)=0; + WFIFOW(fd,17)=0; + WFIFOW(fd,19)=0; + WFIFOB(fd,21)=0; + WFIFOB(fd,22)=fail; +#if PACKETVER >= 20061218 + WFIFOL(fd,23)=0; +#endif +#if PACKETVER >= 20071002 + WFIFOW(fd,27)=0; // unknown +#endif + } + else + { + if( n < 0 || n >= MAX_INVENTORY || sd->status.inventory[n].nameid <=0 || sd->inventory_data[n] == NULL ) + return; + + WFIFOW(fd,0)=cmd; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + if (sd->inventory_data[n]->view_id > 0) + WFIFOW(fd,6)=sd->inventory_data[n]->view_id; + else + WFIFOW(fd,6)=sd->status.inventory[n].nameid; + WFIFOB(fd,8)=sd->status.inventory[n].identify; + WFIFOB(fd,9)=sd->status.inventory[n].attribute; + WFIFOB(fd,10)=sd->status.inventory[n].refine; + clif_addcards(WFIFOP(fd,11), &sd->status.inventory[n]); + WFIFOW(fd,19)=pc_equippoint(sd,n); + WFIFOB(fd,21)=itemtype(sd->inventory_data[n]->type); + WFIFOB(fd,22)=fail; +#if PACKETVER >= 20061218 + WFIFOL(fd,23)=sd->status.inventory[n].expire_time; +#endif +#if PACKETVER >= 20071002 + WFIFOW(fd,27)=0; // unknown +#endif + } + + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client, that an inventory item was deleted or dropped (ZC_ITEM_THROW_ACK). +/// 00af <index>.W <amount>.W +void clif_dropitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xaf)); + WFIFOW(fd,0)=0xaf; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + WFIFOSET(fd,packet_len(0xaf)); +} + + +/// Notifies the client, that an inventory item was deleted (ZC_DELETE_ITEM_FROM_BODY). +/// 07fa <delete type>.W <index>.W <amount>.W +/// delete type: +/// 0 = Normal +/// 1 = Item used for a skill +/// 2 = Refine failed +/// 3 = Material changed +/// 4 = Moved to storage +/// 5 = Moved to cart +/// 6 = Item sold +/// 7 = Consumed by Four Spirit Analysis (SO_EL_ANALYSIS) skill +void clif_delitem(struct map_session_data *sd,int n,int amount, short reason) +{ +#if PACKETVER < 20091117 + clif_dropitem(sd,n,amount); +#else + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd, packet_len(0x7fa)); + WFIFOW(fd,0)=0x7fa; + WFIFOW(fd,2)=reason; + WFIFOW(fd,4)=n+2; + WFIFOW(fd,6)=amount; + WFIFOSET(fd,packet_len(0x7fa)); +#endif +} + + +// Simplifies inventory/cart/storage packets by handling the packet section relevant to items. [Skotlex] +// Equip is >= 0 for equippable items (holds the equip-point, is 0 for pet +// armor/egg) -1 for stackable items, -2 for stackable items where arrows must send in the equip-point. +void clif_item_sub(unsigned char *buf, int n, struct item *i, struct item_data *id, int equip) +{ + if (id->view_id > 0) + WBUFW(buf,n)=id->view_id; + else + WBUFW(buf,n)=i->nameid; + WBUFB(buf,n+2)=itemtype(id->type); + WBUFB(buf,n+3)=i->identify; + if (equip >= 0) { //Equippable item + WBUFW(buf,n+4)=equip; + WBUFW(buf,n+6)=i->equip; + WBUFB(buf,n+8)=i->attribute; + WBUFB(buf,n+9)=i->refine; + } else { //Stackable item. + WBUFW(buf,n+4)=i->amount; + if (equip == -2 && id->equip == EQP_AMMO) + WBUFW(buf,n+6)=EQP_AMMO; + else + WBUFW(buf,n+6)=0; + } + +} +void clif_favorite_item(struct map_session_data* sd, unsigned short index); +//Unified inventory function which sends all of the inventory (requires two packets, one for equipable items and one for stackable ones. [Skotlex] +void clif_inventorylist(struct map_session_data *sd) +{ + int i,n,ne,arrow=-1; + unsigned char *buf; + unsigned char *bufe; + +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int se = 20; +#elif PACKETVER < 20100629 + const int se = 26; +#else + const int se = 28; +#endif + + buf = (unsigned char*)aMalloc(MAX_INVENTORY * s + 4); + bufe = (unsigned char*)aMalloc(MAX_INVENTORY * se + 4); + + for( i = 0, n = 0, ne = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL ) + continue; + + if( !itemdb_isstackable2(sd->inventory_data[i]) ) + { //Non-stackable (Equippable) + WBUFW(bufe,ne*se+4)=i+2; + clif_item_sub(bufe, ne*se+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); + clif_addcards(WBUFP(bufe, ne*se+16), &sd->status.inventory[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*se+24)=sd->status.inventory[i].expire_time; + WBUFW(bufe,ne*se+28)=0; //Unknown +#endif +#if PACKETVER >= 20100629 + if (sd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(bufe,ne*se+30)= sd->inventory_data[i]->look; + else + WBUFW(bufe,ne*se+30)=0; +#endif + ne++; + } + else + { //Stackable. + WBUFW(buf,n*s+4)=i+2; + clif_item_sub(buf, n*s+6, &sd->status.inventory[i], sd->inventory_data[i], -2); + if( sd->inventory_data[i]->equip == EQP_AMMO && sd->status.inventory[i].equip ) + arrow=i; +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf, n*s+14), &sd->status.inventory[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=sd->status.inventory[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0xa3; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1ee; +#else + WBUFW(buf,0)=0x2e8; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( arrow >= 0 ) + clif_arrowequip(sd,arrow); + + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0xa4; +#else + WBUFW(bufe,0)=0x2d0; +#endif + WBUFW(bufe,2)=4+ne*se; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } +#if PACKETVER >= 20111122 + for( i = 0; i < MAX_INVENTORY; i++ ) { + if( sd->status.inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL ) + continue; + + if ( sd->status.inventory[i].favorite ) + clif_favorite_item(sd, i); + } +#endif + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + +//Required when items break/get-repaired. Only sends equippable item list. +void clif_equiplist(struct map_session_data *sd) +{ + int i,n,fd = sd->fd; + unsigned char *buf; +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + WFIFOHEAD(fd, MAX_INVENTORY * cmd + 4); + buf = WFIFOP(fd,0); + + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if (sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL) + continue; + + if(itemdb_isstackable2(sd->inventory_data[i])) + continue; + //Equippable + WBUFW(buf,n*cmd+4)=i+2; + clif_item_sub(buf, n*cmd+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); + clif_addcards(WBUFP(buf, n*cmd+16), &sd->status.inventory[i]); +#if PACKETVER >= 20071002 + WBUFL(buf,n*cmd+24)=sd->status.inventory[i].expire_time; + WBUFW(buf,n*cmd+28)=0; //Unknown +#endif +#if PACKETVER >= 20100629 + if (sd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(buf,n*cmd+30)= sd->inventory_data[i]->look; + else + WBUFW(buf,n*cmd+30)=0; +#endif + n++; + } + if (n) { +#if PACKETVER < 20071002 + WBUFW(buf,0)=0xa4; +#else + WBUFW(buf,0)=0x2d0; +#endif + WBUFW(buf,2)=4+n*cmd; + WFIFOSET(fd,WFIFOW(fd,2)); + } +} + +void clif_storagelist(struct map_session_data* sd, struct item* items, int items_length) +{ + struct item_data *id; + int i,n,ne; + unsigned char *buf; + unsigned char *bufe; +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + buf = (unsigned char*)aMalloc(items_length * s + 4); + bufe = (unsigned char*)aMalloc(items_length * cmd + 4); + + for( i = 0, n = 0, ne = 0; i < items_length; i++ ) + { + if( items[i].nameid <= 0 ) + continue; + id = itemdb_search(items[i].nameid); + if( !itemdb_isstackable2(id) ) + { //Equippable + WBUFW(bufe,ne*cmd+4)=i+1; + clif_item_sub(bufe, ne*cmd+6, &items[i], id, id->equip); + clif_addcards(WBUFP(bufe, ne*cmd+16), &items[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*cmd+24)=items[i].expire_time; + WBUFW(bufe,ne*cmd+28)=0; //Unknown +#endif + ne++; + } + else + { //Stackable + WBUFW(buf,n*s+4)=i+1; + clif_item_sub(buf, n*s+6, &items[i], id,-1); +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf,n*s+14), &items[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=items[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1f0; +#else + WBUFW(buf,0)=0x2ea; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0xa6; +#else + WBUFW(bufe,0)=0x2d1; +#endif + WBUFW(bufe,2)=4+ne*cmd; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + +void clif_cartlist(struct map_session_data *sd) +{ + struct item_data *id; + int i,n,ne; + unsigned char *buf; + unsigned char *bufe; +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + buf = (unsigned char*)aMalloc(MAX_CART * s + 4); + bufe = (unsigned char*)aMalloc(MAX_CART * cmd + 4); + + for( i = 0, n = 0, ne = 0; i < MAX_CART; i++ ) + { + if( sd->status.cart[i].nameid <= 0 ) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if( !itemdb_isstackable2(id) ) + { //Equippable + WBUFW(bufe,ne*cmd+4)=i+2; + clif_item_sub(bufe, ne*cmd+6, &sd->status.cart[i], id, id->equip); + clif_addcards(WBUFP(bufe, ne*cmd+16), &sd->status.cart[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*cmd+24)=sd->status.cart[i].expire_time; + WBUFW(bufe,ne*cmd+28)=0; //Unknown +#endif + ne++; + } + else + { //Stackable + WBUFW(buf,n*s+4)=i+2; + clif_item_sub(buf, n*s+6, &sd->status.cart[i], id,-1); +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf,n*s+14), &sd->status.cart[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=sd->status.cart[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0x123; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1ef; +#else + WBUFW(buf,0)=0x2e9; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0x122; +#else + WBUFW(bufe,0)=0x2d2; +#endif + WBUFW(bufe,2)=4+ne*cmd; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + + +/// Removes cart (ZC_CARTOFF). +/// 012b +/// Client behaviour: +/// Closes the cart storage and removes all it's items from memory. +/// The Num & Weight values of the cart are left untouched and the cart is NOT removed. +void clif_clearcart(int fd) +{ + WFIFOHEAD(fd, packet_len(0x12b)); + WFIFOW(fd,0) = 0x12b; + WFIFOSET(fd, packet_len(0x12b)); + +} + + +/// Guild XY locators (ZC_NOTIFY_POSITION_TO_GUILDM) [Valaris] +/// 01eb <account id>.L <x>.W <y>.W +void clif_guild_xy(struct map_session_data *sd) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1eb; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=sd->bl.x; + WBUFW(buf,8)=sd->bl.y; + clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); +} + +/*========================================== + * Sends x/y dot to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_guild_xy_single(int fd, struct map_session_data *sd) +{ + if( sd->bg_id ) + return; + + WFIFOHEAD(fd,packet_len(0x1eb)); + WFIFOW(fd,0)=0x1eb; + WFIFOL(fd,2)=sd->status.account_id; + WFIFOW(fd,6)=sd->bl.x; + WFIFOW(fd,8)=sd->bl.y; + WFIFOSET(fd,packet_len(0x1eb)); +} + +// Guild XY locators [Valaris] +void clif_guild_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1eb; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=-1; + WBUFW(buf,8)=-1; + clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); +} + +/*========================================== + * + *------------------------------------------*/ +static int clif_hpmeter_sub(struct block_list *bl, va_list ap) +{ + struct map_session_data *sd, *tsd; +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + + sd = va_arg(ap, struct map_session_data *); + tsd = (TBL_PC *)bl; + + nullpo_ret(sd); + nullpo_ret(tsd); + + if( !tsd->fd || tsd == sd ) + return 0; + + if( !pc_has_permission(tsd, PC_PERM_VIEW_HPMETER) ) + return 0; + WFIFOHEAD(tsd->fd,packet_len(cmd)); + WFIFOW(tsd->fd,0) = cmd; + WFIFOL(tsd->fd,2) = sd->status.account_id; +#if PACKETVER < 20100126 + if( sd->battle_status.max_hp > INT16_MAX ) + { //To correctly display the %hp bar. [Skotlex] + WFIFOW(tsd->fd,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WFIFOW(tsd->fd,8) = 100; + } else { + WFIFOW(tsd->fd,6) = sd->battle_status.hp; + WFIFOW(tsd->fd,8) = sd->battle_status.max_hp; + } +#else + WFIFOL(tsd->fd,6) = sd->battle_status.hp; + WFIFOL(tsd->fd,10) = sd->battle_status.max_hp; +#endif + WFIFOSET(tsd->fd,packet_len(cmd)); + return 0; +} + +/*========================================== + * Server tells all players that are allowed to view HP bars + * and are nearby 'sd' that 'sd' hp bar was updated. + *------------------------------------------*/ +static int clif_hpmeter(struct map_session_data *sd) +{ + nullpo_ret(sd); + map_foreachinarea(clif_hpmeter_sub, sd->bl.m, sd->bl.x-AREA_SIZE, sd->bl.y-AREA_SIZE, sd->bl.x+AREA_SIZE, sd->bl.y+AREA_SIZE, BL_PC, sd); + return 0; +} + +/// Notifies client of a character parameter change. +/// 00b0 <var id>.W <value>.L (ZC_PAR_CHANGE) +/// 00b1 <var id>.W <value>.L (ZC_LONGPAR_CHANGE) +/// 00be <status id>.W <value>.B (ZC_STATUS_CHANGE) +/// 0121 <current count>.W <max count>.W <current weight>.L <max weight>.L (ZC_NOTIFY_CARTITEM_COUNTINFO) +/// 013a <atk range>.W (ZC_ATTACK_RANGE) +/// 0141 <status id>.L <base status>.L <plus status>.L (ZC_COUPLESTATUS) +/// TODO: Extract individual packets. +/// FIXME: Packet lengths from packet_len(cmd) +void clif_updatestatus(struct map_session_data *sd,int type) +{ + int fd,len=8; + + nullpo_retv(sd); + + fd=sd->fd; + + if ( !session_isActive(fd) ) // Invalid pointer fix, by sasuke [Kevin] + return; + + WFIFOHEAD(fd, 14); + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + switch(type){ + // 00b0 + case SP_WEIGHT: + pc_updateweightstatus(sd); + WFIFOHEAD(fd,14); + WFIFOW(fd,0)=0xb0; //Need to re-set as pc_updateweightstatus can alter the buffer. [Skotlex] + WFIFOW(fd,2)=type; + WFIFOL(fd,4)=sd->weight; + break; + case SP_MAXWEIGHT: + WFIFOL(fd,4)=sd->max_weight; + break; + case SP_SPEED: + WFIFOL(fd,4)=sd->battle_status.speed; + break; + case SP_BASELEVEL: + WFIFOL(fd,4)=sd->status.base_level; + break; + case SP_JOBLEVEL: + WFIFOL(fd,4)=sd->status.job_level; + break; + case SP_KARMA: // Adding this back, I wonder if the client intercepts this - [Lance] + WFIFOL(fd,4)=sd->status.karma; + break; + case SP_MANNER: + WFIFOL(fd,4)=sd->status.manner; + break; + case SP_STATUSPOINT: + WFIFOL(fd,4)=sd->status.status_point; + break; + case SP_SKILLPOINT: + WFIFOL(fd,4)=sd->status.skill_point; + break; + case SP_HIT: + WFIFOL(fd,4)=sd->battle_status.hit; + break; + case SP_FLEE1: + WFIFOL(fd,4)=sd->battle_status.flee; + break; + case SP_FLEE2: + WFIFOL(fd,4)=sd->battle_status.flee2/10; + break; + case SP_MAXHP: + WFIFOL(fd,4)=sd->battle_status.max_hp; + break; + case SP_MAXSP: + WFIFOL(fd,4)=sd->battle_status.max_sp; + break; + case SP_HP: + WFIFOL(fd,4)=sd->battle_status.hp; + // TODO: Won't these overwrite the current packet? + clif_hpmeter(sd); + if( !battle_config.party_hp_mode && sd->status.party_id ) + clif_party_hp(sd); + if( sd->bg_id ) + clif_bg_hp(sd); + break; + case SP_SP: + WFIFOL(fd,4)=sd->battle_status.sp; + break; + case SP_ASPD: + WFIFOL(fd,4)=sd->battle_status.amotion; + break; + case SP_ATK1: + WFIFOL(fd,4)=pc_leftside_atk(sd); + break; + case SP_DEF1: + WFIFOL(fd,4)=pc_leftside_def(sd); + break; + case SP_MDEF1: + WFIFOL(fd,4)=pc_leftside_mdef(sd); + break; + case SP_ATK2: + WFIFOL(fd,4)=pc_rightside_atk(sd); + break; + case SP_DEF2: + WFIFOL(fd,4)=pc_rightside_def(sd); + break; + case SP_MDEF2: { + //negative check (in case you have something like Berserk active) + int mdef2 = pc_rightside_mdef(sd); + + WFIFOL(fd,4)= +#ifndef RENEWAL + ( mdef2 < 0 ) ? 0 : +#endif + mdef2; + + } + break; + case SP_CRITICAL: + WFIFOL(fd,4)=sd->battle_status.cri/10; + break; + case SP_MATK1: + WFIFOL(fd,4)=pc_rightside_matk(sd); + break; + case SP_MATK2: + WFIFOL(fd,4)=pc_leftside_matk(sd); + break; + + + case SP_ZENY: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.zeny; + break; + case SP_BASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.base_exp; + break; + case SP_JOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextbaseexp(sd); + break; + case SP_NEXTJOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextjobexp(sd); + break; + + /** + * SP_U<STAT> are used to update the amount of points necessary to increase that stat + **/ + case SP_USTR: + case SP_UAGI: + case SP_UVIT: + case SP_UINT: + case SP_UDEX: + case SP_ULUK: + WFIFOW(fd,0)=0xbe; + WFIFOB(fd,4)=pc_need_status_point(sd,type-SP_USTR+SP_STR,1); + len=5; + break; + + /** + * Tells the client how far it is allowed to attack (weapon range) + **/ + case SP_ATTACKRANGE: + WFIFOW(fd,0)=0x13a; + WFIFOW(fd,2)=sd->battle_status.rhw.range; + len=4; + break; + + case SP_STR: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.str; + WFIFOL(fd,10)=sd->battle_status.str - sd->status.str; + len=14; + break; + case SP_AGI: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.agi; + WFIFOL(fd,10)=sd->battle_status.agi - sd->status.agi; + len=14; + break; + case SP_VIT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.vit; + WFIFOL(fd,10)=sd->battle_status.vit - sd->status.vit; + len=14; + break; + case SP_INT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.int_; + WFIFOL(fd,10)=sd->battle_status.int_ - sd->status.int_; + len=14; + break; + case SP_DEX: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.dex; + WFIFOL(fd,10)=sd->battle_status.dex - sd->status.dex; + len=14; + break; + case SP_LUK: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.luk; + WFIFOL(fd,10)=sd->battle_status.luk - sd->status.luk; + len=14; + break; + + case SP_CARTINFO: + WFIFOW(fd,0)=0x121; + WFIFOW(fd,2)=sd->cart_num; + WFIFOW(fd,4)=MAX_CART; + WFIFOL(fd,6)=sd->cart_weight; + WFIFOL(fd,10)=sd->cart_weight_max; + len=14; + break; + + default: + ShowError("clif_updatestatus : unrecognized type %d\n",type); + return; + } + WFIFOSET(fd,len); +} + + +/// Notifies client of a parameter change of an another player (ZC_PAR_CHANGE_USER). +/// 01ab <account id>.L <var id>.W <value>.L +void clif_changestatus(struct map_session_data* sd,int type,int val) +{ + unsigned char buf[12]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1ab; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=type; + + switch(type) + { + case SP_MANNER: + WBUFL(buf,8)=val; + break; + default: + ShowError("clif_changestatus : unrecognized type %d.\n",type); + return; + } + + clif_send(buf,packet_len(0x1ab),&sd->bl,AREA_WOS); +} + + +/// Updates sprite/style properties of an object. +/// 00c3 <id>.L <type>.B <value>.B (ZC_SPRITE_CHANGE) +/// 01d7 <id>.L <type>.B <value>.L (ZC_SPRITE_CHANGE2) +void clif_changelook(struct block_list *bl,int type,int val) +{ + unsigned char buf[16]; + struct map_session_data* sd = NULL; + struct status_change* sc; + struct view_data* vd; + enum send_target target = AREA; + nullpo_retv(bl); + + sd = BL_CAST(BL_PC, bl); + sc = status_get_sc(bl); + vd = status_get_viewdata(bl); + //nullpo_ret(vd); + if( vd ) //temp hack to let Warp Portal change appearance + switch(type) + { + case LOOK_WEAPON: + if (sd) + { + clif_get_weapon_view(sd, &vd->weapon, &vd->shield); + val = vd->weapon; + } + else vd->weapon = val; + break; + case LOOK_SHIELD: + if (sd) + { + clif_get_weapon_view(sd, &vd->weapon, &vd->shield); + val = vd->shield; + } + else vd->shield = val; + break; + case LOOK_BASE: + vd->class_ = val; + if (vd->class_ == JOB_WEDDING || vd->class_ == JOB_XMAS || vd->class_ == JOB_SUMMER) + vd->weapon = vd->shield = 0; + if (vd->cloth_color && ( + (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || + (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || + (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) + )) + clif_changelook(bl,LOOK_CLOTHES_COLOR,0); + break; + case LOOK_HAIR: + vd->hair_style = val; + break; + case LOOK_HEAD_BOTTOM: + vd->head_bottom = val; + break; + case LOOK_HEAD_TOP: + vd->head_top = val; + break; + case LOOK_HEAD_MID: + vd->head_mid = val; + break; + case LOOK_HAIR_COLOR: + vd->hair_color = val; + break; + case LOOK_CLOTHES_COLOR: + if (val && ( + (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || + (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || + (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) + )) + val = 0; + vd->cloth_color = val; + break; + case LOOK_SHOES: +#if PACKETVER > 3 + if (sd) { + int n; + if((n = sd->equip_index[2]) >= 0 && sd->inventory_data[n]) { + if(sd->inventory_data[n]->view_id > 0) + val = sd->inventory_data[n]->view_id; + else + val = sd->status.inventory[n].nameid; + } else + val = 0; + } +#endif + //Shoes? No packet uses this.... + break; + case LOOK_BODY: + case LOOK_FLOOR: + // unknown purpose + break; + case LOOK_ROBE: +#if PACKETVER < 20110111 + return; +#else + vd->robe = val; +#endif + break; + } + + // prevent leaking the presence of GM-hidden objects + if( sc && sc->option&OPTION_INVISIBLE ) + target = SELF; + +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,target); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + if(type == LOOK_WEAPON || type == LOOK_SHIELD) { + WBUFB(buf,6)=LOOK_WEAPON; + WBUFW(buf,7)=vd->weapon; + WBUFW(buf,9)=vd->shield; + } else { + WBUFB(buf,6)=type; + WBUFL(buf,7)=val; + } + clif_send(buf,packet_len(0x1d7),bl,target); +#endif +} + +//Sends a change-base-look packet required for traps as they are triggered. +void clif_changetraplook(struct block_list *bl,int val) +{ + unsigned char buf[32]; +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=LOOK_BASE; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,AREA); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=LOOK_BASE; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len(0x1d7),bl,AREA); +#endif +} + +//For the stupid cloth-dye bug. Resends the given view data to the area specified by bl. +void clif_refreshlook(struct block_list *bl,int id,int type,int val,enum send_target target) +{ + unsigned char buf[32]; +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,target); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=id; + WBUFB(buf,6)=type; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len(0x1d7),bl,target); +#endif +} + + +/// Character status (ZC_STATUS). +/// 00bd <stpoint>.W <str>.B <need str>.B <agi>.B <need agi>.B <vit>.B <need vit>.B +/// <int>.B <need int>.B <dex>.B <need dex>.B <luk>.B <need luk>.B <atk>.W <atk2>.W +/// <matk min>.W <matk max>.W <def>.W <def2>.W <mdef>.W <mdef2>.W <hit>.W +/// <flee>.W <flee2>.W <crit>.W <aspd>.W <aspd2>.W +void clif_initialstatus(struct map_session_data *sd) +{ + int fd, mdef2; + unsigned char *buf; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xbd)); + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xbd; + WBUFW(buf,2)=min(sd->status.status_point, INT16_MAX); + WBUFB(buf,4)=min(sd->status.str, UINT8_MAX); + WBUFB(buf,5)=pc_need_status_point(sd,SP_STR,1); + WBUFB(buf,6)=min(sd->status.agi, UINT8_MAX); + WBUFB(buf,7)=pc_need_status_point(sd,SP_AGI,1); + WBUFB(buf,8)=min(sd->status.vit, UINT8_MAX); + WBUFB(buf,9)=pc_need_status_point(sd,SP_VIT,1); + WBUFB(buf,10)=min(sd->status.int_, UINT8_MAX); + WBUFB(buf,11)=pc_need_status_point(sd,SP_INT,1); + WBUFB(buf,12)=min(sd->status.dex, UINT8_MAX); + WBUFB(buf,13)=pc_need_status_point(sd,SP_DEX,1); + WBUFB(buf,14)=min(sd->status.luk, UINT8_MAX); + WBUFB(buf,15)=pc_need_status_point(sd,SP_LUK,1); + + WBUFW(buf,16) = pc_leftside_atk(sd); + WBUFW(buf,18) = pc_rightside_atk(sd); + WBUFW(buf,20) = pc_rightside_matk(sd); + WBUFW(buf,22) = pc_leftside_matk(sd); + WBUFW(buf,24) = pc_leftside_def(sd); + WBUFW(buf,26) = pc_rightside_def(sd); + WBUFW(buf,28) = pc_leftside_mdef(sd); + mdef2 = pc_rightside_mdef(sd); + WBUFW(buf,30) = +#ifndef RENEWAL + ( mdef2 < 0 ) ? 0 : //Negative check for Frenzy'ed characters. +#endif + mdef2; + WBUFW(buf,32) = sd->battle_status.hit; + WBUFW(buf,34) = sd->battle_status.flee; + WBUFW(buf,36) = sd->battle_status.flee2/10; + WBUFW(buf,38) = sd->battle_status.cri/10; + WBUFW(buf,40) = sd->battle_status.amotion; // aspd + WBUFW(buf,42) = 0; // always 0 (plusASPD) + + WFIFOSET(fd,packet_len(0xbd)); + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_ATTACKRANGE); + clif_updatestatus(sd,SP_ASPD); +} + + +/// Marks an ammunition item in inventory as equipped (ZC_EQUIP_ARROW). +/// 013c <index>.W +void clif_arrowequip(struct map_session_data *sd,int val) +{ + int fd; + + nullpo_retv(sd); + + pc_stop_attack(sd); // [Valaris] + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x013c)); + WFIFOW(fd,0)=0x013c; + WFIFOW(fd,2)=val+2; //Item ID of the arrow + WFIFOSET(fd,packet_len(0x013c)); +} + + +/// Ammunition action message (ZC_ACTION_FAILURE). +/// 013b <type>.W +/// type: +/// 0 = MsgStringTable[242]="Please equip the proper ammunition first." +/// 1 = MsgStringTable[243]="You can't Attack or use Skills because your Weight Limit has been exceeded." +/// 2 = MsgStringTable[244]="You can't use Skills because Weight Limit has been exceeded." +/// 3 = assassin, baby_assassin, assassin_cross => MsgStringTable[1040]="You have equipped throwing daggers." +/// gunslinger => MsgStringTable[1175]="Bullets have been equipped." +/// NOT ninja => MsgStringTable[245]="Ammunition has been equipped." +void clif_arrow_fail(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x013b)); + WFIFOW(fd,0)=0x013b; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x013b)); +} + + +/// Presents a list of items, that can be processed by Arrow Crafting (ZC_MAKINGARROW_LIST). +/// 01ad <packet len>.W { <name id>.W }* +void clif_arrow_create_list(struct map_session_data *sd) +{ + int i, c, j; + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd, MAX_SKILL_ARROW_DB*2+4); + WFIFOW(fd,0) = 0x1ad; + + for (i = 0, c = 0; i < MAX_SKILL_ARROW_DB; i++) { + if (skill_arrow_db[i].nameid > 0 && + (j = pc_search_inventory(sd, skill_arrow_db[i].nameid)) >= 0 && + !sd->status.inventory[j].equip && sd->status.inventory[j].identify) + { + if ((j = itemdb_viewid(skill_arrow_db[i].nameid)) > 0) + WFIFOW(fd,c*2+4) = j; + else + WFIFOW(fd,c*2+4) = skill_arrow_db[i].nameid; + c++; + } + } + WFIFOW(fd,2) = c*2+4; + WFIFOSET(fd, WFIFOW(fd,2)); + if (c > 0) { + sd->menuskill_id = AC_MAKINGARROW; + sd->menuskill_val = c; + } +} + + +/// Notifies the client, about the result of an status change request (ZC_STATUS_CHANGE_ACK). +/// 00bc <status id>.W <result>.B <value>.B +/// status id: +/// SP_STR ~ SP_LUK +/// result: +/// 0 = failure +/// 1 = success +void clif_statusupack(struct map_session_data *sd,int type,int ok,int val) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xbc)); + WFIFOW(fd,0)=0xbc; + WFIFOW(fd,2)=type; + WFIFOB(fd,4)=ok; + WFIFOB(fd,5)=cap_value(val,0,UINT8_MAX); + WFIFOSET(fd,packet_len(0xbc)); +} + + +/// Notifies the client about the result of a request to equip an item (ZC_REQ_WEAR_EQUIP_ACK). +/// 00aa <index>.W <equip location>.W <result>.B +/// 00aa <index>.W <equip location>.W <view id>.W <result>.B (PACKETVER >= 20100629) +/// result: +/// 0 = failure +/// 1 = success +/// 2 = failure due to low level +void clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xaa)); + WFIFOW(fd,0)=0xaa; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; +#if PACKETVER < 20100629 + WFIFOB(fd,6)=ok; +#else + if (ok && sd->inventory_data[n]->equip&EQP_VISIBLE) + WFIFOW(fd,6)=sd->inventory_data[n]->look; + else + WFIFOW(fd,6)=0; + WFIFOB(fd,8)=ok; +#endif + WFIFOSET(fd,packet_len(0xaa)); +} + + +/// Notifies the client about the result of a request to take off an item (ZC_REQ_TAKEOFF_EQUIP_ACK). +/// 00ac <index>.W <equip location>.W <result>.B +/// result: +/// 0 = failure +/// 1 = success +void clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xac)); + WFIFOW(fd,0)=0xac; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xac)); +} + + +/// Notifies clients in the area about an special/visual effect (ZC_NOTIFY_EFFECT). +/// 019b <id>.L <effect id>.L +/// effect id: +/// 0 = base level up +/// 1 = job level up +/// 2 = refine failure +/// 3 = refine success +/// 4 = game over +/// 5 = pharmacy success +/// 6 = pharmacy failure +/// 7 = base level up (super novice) +/// 8 = job level up (super novice) +/// 9 = base level up (taekwon) +void clif_misceffect(struct block_list* bl,int type) +{ + unsigned char buf[32]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x19b; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf,packet_len(0x19b),bl,AREA); +} + + +/// Notifies clients in the area of a state change. +/// 0119 <id>.L <body state>.W <health state>.W <effect state>.W <pk mode>.B (ZC_STATE_CHANGE) +/// 0229 <id>.L <body state>.W <health state>.W <effect state>.L <pk mode>.B (ZC_STATE_CHANGE3) +void clif_changeoption(struct block_list* bl) +{ + unsigned char buf[32]; + struct status_change *sc; + struct map_session_data* sd; + + nullpo_retv(bl); + sc = status_get_sc(bl); + if (!sc) return; //How can an option change if there's no sc? + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER >= 7 + WBUFW(buf,0) = 0x229; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = sc->opt1; + WBUFW(buf,8) = sc->opt2; + WBUFL(buf,10) = sc->option; + WBUFB(buf,14) = (sd)? sd->status.karma : 0; + if(disguised(bl)) { + clif_send(buf,packet_len(0x229),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x229),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFL(buf,10) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x229),bl,SELF); + } else + clif_send(buf,packet_len(0x229),bl,AREA); +#else + WBUFW(buf,0) = 0x119; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = sc->opt1; + WBUFW(buf,8) = sc->opt2; + WBUFW(buf,10) = sc->option; + WBUFB(buf,12) = (sd)? sd->status.karma : 0; + if(disguised(bl)) { + clif_send(buf,packet_len(0x119),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x119),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFW(buf,10) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x119),bl,SELF); + } else + clif_send(buf,packet_len(0x119),bl,AREA); +#endif +} + + +/// Displays status change effects on NPCs/monsters (ZC_NPC_SHOWEFST_UPDATE). +/// 028a <id>.L <effect state>.L <level>.L <showEFST>.L +void clif_changeoption2(struct block_list* bl) +{ + unsigned char buf[20]; + struct status_change *sc; + + sc = status_get_sc(bl); + if (!sc) return; //How can an option change if there's no sc? + + WBUFW(buf,0) = 0x28a; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = sc->option; + WBUFL(buf,10) = clif_setlevel(bl); + WBUFL(buf,14) = sc->opt3; + if(disguised(bl)) { + clif_send(buf,packet_len(0x28a),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x28a),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x28a),bl,SELF); + } else + clif_send(buf,packet_len(0x28a),bl,AREA); +} + + +/// Notifies the client about the result of an item use request. +/// 00a8 <index>.W <amount>.W <result>.B (ZC_USE_ITEM_ACK) +/// 01c8 <index>.W <name id>.W <id>.L <amount>.W <result>.B (ZC_USE_ITEM_ACK2) +void clif_useitemack(struct map_session_data *sd,int index,int amount,bool ok) +{ + nullpo_retv(sd); + + if(!ok) { + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xa8)); + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xa8)); + } + else { +#if PACKETVER < 3 + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xa8)); + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xa8)); +#else + unsigned char buf[32]; + + WBUFW(buf,0)=0x1c8; + WBUFW(buf,2)=index+2; + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,4)=sd->inventory_data[index]->view_id; + else + WBUFW(buf,4)=sd->status.inventory[index].nameid; + WBUFL(buf,6)=sd->bl.id; + WBUFW(buf,10)=amount; + WBUFB(buf,12)=ok; + clif_send(buf,packet_len(0x1c8),&sd->bl,AREA); +#endif + } +} + + +/// Inform client whether chatroom creation was successful or not (ZC_ACK_CREATE_CHATROOM). +/// 00d6 <flag>.B +/// flag: +/// 0 = Room has been successfully created (opens chat room) +/// 1 = Room limit exceeded +/// 2 = Same room already exists +void clif_createchat(struct map_session_data* sd, int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xd6)); + WFIFOW(fd,0) = 0xd6; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0xd6)); +} + + +/// Display a chat above the owner (ZC_ROOM_NEWENTRY). +/// 00d7 <packet len>.W <owner id>.L <char id>.L <limit>.W <users>.W <type>.B <title>.?B +/// type: +/// 0 = private (password protected) +/// 1 = public +/// 2 = arena (npc waiting room) +/// 3 = PK zone (non-clickable) +void clif_dispchat(struct chat_data* cd, int fd) +{ + unsigned char buf[128]; + uint8 type; + + if( cd == NULL || cd->owner == NULL ) + return; + + type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 + : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 + : 1; + + WBUFW(buf, 0) = 0xd7; + WBUFW(buf, 2) = 17 + strlen(cd->title); + WBUFL(buf, 4) = cd->owner->id; + WBUFL(buf, 8) = cd->bl.id; + WBUFW(buf,12) = cd->limit; + WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; + WBUFB(buf,16) = type; + memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated + + if( fd ) { + WFIFOHEAD(fd,WBUFW(buf,2)); + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WBUFW(buf,2)); + } else { + clif_send(buf,WBUFW(buf,2),cd->owner,AREA_WOSC); + } +} + + +/// Chatroom properties adjustment (ZC_CHANGE_CHATROOM). +/// 00df <packet len>.W <owner id>.L <chat id>.L <limit>.W <users>.W <type>.B <title>.?B +/// type: +/// 0 = private (password protected) +/// 1 = public +/// 2 = arena (npc waiting room) +/// 3 = PK zone (non-clickable) +void clif_changechatstatus(struct chat_data* cd) +{ + unsigned char buf[128]; + uint8 type; + + if( cd == NULL || cd->usersd[0] == NULL ) + return; + + type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 + : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 + : 1; + + WBUFW(buf, 0) = 0xdf; + WBUFW(buf, 2) = 17 + strlen(cd->title); + WBUFL(buf, 4) = cd->owner->id; + WBUFL(buf, 8) = cd->bl.id; + WBUFW(buf,12) = cd->limit; + WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; + WBUFB(buf,16) = type; + memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated + + clif_send(buf,WBUFW(buf,2),cd->owner,CHAT); +} + + +/// Removes the chatroom (ZC_DESTROY_ROOM). +/// 00d8 <chat id>.L +void clif_clearchat(struct chat_data *cd,int fd) +{ + unsigned char buf[32]; + + nullpo_retv(cd); + + WBUFW(buf,0) = 0xd8; + WBUFL(buf,2) = cd->bl.id; + if( fd ) { + WFIFOHEAD(fd,packet_len(0xd8)); + memcpy(WFIFOP(fd,0),buf,packet_len(0xd8)); + WFIFOSET(fd,packet_len(0xd8)); + } else { + clif_send(buf,packet_len(0xd8),cd->owner,AREA_WOSC); + } +} + + +/// Displays messages regarding join chat failures (ZC_REFUSE_ENTER_ROOM). +/// 00da <result>.B +/// result: +/// 0 = room full +/// 1 = wrong password +/// 2 = kicked +/// 3 = success (no message) +/// 4 = no enough zeny +/// 5 = too low level +/// 6 = too high level +/// 7 = unsuitable job class +void clif_joinchatfail(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xda)); + WFIFOW(fd,0) = 0xda; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0xda)); +} + + +/// Notifies the client about entering a chatroom (ZC_ENTER_ROOM). +/// 00db <packet len>.W <chat id>.L { <role>.L <name>.24B }* +/// role: +/// 0 = owner (menu) +/// 1 = normal +void clif_joinchatok(struct map_session_data *sd,struct chat_data* cd) +{ + int fd; + int i,t; + + nullpo_retv(sd); + nullpo_retv(cd); + + fd = sd->fd; + if (!session_isActive(fd)) + return; + t = (int)(cd->owner->type == BL_NPC); + WFIFOHEAD(fd, 8 + (28*(cd->users+t))); + WFIFOW(fd, 0) = 0xdb; + WFIFOW(fd, 2) = 8 + (28*(cd->users+t)); + WFIFOL(fd, 4) = cd->bl.id; + + if(cd->owner->type == BL_NPC){ + WFIFOL(fd, 30) = 1; + WFIFOL(fd, 8) = 0; + memcpy(WFIFOP(fd, 12), ((struct npc_data *)cd->owner)->name, NAME_LENGTH); + for (i = 0; i < cd->users; i++) { + WFIFOL(fd, 8+(i+1)*28) = 1; + memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); + } + } else + for (i = 0; i < cd->users; i++) { + WFIFOL(fd, 8+i*28) = (i != 0 || cd->owner->type == BL_NPC); + memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); + } + WFIFOSET(fd, WFIFOW(fd, 2)); +} + + +/// Notifies clients in a chat about a new member (ZC_MEMBER_NEWENTRY). +/// 00dc <users>.W <name>.24B +void clif_addchat(struct chat_data* cd,struct map_session_data *sd) +{ + unsigned char buf[32]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xdc; + WBUFW(buf, 2) = cd->users; + memcpy(WBUFP(buf, 4),sd->status.name,NAME_LENGTH); + clif_send(buf,packet_len(0xdc),&sd->bl,CHAT_WOS); +} + + +/// Announce the new owner (ZC_ROLE_CHANGE). +/// 00e1 <role>.L <nick>.24B +/// role: +/// 0 = owner (menu) +/// 1 = normal +void clif_changechatowner(struct chat_data* cd, struct map_session_data* sd) +{ + unsigned char buf[64]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xe1; + WBUFL(buf, 2) = 1; + memcpy(WBUFP(buf,6),cd->usersd[0]->status.name,NAME_LENGTH); + + WBUFW(buf,30) = 0xe1; + WBUFL(buf,32) = 0; + memcpy(WBUFP(buf,36),sd->status.name,NAME_LENGTH); + + clif_send(buf,packet_len(0xe1)*2,&sd->bl,CHAT); +} + + +/// Notify about user leaving the chatroom (ZC_MEMBER_EXIT). +/// 00dd <users>.W <nick>.24B <flag>.B +/// flag: +/// 0 = left +/// 1 = kicked +void clif_leavechat(struct chat_data* cd, struct map_session_data* sd, bool flag) +{ + unsigned char buf[32]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xdd; + WBUFW(buf, 2) = cd->users-1; + memcpy(WBUFP(buf,4),sd->status.name,NAME_LENGTH); + WBUFB(buf,28) = flag; + + clif_send(buf,packet_len(0xdd),&sd->bl,CHAT); +} + + +/// Opens a trade request window from char 'name'. +/// 00e5 <nick>.24B (ZC_REQ_EXCHANGE_ITEM) +/// 01f4 <nick>.24B <charid>.L <baselvl>.W (ZC_REQ_EXCHANGE_ITEM2) +void clif_traderequest(struct map_session_data* sd, const char* name) +{ + int fd = sd->fd; + +#if PACKETVER < 6 + WFIFOHEAD(fd,packet_len(0xe5)); + WFIFOW(fd,0) = 0xe5; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0xe5)); +#else + struct map_session_data* tsd = map_id2sd(sd->trade_partner); + if( !tsd ) return; + + WFIFOHEAD(fd,packet_len(0x1f4)); + WFIFOW(fd,0) = 0x1f4; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOL(fd,26) = tsd->status.char_id; + WFIFOW(fd,30) = tsd->status.base_level; + WFIFOSET(fd,packet_len(0x1f4)); +#endif +} + + +/// Reply to a trade-request. +/// 00e7 <result>.B (ZC_ACK_EXCHANGE_ITEM) +/// 01f5 <result>.B <charid>.L <baselvl>.W (ZC_ACK_EXCHANGE_ITEM2) +/// result: +/// 0 = Char is too far +/// 1 = Character does not exist +/// 2 = Trade failed +/// 3 = Accept +/// 4 = Cancel +/// 5 = Busy +void clif_tradestart(struct map_session_data* sd, uint8 type) +{ + int fd = sd->fd; + struct map_session_data* tsd = map_id2sd(sd->trade_partner); + if( PACKETVER < 6 || !tsd ) { + WFIFOHEAD(fd,packet_len(0xe7)); + WFIFOW(fd,0) = 0xe7; + WFIFOB(fd,2) = type; + WFIFOSET(fd,packet_len(0xe7)); + } else { + WFIFOHEAD(fd,packet_len(0x1f5)); + WFIFOW(fd,0) = 0x1f5; + WFIFOB(fd,2) = type; + WFIFOL(fd,3) = tsd->status.char_id; + WFIFOW(fd,7) = tsd->status.base_level; + WFIFOSET(fd,packet_len(0x1f5)); + } +} + + +/// Notifies the client about an item from other player in current trade. +/// 00e9 <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM) +/// 080f <nameid>.W <item type>.B <amount>.L <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM2) +void clif_tradeadditem(struct map_session_data* sd, struct map_session_data* tsd, int index, int amount) +{ + int fd; + unsigned char *buf; +#if PACKETVER < 20100223 + const int cmd = 0xe9; +#else + const int cmd = 0x80f; +#endif + nullpo_retv(sd); + nullpo_retv(tsd); + + fd = tsd->fd; + buf = WFIFOP(fd,0); + WFIFOHEAD(fd,packet_len(cmd)); + WBUFW(buf,0) = cmd; + if( index == 0 ) + { +#if PACKETVER < 20100223 + WBUFL(buf,2) = amount; //amount + WBUFW(buf,6) = 0; // type id +#else + WBUFW(buf,2) = 0; // type id + WBUFB(buf,4) = 0; // item type + WBUFL(buf,5) = amount; // amount + buf = WBUFP(buf,1); //Advance 1B +#endif + WBUFB(buf,8) = 0; //identify flag + WBUFB(buf,9) = 0; // attribute + WBUFB(buf,10)= 0; //refine + WBUFW(buf,11)= 0; //card (4w) + WBUFW(buf,13)= 0; //card (4w) + WBUFW(buf,15)= 0; //card (4w) + WBUFW(buf,17)= 0; //card (4w) + } + else + { + index -= 2; //index fix +#if PACKETVER < 20100223 + WBUFL(buf,2) = amount; //amount + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,6) = sd->inventory_data[index]->view_id; + else + WBUFW(buf,6) = sd->status.inventory[index].nameid; // type id +#else + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,2) = sd->inventory_data[index]->view_id; + else + WBUFW(buf,2) = sd->status.inventory[index].nameid; // type id + WBUFB(buf,4) = sd->inventory_data[index]->type; // item type + WBUFL(buf,5) = amount; // amount + buf = WBUFP(buf,1); //Advance 1B +#endif + WBUFB(buf,8) = sd->status.inventory[index].identify; //identify flag + WBUFB(buf,9) = sd->status.inventory[index].attribute; // attribute + WBUFB(buf,10)= sd->status.inventory[index].refine; //refine + clif_addcards(WBUFP(buf, 11), &sd->status.inventory[index]); + } + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client about the result of request to add an item to the current trade (ZC_ACK_ADD_EXCHANGE_ITEM). +/// 00ea <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = overweight +/// 2 = trade canceled +void clif_tradeitemok(struct map_session_data* sd, int index, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xea)); + WFIFOW(fd,0) = 0xea; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = fail; + WFIFOSET(fd,packet_len(0xea)); +} + + +/// Notifies the client about finishing one side of the current trade (ZC_CONCLUDE_EXCHANGE_ITEM). +/// 00ec <who>.B +/// who: +/// 0 = self +/// 1 = other player +void clif_tradedeal_lock(struct map_session_data* sd, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xec)); + WFIFOW(fd,0) = 0xec; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0xec)); +} + + +/// Notifies the client about the trade being canceled (ZC_CANCEL_EXCHANGE_ITEM). +/// 00ee +void clif_tradecancelled(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xee)); + WFIFOW(fd,0) = 0xee; + WFIFOSET(fd,packet_len(0xee)); +} + + +/// Result of a trade (ZC_EXEC_EXCHANGE_ITEM). +/// 00f0 <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_tradecompleted(struct map_session_data* sd, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xf0)); + WFIFOW(fd,0) = 0xf0; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0xf0)); +} + + +/// Resets the trade window on the send side (ZC_EXCHANGEITEM_UNDO). +/// 00f1 +/// NOTE: Unknown purpose. Items are not removed until the window is +/// refreshed (ex. by putting another item in there). +void clif_tradeundo(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xf1)); + WFIFOW(fd,0) = 0xf1; + WFIFOSET(fd,packet_len(0xf1)); +} + + +/// Updates storage total amount (ZC_NOTIFY_STOREITEM_COUNTINFO). +/// 00f2 <current count>.W <max count>.W +void clif_updatestorageamount(struct map_session_data* sd, int amount, int max_amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf2)); + WFIFOW(fd,0) = 0xf2; + WFIFOW(fd,2) = amount; + WFIFOW(fd,4) = max_amount; + WFIFOSET(fd,packet_len(0xf2)); +} + + +/// Notifies the client of an item being added to the storage. +/// 00f4 <index>.W <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE) +/// 01c4 <index>.W <amount>.L <nameid>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE2) +void clif_storageitemadded(struct map_session_data* sd, struct item* i, int index, int amount) +{ + int view,fd; + + nullpo_retv(sd); + nullpo_retv(i); + fd=sd->fd; + view = itemdb_viewid(i->nameid); + +#if PACKETVER < 5 + WFIFOHEAD(fd,packet_len(0xf4)); + WFIFOW(fd, 0) = 0xf4; // Storage item added + WFIFOW(fd, 2) = index+1; // index + WFIFOL(fd, 4) = amount; // amount + WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id + WFIFOB(fd,10) = i->identify; //identify flag + WFIFOB(fd,11) = i->attribute; // attribute + WFIFOB(fd,12) = i->refine; //refine + clif_addcards(WFIFOP(fd,13), i); + WFIFOSET(fd,packet_len(0xf4)); +#else + WFIFOHEAD(fd,packet_len(0x1c4)); + WFIFOW(fd, 0) = 0x1c4; // Storage item added + WFIFOW(fd, 2) = index+1; // index + WFIFOL(fd, 4) = amount; // amount + WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id + WFIFOB(fd,10) = itemdb_type(i->nameid); //type + WFIFOB(fd,11) = i->identify; //identify flag + WFIFOB(fd,12) = i->attribute; // attribute + WFIFOB(fd,13) = i->refine; //refine + clif_addcards(WFIFOP(fd,14), i); + WFIFOSET(fd,packet_len(0x1c4)); +#endif +} + + +/// Notifies the client of an item being deleted from the storage (ZC_DELETE_ITEM_FROM_STORE). +/// 00f6 <index>.W <amount>.L +void clif_storageitemremoved(struct map_session_data* sd, int index, int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf6)); + WFIFOW(fd,0)=0xf6; // Storage item removed + WFIFOW(fd,2)=index+1; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len(0xf6)); +} + + +/// Closes storage (ZC_CLOSE_STORE). +/// 00f8 +void clif_storageclose(struct map_session_data* sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf8)); + WFIFOW(fd,0) = 0xf8; // Storage Closed + WFIFOSET(fd,packet_len(0xf8)); +} + +/*========================================== + * Server tells 'sd' player client the abouts of 'dstsd' player + *------------------------------------------*/ +static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_data* dstsd) +{ + struct block_list *d_bl; + int i; + + if( dstsd->chatID ) { + struct chat_data *cd = NULL; + if( (cd = (struct chat_data*)map_id2bl(dstsd->chatID)) && cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } else if( dstsd->state.vending ) + clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); + else if( dstsd->state.buyingstore ) + clif_buyingstore_entry_single(sd, dstsd); + + if(dstsd->spiritball > 0) + clif_spiritball_single(sd->fd, dstsd); + for(i = 1; i < 5; i++){ + if( dstsd->talisman[i] > 0 ) + clif_talisman_single(sd->fd, dstsd, i); + } + if( dstsd->sc.option&OPTION_MOUNTING ) { + //New Mounts are not complaint to the original method, so we gotta tell this guy that I'm mounting. + clif_status_load_single(sd->fd,dstsd->bl.id,SI_ALL_RIDING,2,1,0,0); + } +#ifdef NEW_CARTS + if( dstsd->sc.data[SC_PUSH_CART] ) + clif_status_load_single(sd->fd, dstsd->bl.id, SI_ON_PUSH_CART, 2, dstsd->sc.data[SC_PUSH_CART]->val1, 0, 0); +#endif + if( (sd->status.party_id && dstsd->status.party_id == sd->status.party_id) || //Party-mate, or hpdisp setting. + (sd->bg_id && sd->bg_id == dstsd->bg_id) || //BattleGround + pc_has_permission(sd, PC_PERM_VIEW_HPMETER) + ) + clif_hpmeter_single(sd->fd, dstsd->bl.id, dstsd->battle_status.hp, dstsd->battle_status.max_hp); + + // display link (sd - dstsd) to sd + ARR_FIND( 0, 5, i, sd->devotion[i] == dstsd->bl.id ); + if( i < 5 ) clif_devotion(&sd->bl, sd); + // display links (dstsd - devotees) to sd + ARR_FIND( 0, 5, i, dstsd->devotion[i] > 0 ); + if( i < 5 ) clif_devotion(&dstsd->bl, sd); + // display link (dstsd - crusader) to sd + if( dstsd->sc.data[SC_DEVOTION] && (d_bl = map_id2bl(dstsd->sc.data[SC_DEVOTION]->val1)) != NULL ) + clif_devotion(d_bl, sd); +} + +void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl) +{ + uint8 buf[128]; + struct unit_data *ud; + struct view_data *vd; + int len; + + vd = status_get_viewdata(bl); + if (!vd || vd->class_ == INVISIBLE_CLASS) + return; + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return; + + ud = unit_bl2ud(bl); + len = ( ud && ud->walktimer != INVALID_TIMER ) ? clif_set_unit_walking(bl,ud,buf) : clif_set_unit_idle(bl,buf,false); + clif_send(buf,len,&sd->bl,SELF); + + if (vd->cloth_color) + clif_refreshlook(&sd->bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,SELF); + + switch (bl->type) + { + case BL_PC: + { + TBL_PC* tsd = (TBL_PC*)bl; + clif_getareachar_pc(sd, tsd); + if(tsd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect_single(bl,423,sd->fd); + else if(tsd->state.size==SZ_MEDIUM) + clif_specialeffect_single(bl,421,sd->fd); + if( tsd->bg_id && map[tsd->bl.m].flag.battleground ) + clif_sendbgemblem_single(sd->fd,tsd); + if( tsd->sc.data[SC_CAMOUFLAGE] ) + clif_status_load(bl,SI_CAMOUFLAGE,1); + } + break; + case BL_MER: // Devotion Effects + if( ((TBL_MER*)bl)->devotion_flag ) + clif_devotion(bl, sd); + break; + case BL_NPC: + { + TBL_NPC* nd = (TBL_NPC*)bl; + if( nd->chat_id ) + clif_dispchat((struct chat_data*)map_id2bl(nd->chat_id),sd->fd); + if( nd->size == SZ_BIG ) + clif_specialeffect_single(bl,423,sd->fd); + else if( nd->size == SZ_MEDIUM ) + clif_specialeffect_single(bl,421,sd->fd); + } + break; + case BL_MOB: + { + TBL_MOB* md = (TBL_MOB*)bl; + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect_single(bl,423,sd->fd); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect_single(bl,421,sd->fd); +#if PACKETVER >= 20120404 + if( !(md->status.mode&MD_BOSS) ){ + int i; + for(i = 0; i < DAMAGELOG_SIZE; i++)// must show hp bar to all char who already hit the mob. + if( md->dmglog[i].id == sd->status.char_id ) + clif_monster_hp_bar(md, sd->fd); + } +#endif + } + break; + case BL_PET: + if (vd->head_bottom) + clif_pet_equip(sd, (TBL_PET*)bl); // needed to display pet equip properly + break; + } +} + +//Modifies the type of damage according to status changes [Skotlex] +//Aegis data specifies that: 4 endure against single hit sources, 9 against multi-hit. +static inline int clif_calc_delay(int type, int div, int damage, int delay) +{ + return ( delay == 0 && damage > 0 ) ? ( div > 1 ? 9 : 4 ) : type; +} + +/*========================================== + * Estimates walk delay based on the damage criteria. [Skotlex] + *------------------------------------------*/ +static int clif_calc_walkdelay(struct block_list *bl,int delay, int type, int damage, int div_) +{ + if (type == 4 || type == 9 || damage <=0) + return 0; + + if (bl->type == BL_PC) { + if (battle_config.pc_walk_delay_rate != 100) + delay = delay*battle_config.pc_walk_delay_rate/100; + } else + if (battle_config.walk_delay_rate != 100) + delay = delay*battle_config.walk_delay_rate/100; + + if (div_ > 1) //Multi-hit skills mean higher delays. + delay += battle_config.multihit_delay*(div_-1); + + return delay>0?delay:1; //Return 1 to specify there should be no noticeable delay, but you should stop walking. +} + + +/// Sends a 'damage' packet (src performs action on dst) +/// 008a <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.W <div>.W <type>.B <damage2>.W (ZC_NOTIFY_ACT) +/// 02e1 <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.L <div>.W <type>.B <damage2>.L (ZC_NOTIFY_ACT2) +/// type: +/// 0 = damage [ damage: total damage, div: amount of hits, damage2: assassin dual-wield damage ] +/// 1 = pick up item +/// 2 = sit down +/// 3 = stand up +/// 4 = damage (endure) +/// 5 = (splash?) +/// 6 = (skill?) +/// 7 = (repeat damage?) +/// 8 = multi-hit damage +/// 9 = multi-hit damage (endure) +/// 10 = critical hit +/// 11 = lucky dodge +/// 12 = (touch skill?) +int clif_damage(struct block_list* src, struct block_list* dst, unsigned int tick, int sdelay, int ddelay, int damage, int div, int type, int damage2) +{ + unsigned char buf[33]; + struct status_change *sc; +#if PACKETVER < 20071113 + const int cmd = 0x8a; +#else + const int cmd = 0x2e1; +#endif + + nullpo_ret(src); + nullpo_ret(dst); + + type = clif_calc_delay(type,div,damage+damage2,ddelay); + sc = status_get_sc(dst); + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION]) { + if(damage) damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + if(damage2) damage2 = damage2*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + } + } + + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=tick; + WBUFL(buf,14)=sdelay; + WBUFL(buf,18)=ddelay; +#if PACKETVER < 20071113 + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,22)=damage?div:0; + WBUFW(buf,27)=damage2?div:0; + } else { + WBUFW(buf,22)=min(damage, INT16_MAX); + WBUFW(buf,27)=damage2; + } + WBUFW(buf,24)=div; + WBUFB(buf,26)=type; +#else + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFL(buf,22)=damage?div:0; + WBUFL(buf,29)=damage2?div:0; + } else { + WBUFL(buf,22)=damage; + WBUFL(buf,29)=damage2; + } + WBUFW(buf,26)=div; + WBUFB(buf,28)=type; +#endif + if(disguised(dst)) { + clif_send(buf,packet_len(cmd),dst,AREA_WOS); + WBUFL(buf,6) = -dst->id; + clif_send(buf,packet_len(cmd),dst,SELF); + } else + clif_send(buf,packet_len(cmd),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,2) = -src->id; + if (disguised(dst)) + WBUFL(buf,6) = dst->id; +#if PACKETVER < 20071113 + if(damage > 0) WBUFW(buf,22) = -1; + if(damage2 > 0) WBUFW(buf,27) = -1; +#else + if(damage > 0) WBUFL(buf,22) = -1; + if(damage2 > 0) WBUFL(buf,29) = -1; +#endif + clif_send(buf,packet_len(cmd),src,SELF); + } + + if(src == dst) { + unit_setdir(src,unit_getdir(src)); + } + //Return adjusted can't walk delay for further processing. + return clif_calc_walkdelay(dst,ddelay,type,damage+damage2,div); +} + +/*========================================== + * src picks up dst + *------------------------------------------*/ +void clif_takeitem(struct block_list* src, struct block_list* dst) +{ + //clif_damage(src,dst,0,0,0,0,0,1,0); + unsigned char buf[32]; + + nullpo_retv(src); + nullpo_retv(dst); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = src->id; + WBUFL(buf, 6) = dst->id; + WBUFB(buf,26) = 1; + clif_send(buf, packet_len(0x8a), src, AREA); + +} + +/*========================================== + * inform clients in area that `bl` is sitting + *------------------------------------------*/ +void clif_sitting(struct block_list* bl) +{ + unsigned char buf[32]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = bl->id; + WBUFB(buf,26) = 2; + clif_send(buf, packet_len(0x8a), bl, AREA); + + if(disguised(bl)) { + WBUFL(buf, 2) = - bl->id; + clif_send(buf, packet_len(0x8a), bl, SELF); + } +} + +/*========================================== + * inform clients in area that `bl` is standing + *------------------------------------------*/ +void clif_standing(struct block_list* bl) +{ + unsigned char buf[32]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = bl->id; + WBUFB(buf,26) = 3; + clif_send(buf, packet_len(0x8a), bl, AREA); + + if(disguised(bl)) { + WBUFL(buf, 2) = - bl->id; + clif_send(buf, packet_len(0x8a), bl, SELF); + } +} + + +/// Inform client(s) about a map-cell change (ZC_UPDATE_MAPINFO). +/// 0192 <x>.W <y>.W <type>.W <map name>.16B +void clif_changemapcell(int fd, int16 m, int x, int y, int type, enum send_target target) +{ + unsigned char buf[32]; + + WBUFW(buf,0) = 0x192; + WBUFW(buf,2) = x; + WBUFW(buf,4) = y; + WBUFW(buf,6) = type; + mapindex_getmapname_ext(map[m].name,(char*)WBUFP(buf,8)); + + if( fd ) + { + WFIFOHEAD(fd,packet_len(0x192)); + memcpy(WFIFOP(fd,0), buf, packet_len(0x192)); + WFIFOSET(fd,packet_len(0x192)); + } + else + { + struct block_list dummy_bl; + dummy_bl.type = BL_NUL; + dummy_bl.x = x; + dummy_bl.y = y; + dummy_bl.m = m; + clif_send(buf,packet_len(0x192),&dummy_bl,target); + } +} + + +/// Notifies the client about an item on floor (ZC_ITEM_ENTRY). +/// 009d <id>.L <name id>.W <identified>.B <x>.W <y>.W <amount>.W <subX>.B <subY>.B +void clif_getareachar_item(struct map_session_data* sd,struct flooritem_data* fitem) +{ + int view,fd; + fd=sd->fd; + + WFIFOHEAD(fd,packet_len(0x9d)); + WFIFOW(fd,0)=0x9d; + WFIFOL(fd,2)=fitem->bl.id; + if((view = itemdb_viewid(fitem->item_data.nameid)) > 0) + WFIFOW(fd,6)=view; + else + WFIFOW(fd,6)=fitem->item_data.nameid; + WFIFOB(fd,8)=fitem->item_data.identify; + WFIFOW(fd,9)=fitem->bl.x; + WFIFOW(fd,11)=fitem->bl.y; + WFIFOW(fd,13)=fitem->item_data.amount; + WFIFOB(fd,15)=fitem->subx; + WFIFOB(fd,16)=fitem->suby; + WFIFOSET(fd,packet_len(0x9d)); +} + + +/// Notifies the client of a skill unit. +/// 011f <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B (ZC_SKILL_ENTRY) +/// 01c9 <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B <has msg>.B <msg>.80B (ZC_SKILL_ENTRY2) +static void clif_getareachar_skillunit(struct map_session_data *sd, struct skill_unit *unit) +{ + int fd = sd->fd; + + if( unit->group->state.guildaura ) + return; + +#if PACKETVER >= 3 + if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] + WFIFOHEAD(fd,packet_len(0x1c9)); + WFIFOW(fd, 0)=0x1c9; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=1; + WFIFOB(fd,16)=1; + safestrncpy((char*)WFIFOP(fd,17),unit->group->valstr,MESSAGE_SIZE); + WFIFOSET(fd,packet_len(0x1c9)); + return; + } +#endif + WFIFOHEAD(fd,packet_len(0x11f)); + WFIFOW(fd, 0)=0x11f; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + if (battle_config.traps_setting&1 && skill_get_inf2(unit->group->skill_id)&INF2_TRAP) + WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. + else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) + WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. + else + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=1; // ignored by client (always gets set to 1) + WFIFOSET(fd,packet_len(0x11f)); + + if(unit->group->skill_id == WZ_ICEWALL) + clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,5,SELF); +} + + +/*========================================== + * Server tells client to remove unit of id 'unit->bl.id' + *------------------------------------------*/ +static void clif_clearchar_skillunit(struct skill_unit *unit, int fd) +{ + nullpo_retv(unit); + + WFIFOHEAD(fd,packet_len(0x120)); + WFIFOW(fd, 0)=0x120; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOSET(fd,packet_len(0x120)); + + if(unit->group && unit->group->skill_id == WZ_ICEWALL) + clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,SELF); +} + + +/// Removes a skill unit (ZC_SKILL_DISAPPEAR). +/// 0120 <id>.L +void clif_skill_delunit(struct skill_unit *unit) +{ + unsigned char buf[16]; + + nullpo_retv(unit); + + WBUFW(buf, 0)=0x120; + WBUFL(buf, 2)=unit->bl.id; + clif_send(buf,packet_len(0x120),&unit->bl,AREA); +} + + +/// Sent when an object gets ankle-snared (ZC_SKILL_UPDATE). +/// 01ac <id>.L +/// Only affects units with class [139,153] client-side. +void clif_skillunit_update(struct block_list* bl) +{ + unsigned char buf[6]; + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1ac; + WBUFL(buf,2) = bl->id; + + clif_send(buf,packet_len(0x1ac),bl,AREA); +} + + +/*========================================== + * + *------------------------------------------*/ +static int clif_getareachar(struct block_list* bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_ret(bl); + + sd=va_arg(ap,struct map_session_data*); + + if (sd == NULL || !sd->fd) + return 0; + + switch(bl->type){ + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*) bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(TBL_SKILL*)bl); + break; + default: + if(&sd->bl == bl) + break; + clif_getareachar_unit(sd,bl); + break; + } + return 0; +} + +/*========================================== + * tbl has gone out of view-size of bl + *------------------------------------------*/ +int clif_outsight(struct block_list *bl,va_list ap) +{ + struct block_list *tbl; + struct view_data *vd; + TBL_PC *sd, *tsd; + tbl=va_arg(ap,struct block_list*); + if(bl == tbl) return 0; + sd = BL_CAST(BL_PC, bl); + tsd = BL_CAST(BL_PC, tbl); + + if (tsd && tsd->fd) + { //tsd has lost sight of the bl object. + switch(bl->type){ + case BL_PC: + if (sd->vd.class_ != INVISIBLE_CLASS) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + if(sd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd->usersd[0]==sd) + clif_dispchat(cd,tsd->fd); + } + if( sd->state.vending ) + clif_closevendingboard(bl,tsd->fd); + if( sd->state.buyingstore ) + clif_buyingstore_disappear_entry_single(tsd, sd); + break; + case BL_ITEM: + clif_clearflooritem((struct flooritem_data*)bl,tsd->fd); + break; + case BL_SKILL: + clif_clearchar_skillunit((struct skill_unit *)bl,tsd->fd); + break; + case BL_NPC: + if( !(((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE) ) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + break; + default: + if ((vd=status_get_viewdata(bl)) && vd->class_ != INVISIBLE_CLASS) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + break; + } + } + if (sd && sd->fd) + { //sd is watching tbl go out of view. + if (((vd=status_get_viewdata(tbl)) && vd->class_ != INVISIBLE_CLASS) && + !(tbl->type == BL_NPC && (((TBL_NPC*)tbl)->sc.option&OPTION_INVISIBLE))) + clif_clearunit_single(tbl->id,CLR_OUTSIGHT,sd->fd); + } + return 0; +} + +/*========================================== + * tbl has come into view of bl + *------------------------------------------*/ +int clif_insight(struct block_list *bl,va_list ap) +{ + struct block_list *tbl; + TBL_PC *sd, *tsd; + tbl=va_arg(ap,struct block_list*); + + if (bl == tbl) return 0; + + sd = BL_CAST(BL_PC, bl); + tsd = BL_CAST(BL_PC, tbl); + + if (tsd && tsd->fd) + { //Tell tsd that bl entered into his view + switch(bl->type){ + case BL_ITEM: + clif_getareachar_item(tsd,(struct flooritem_data*)bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(tsd,(TBL_SKILL*)bl); + break; + default: + clif_getareachar_unit(tsd,bl); + break; + } + } + if (sd && sd->fd) + { //Tell sd that tbl walked into his view + clif_getareachar_unit(sd,tbl); + } + return 0; +} + + +/// Updates whole skill tree (ZC_SKILLINFO_LIST). +/// 010f <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* +void clif_skillinfoblock(struct map_session_data *sd) +{ + int fd; + int i,len,id; + + nullpo_retv(sd); + + fd=sd->fd; + if (!fd) return; + + WFIFOHEAD(fd, MAX_SKILL * 37 + 4); + WFIFOW(fd,0) = 0x10f; + for ( i = 0, len = 4; i < MAX_SKILL; i++) + { + if( (id = sd->status.skill[i].id) != 0 ) + { + // workaround for bugreport:5348 + if (len + 37 > 8192) + break; + + WFIFOW(fd,len) = id; + WFIFOL(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+6) = sd->status.skill[i].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,sd->status.skill[i].lv); + WFIFOW(fd,len+10)= skill_get_range2(&sd->bl, id,sd->status.skill[i].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + if(sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) + WFIFOB(fd,len+36) = (sd->status.skill[i].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; + else + WFIFOB(fd,len+36) = 0; + len += 37; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + // workaround for bugreport:5348; send the remaining skills one by one to bypass packet size limit + for ( ; i < MAX_SKILL; i++) + { + if( (id = sd->status.skill[i].id) != 0 ) + { + clif_addskill(sd, id); + clif_skillinfo(sd, id, 0); + } + } +} +/** + * Server tells client 'sd' to add skill of id 'id' to it's skill tree (e.g. with Ice Falcion item) + **/ + +/// Adds new skill to the skill tree (ZC_ADD_SKILL). +/// 0111 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B +void clif_addskill(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + if (!fd) return; + + if( sd->status.skill[id].id <= 0 ) + return; + + WFIFOHEAD(fd, packet_len(0x111)); + WFIFOW(fd,0) = 0x111; + WFIFOW(fd,2) = id; + WFIFOL(fd,4) = skill_get_inf(id); + WFIFOW(fd,8) = sd->status.skill[id].lv; + WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[id].lv); + WFIFOW(fd,12)= skill_get_range2(&sd->bl, id,sd->status.skill[id].lv); + safestrncpy((char*)WFIFOP(fd,14), skill_get_name(id), NAME_LENGTH); + if( sd->status.skill[id].flag == SKILL_FLAG_PERMANENT ) + WFIFOB(fd,38) = (sd->status.skill[id].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; + else + WFIFOB(fd,38) = 0; + WFIFOSET(fd,packet_len(0x111)); +} + + +/// Deletes a skill from the skill tree (ZC_SKILLINFO_DELETE). +/// 0441 <skill id>.W +void clif_deleteskill(struct map_session_data *sd, int id) +{ +#if PACKETVER >= 20081217 + int fd; + + nullpo_retv(sd); + fd = sd->fd; + if( !fd ) return; + + WFIFOHEAD(fd,packet_len(0x441)); + WFIFOW(fd,0) = 0x441; + WFIFOW(fd,2) = id; + WFIFOSET(fd,packet_len(0x441)); +#endif + clif_skillinfoblock(sd); +} + + +/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE). +/// 010e <skill id>.W <level>.W <sp cost>.W <attack range>.W <upgradable>.B +void clif_skillup(struct map_session_data *sd,uint16 skill_id) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10e)); + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = sd->status.skill[skill_id].lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,sd->status.skill[skill_id].lv); + WFIFOW(fd,8) = skill_get_range2(&sd->bl,skill_id,sd->status.skill[skill_id].lv); + WFIFOB(fd,10) = (sd->status.skill[skill_id].lv < skill_tree_get_max(sd->status.skill[skill_id].id, sd->status.class_)) ? 1 : 0; + WFIFOSET(fd,packet_len(0x10e)); +} + + +/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE2). +/// 07e1 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <upgradable>.B +void clif_skillinfo(struct map_session_data *sd,int skill, int inf) +{ + const int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x7e1)); + WFIFOW(fd,0) = 0x7e1; + WFIFOW(fd,2) = skill; + WFIFOL(fd,4) = inf?inf:skill_get_inf(skill); + WFIFOW(fd,8) = sd->status.skill[skill].lv; + WFIFOW(fd,10) = skill_get_sp(skill,sd->status.skill[skill].lv); + WFIFOW(fd,12) = skill_get_range2(&sd->bl,skill,sd->status.skill[skill].lv); + if( sd->status.skill[skill].flag == SKILL_FLAG_PERMANENT ) + WFIFOB(fd,14) = (sd->status.skill[skill].lv < skill_tree_get_max(skill, sd->status.class_))? 1:0; + else + WFIFOB(fd,14) = 0; + WFIFOSET(fd,packet_len(0x7e1)); +} + + +/// Notifies clients in area, that an object is about to use a skill. +/// 013e <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L (ZC_USESKILL_ACK) +/// 07fb <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L <is disposable>.B (ZC_USESKILL_ACK2) +/// property: +/// 0 = Yellow cast aura +/// 1 = Water elemental cast aura +/// 2 = Earth elemental cast aura +/// 3 = Fire elemental cast aura +/// 4 = Wind elemental cast aura +/// 5 = Poison elemental cast aura +/// 6 = Holy elemental cast aura +/// ? = like 0 +/// is disposable: +/// 0 = yellow chat text "[src name] will use skill [skill name]." +/// 1 = no text +void clif_skillcasting(struct block_list* bl, int src_id, int dst_id, int dst_x, int dst_y, uint16 skill_id, int property, int casttime) +{ +#if PACKETVER < 20091124 + const int cmd = 0x13e; +#else + const int cmd = 0x7fb; +#endif + unsigned char buf[32]; + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = src_id; + WBUFL(buf,6) = dst_id; + WBUFW(buf,10) = dst_x; + WBUFW(buf,12) = dst_y; + WBUFW(buf,14) = skill_id; + WBUFL(buf,16) = property<0?0:property; //Avoid sending negatives as element [Skotlex] + WBUFL(buf,20) = casttime; +#if PACKETVER >= 20091124 + WBUFB(buf,24) = 0; // isDisposable +#endif + + if (disguised(bl)) { + clif_send(buf,packet_len(cmd), bl, AREA_WOS); + WBUFL(buf,2) = -src_id; + clif_send(buf,packet_len(cmd), bl, SELF); + } else + clif_send(buf,packet_len(cmd), bl, AREA); +} + + +/// Notifies clients in area, that an object canceled casting (ZC_DISPEL). +/// 01b9 <id>.L +void clif_skillcastcancel(struct block_list* bl) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1b9; + WBUFL(buf,2) = bl->id; + clif_send(buf,packet_len(0x1b9), bl, AREA); +} + + +/// Notifies the client about the result of a skill use request (ZC_ACK_TOUSESKILL). +/// 0110 <skill id>.W <num>.L <result>.B <cause>.B +/// num (only used when skill id = NV_BASIC and cause = 0): +/// 0 = "skill failed" MsgStringTable[159] +/// 1 = "no emotions" MsgStringTable[160] +/// 2 = "no sit" MsgStringTable[161] +/// 3 = "no chat" MsgStringTable[162] +/// 4 = "no party" MsgStringTable[163] +/// 5 = "no shout" MsgStringTable[164] +/// 6 = "no PKing" MsgStringTable[165] +/// 7 = "no alligning" MsgStringTable[383] +/// ? = ignored +/// cause: +/// 0 = "not enough skill level" MsgStringTable[214] (AL_WARP) +/// "steal failed" MsgStringTable[205] (TF_STEAL) +/// "envenom failed" MsgStringTable[207] (TF_POISON) +/// "skill failed" MsgStringTable[204] (otherwise) +/// ... = @see enum useskill_fail_cause +/// ? = ignored +/// +/// if(result!=0) doesn't display any of the previous messages +/// Note: when this packet is received an unknown flag is always set to 0, +/// suggesting this is an ACK packet for the UseSkill packets and should be sent on success too [FlavioJS] +void clif_skill_fail(struct map_session_data *sd,uint16 skill_id,enum useskill_fail_cause cause,int btype) +{ + int fd; + + if (!sd) { //Since this is the most common nullpo.... + ShowDebug("clif_skill_fail: Error, received NULL sd for skill %d\n", skill_id); + return; + } + + fd=sd->fd; + if (!fd) return; + + if(battle_config.display_skill_fail&1) + return; //Disable all skill failed messages + + if(cause==USESKILL_FAIL_SKILLINTERVAL && !sd->state.showdelay) + return; //Disable delay failed messages + + if(skill_id == RG_SNATCHER && battle_config.display_skill_fail&4) + return; + + if(skill_id == TF_POISON && battle_config.display_skill_fail&8) + return; + + WFIFOHEAD(fd,packet_len(0x110)); + WFIFOW(fd,0) = 0x110; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = btype; + WFIFOB(fd,8) = 0;// success + WFIFOB(fd,9) = cause; + WFIFOSET(fd,packet_len(0x110)); +} + + +/// Skill cooldown display icon (ZC_SKILL_POSTDELAY). +/// 043d <skill ID>.W <tick>.L +void clif_skill_cooldown(struct map_session_data *sd, uint16 skill_id, unsigned int tick) +{ +#if PACKETVER>=20081112 + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x43d)); + WFIFOW(fd,0) = 0x43d; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = tick; + WFIFOSET(fd,packet_len(0x43d)); +#endif +} + + +/// Skill attack effect and damage. +/// 0114 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.W <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL) +/// 01de <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.L <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL2) +int clif_skill_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc; + + nullpo_ret(src); + nullpo_ret(dst); + + type = clif_calc_delay(type,div,damage,ddelay); + sc = status_get_sc(dst); + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION] && damage) + damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + } + +#if PACKETVER < 3 + WBUFW(buf,0)=0x114; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,24)=damage?div:0; + } else { + WBUFW(buf,24)=damage; + } + WBUFW(buf,26)=skill_lv; + WBUFW(buf,28)=div; + WBUFB(buf,30)=type; + if (disguised(dst)) { + clif_send(buf,packet_len(0x114),dst,AREA_WOS); + WBUFL(buf,8)=-dst->id; + clif_send(buf,packet_len(0x114),dst,SELF); + } else + clif_send(buf,packet_len(0x114),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if (disguised(dst)) + WBUFL(buf,8)=dst->id; + if(damage > 0) + WBUFW(buf,24)=-1; + clif_send(buf,packet_len(0x114),src,SELF); + } +#else + WBUFW(buf,0)=0x1de; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFL(buf,24)=damage?div:0; + } else { + WBUFL(buf,24)=damage; + } + WBUFW(buf,28)=skill_lv; + WBUFW(buf,30)=div; + WBUFB(buf,32)=type; + if (disguised(dst)) { + clif_send(buf,packet_len(0x1de),dst,AREA_WOS); + WBUFL(buf,8)=-dst->id; + clif_send(buf,packet_len(0x1de),dst,SELF); + } else + clif_send(buf,packet_len(0x1de),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if (disguised(dst)) + WBUFL(buf,8)=dst->id; + if(damage > 0) + WBUFL(buf,24)=-1; + clif_send(buf,packet_len(0x1de),src,SELF); + } +#endif + + //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] + return clif_calc_walkdelay(dst,ddelay,type,damage,div); +} + + +/// Ground skill attack effect and damage (ZC_NOTIFY_SKILL_POSITION). +/// 0115 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <x>.W <y>.W <damage>.W <level>.W <div>.W <type>.B +/* +int clif_skill_damage2(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc; + + nullpo_ret(src); + nullpo_ret(dst); + + type = (type>0)?type:skill_get_hit(skill_id); + type = clif_calc_delay(type,div,damage,ddelay); + sc = status_get_sc(dst); + + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION] && damage) + damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + } + + WBUFW(buf,0)=0x115; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFW(buf,24)=dst->x; + WBUFW(buf,26)=dst->y; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,28)=damage?div:0; + } else { + WBUFW(buf,28)=damage; + } + WBUFW(buf,30)=skill_lv; + WBUFW(buf,32)=div; + WBUFB(buf,34)=type; + clif_send(buf,packet_len(0x115),src,AREA); + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if(damage > 0) + WBUFW(buf,28)=-1; + clif_send(buf,packet_len(0x115),src,SELF); + } + if (disguised(dst)) { + WBUFL(buf,8)=-dst->id; + if (disguised(src)) + WBUFL(buf,4)=src->id; + else if(damage > 0) + WBUFW(buf,28)=-1; + clif_send(buf,packet_len(0x115),dst,SELF); + } + + //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] + return clif_calc_walkdelay(dst,ddelay,type,damage,div); +} +*/ + + +/// Non-damaging skill effect (ZC_USE_SKILL). +/// 011a <skill id>.W <skill lv>.W <dst id>.L <src id>.L <result>.B +int clif_skill_nodamage(struct block_list *src,struct block_list *dst,uint16 skill_id,int heal,int fail) +{ + unsigned char buf[32]; + + nullpo_ret(dst); + + WBUFW(buf,0)=0x11a; + WBUFW(buf,2)=skill_id; + WBUFW(buf,4)=min(heal, INT16_MAX); + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=src?src->id:0; + WBUFB(buf,14)=fail; + + if (disguised(dst)) { + clif_send(buf,packet_len(0x11a),dst,AREA_WOS); + WBUFL(buf,6)=-dst->id; + clif_send(buf,packet_len(0x11a),dst,SELF); + } else + clif_send(buf,packet_len(0x11a),dst,AREA); + + if(src && disguised(src)) { + WBUFL(buf,10)=-src->id; + if (disguised(dst)) + WBUFL(buf,6)=dst->id; + clif_send(buf,packet_len(0x11a),src,SELF); + } + + return fail; +} + + +/// Non-damaging ground skill effect (ZC_NOTIFY_GROUNDSKILL). +/// 0117 <skill id>.W <src id>.L <level>.W <x>.W <y>.W <tick>.L +void clif_skill_poseffect(struct block_list *src,uint16 skill_id,int val,int x,int y,int tick) +{ + unsigned char buf[32]; + + nullpo_retv(src); + + WBUFW(buf,0)=0x117; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFW(buf,8)=val; + WBUFW(buf,10)=x; + WBUFW(buf,12)=y; + WBUFL(buf,14)=tick; + if(disguised(src)) { + clif_send(buf,packet_len(0x117),src,AREA_WOS); + WBUFL(buf,4)=-src->id; + clif_send(buf,packet_len(0x117),src,SELF); + } else + clif_send(buf,packet_len(0x117),src,AREA); +} + + +/*========================================== + * Tells all client's nearby 'unit' sight range that it spawned + *------------------------------------------*/ +//FIXME: this is just an AREA version of clif_getareachar_skillunit() +void clif_skill_setunit(struct skill_unit *unit) +{ + unsigned char buf[128]; + + nullpo_retv(unit); + + if( unit->group->state.guildaura ) + return; + +#if PACKETVER >= 3 + if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] + WBUFW(buf, 0)=0x1c9; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=1; + WBUFB(buf,16)=1; + safestrncpy((char*)WBUFP(buf,17),unit->group->valstr,MESSAGE_SIZE); + clif_send(buf,packet_len(0x1c9),&unit->bl,AREA); + return; + } +#endif + WBUFW(buf, 0)=0x11f; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + if (unit->group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) + WBUFB(buf,14)=unit->val2&UF_SONG?UNT_DISSONANCE:UNT_UGLYDANCE; + else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) + WBUFB(buf, 14) = UNT_DUMMYSKILL; // Only display the unit at center. + else + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=1; // ignored by client (always gets set to 1) + clif_send(buf,packet_len(0x11f),&unit->bl,AREA); +} + + +/// Presents a list of available warp destinations (ZC_WARPLIST). +/// 011c <skill id>.W { <map name>.16B }*4 +void clif_skill_warppoint(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv, unsigned short map1, unsigned short map2, unsigned short map3, unsigned short map4) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x11c)); + WFIFOW(fd,0) = 0x11c; + WFIFOW(fd,2) = skill_id; + memset(WFIFOP(fd,4), 0x00, 4*MAP_NAME_LENGTH_EXT); + if (map1 == (unsigned short)-1) strcpy((char*)WFIFOP(fd,4), "Random"); + else // normal map name + if (map1 > 0) mapindex_getmapname_ext(mapindex_id2name(map1), (char*)WFIFOP(fd,4)); + if (map2 > 0) mapindex_getmapname_ext(mapindex_id2name(map2), (char*)WFIFOP(fd,20)); + if (map3 > 0) mapindex_getmapname_ext(mapindex_id2name(map3), (char*)WFIFOP(fd,36)); + if (map4 > 0) mapindex_getmapname_ext(mapindex_id2name(map4), (char*)WFIFOP(fd,52)); + WFIFOSET(fd,packet_len(0x11c)); + + sd->menuskill_id = skill_id; + if (skill_id == AL_WARP) + sd->menuskill_val = (sd->ud.skillx<<16)|sd->ud.skilly; //Store warp position here. + else + sd->menuskill_val = skill_lv; +} + + +/// Memo message (ZC_ACK_REMEMBER_WARPPOINT). +/// 011e <type>.B +/// type: +/// 0 = "Saved location as a Memo Point for Warp skill." in color 0xFFFF00 (cyan) +/// 1 = "Skill Level is not high enough." in color 0x0000FF (red) +/// 2 = "You haven't learned Warp." in color 0x0000FF (red) +/// +/// @param sd Who receives the message +/// @param type What message +void clif_skill_memomessage(struct map_session_data* sd, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x11e)); + WFIFOW(fd,0)=0x11e; + WFIFOB(fd,2)=type; + WFIFOSET(fd,packet_len(0x11e)); +} + + +/// Teleport message (ZC_NOTIFY_MAPINFO). +/// 0189 <type>.W +/// type: +/// 0 = "Unable to Teleport in this area" in color 0xFFFF00 (cyan) +/// 1 = "Saved point cannot be memorized." in color 0x0000FF (red) +/// +/// @param sd Who receives the message +/// @param type What message +void clif_skill_teleportmessage(struct map_session_data *sd, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x189)); + WFIFOW(fd,0)=0x189; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x189)); +} + + +/// Displays Sense (WZ_ESTIMATION) information window (ZC_MONSTER_INFO). +/// 018c <class>.W <level>.W <size>.W <hp>.L <def>.W <race>.W <mdef>.W <element>.W +/// <water%>.B <earth%>.B <fire%>.B <wind%>.B <poison%>.B <holy%>.B <shadow%>.B <ghost%>.B <undead%>.B +void clif_skill_estimation(struct map_session_data *sd,struct block_list *dst) +{ + struct status_data *status; + unsigned char buf[64]; + int i;//, fix; + + nullpo_retv(sd); + nullpo_retv(dst); + + if( dst->type != BL_MOB ) + return; + + status = status_get_status_data(dst); + + WBUFW(buf, 0)=0x18c; + WBUFW(buf, 2)=status_get_class(dst); + WBUFW(buf, 4)=status_get_lv(dst); + WBUFW(buf, 6)=status->size; + WBUFL(buf, 8)=status->hp; + WBUFW(buf,12)= (battle_config.estimation_type&1?status->def:0) + +(battle_config.estimation_type&2?status->def2:0); + WBUFW(buf,14)=status->race; + WBUFW(buf,16)= (battle_config.estimation_type&1?status->mdef:0) + +(battle_config.estimation_type&2?status->mdef2:0); + WBUFW(buf,18)= status->def_ele; + for(i=0;i<9;i++) + WBUFB(buf,20+i)= (unsigned char)battle_attr_ratio(i+1,status->def_ele, status->ele_lv); +// The following caps negative attributes to 0 since the client displays them as 255-fix. [Skotlex] +// WBUFB(buf,20+i)= (unsigned char)((fix=battle_attr_ratio(i+1,status->def_ele, status->ele_lv))<0?0:fix); + + clif_send(buf,packet_len(0x18c),&sd->bl,sd->status.party_id>0?PARTY_SAMEMAP:SELF); +} + + +/// Presents a textual list of producable items (ZC_MAKABLEITEMLIST). +/// 018d <packet len>.W { <name id>.W { <material id>.W }*3 }* +/// material id: +/// unused by the client +void clif_skill_produce_mix_list(struct map_session_data *sd, int skill_id , int trigger) +{ + int i,c,view,fd; + nullpo_retv(sd); + + if(sd->menuskill_id == skill_id) + return; //Avoid resending the menu twice or more times... + if( skill_id == GC_CREATENEWPOISON ) + skill_id = GC_RESEARCHNEWPOISON; + + fd=sd->fd; + WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB * 8 + 8); + WFIFOW(fd, 0)=0x18d; + + for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if( skill_can_produce_mix(sd,skill_produce_db[i].nameid, trigger, 1) && + ( skill_id > 0 && skill_produce_db[i].req_skill == skill_id || skill_id < 0 ) + ){ + if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) + WFIFOW(fd,c*8+ 4)= view; + else + WFIFOW(fd,c*8+ 4)= skill_produce_db[i].nameid; + WFIFOW(fd,c*8+ 6)= 0; + WFIFOW(fd,c*8+ 8)= 0; + WFIFOW(fd,c*8+10)= 0; + c++; + } + } + WFIFOW(fd, 2)=c*8+8; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) { + sd->menuskill_id = skill_id; + sd->menuskill_val = trigger; + return; + } +} + + +/// Present a list of producable items (ZC_MAKINGITEM_LIST). +/// 025a <packet len>.W <mk type>.W { <name id>.W }* +/// mk type: +/// 1 = cooking +/// 2 = arrow +/// 3 = elemental +/// 4 = GN_MIX_COOKING +/// 5 = GN_MAKEBOMB +/// 6 = GN_S_PHARMACY +void clif_cooking_list(struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type) +{ + int fd; + int i, c; + int view; + + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, 6 + 2 * MAX_SKILL_PRODUCE_DB); + WFIFOW(fd,0) = 0x25a; + WFIFOW(fd,4) = list_type; // list type + + c = 0; + for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { + if( !skill_can_produce_mix(sd,skill_produce_db[i].nameid,trigger, qty) ) + continue; + + if( (view = itemdb_viewid(skill_produce_db[i].nameid)) > 0 ) + WFIFOW(fd, 6 + 2 * c) = view; + else + WFIFOW(fd, 6 + 2 * c) = skill_produce_db[i].nameid; + + c++; + } + + if( skill_id == AM_PHARMACY ) { // Only send it while Cooking else check for c. + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + if( c > 0 ) { + sd->menuskill_id = skill_id; + sd->menuskill_val = trigger; + if( skill_id != AM_PHARMACY ) { + sd->menuskill_val2 = qty; // amount. + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } else { + clif_menuskill_clear(sd); + if( skill_id != AM_PHARMACY ) { // AM_PHARMACY is used to Cooking. + // It fails. +#if PACKETVER >= 20090922 + clif_msg_skill(sd,skill_id,0x625); +#else + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); +#endif + } + } +} + + +/// Notifies clients of a status change. +/// 0196 <index>.W <id>.L <state>.B (ZC_MSG_STATE_CHANGE) [used for ending status changes and starting them on non-pc units (when needed)] +/// 043f <index>.W <id>.L <state>.B <remain msec>.L { <val>.L }*3 (ZC_MSG_STATE_CHANGE2) [used exclusively for starting statuses on pcs] +void clif_status_change(struct block_list *bl,int type,int flag,int tick,int val1, int val2, int val3) +{ + unsigned char buf[32]; + struct map_session_data *sd; + + if (type == SI_BLANK) //It shows nothing on the client... + return; + + nullpo_retv(bl); + + sd = BL_CAST(BL_PC, bl); + + if (!(status_type2relevant_bl_types(type)&bl->type)) // only send status changes that actually matter to the client + return; + +#if PACKETVER >= 20090121 + if(flag && battle_config.display_status_timers && sd) + WBUFW(buf,0)=0x43f; + else +#endif + WBUFW(buf,0)=0x196; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; +#if PACKETVER >= 20090121 + if(flag && battle_config.display_status_timers && sd) + { + if (tick <= 0) + tick = 9999; // this is indeed what official servers do + + WBUFL(buf,9) = tick; + WBUFL(buf,13) = val1; + WBUFL(buf,17) = val2; + WBUFL(buf,21) = val3; + } +#endif + clif_send(buf,packet_len(WBUFW(buf,0)),bl, (sd && sd->status.option&OPTION_INVISIBLE) ? SELF : AREA); +} + +/// Send message (modified by [Yor]) (ZC_NOTIFY_PLAYERCHAT). +/// 008e <packet len>.W <message>.?B +void clif_displaymessage(const int fd, const char* mes) +{ + nullpo_retv(mes); + + //Scrapped, as these are shared by disconnected players =X [Skotlex] + if (fd == 0) + ; + else { + char *message, *line; + + message = aStrdup(mes); + line = strtok(message, "\n"); + while(line != NULL) { + // Limit message to 255+1 characters (otherwise it causes a buffer overflow in the client) + int len = strnlen(line, 255); + + if (len > 0) { // don't send a void message (it's not displaying on the client chat). @help can send void line. + WFIFOHEAD(fd, 5 + len); + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = 5 + len; // 4 + len + NULL teminate + safestrncpy((char *)WFIFOP(fd,4), line, len + 1); + WFIFOSET(fd, 5 + len); + } + line = strtok(NULL, "\n"); + } + aFree(message); + } +} + +/// Send broadcast message in yellow or blue without font formatting (ZC_BROADCAST). +/// 009a <packet len>.W <message>.?B +void clif_broadcast(struct block_list* bl, const char* mes, int len, int type, enum send_target target) +{ + int lp = type ? 4 : 0; + unsigned char *buf = (unsigned char*)aMalloc((4 + lp + len)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = 4 + lp + len; + if (type == 0x10) // bc_blue + WBUFL(buf,4) = 0x65756c62; //If there's "blue" at the beginning of the message, game client will display it in blue instead of yellow. + else if (type == 0x20) // bc_woe + WBUFL(buf,4) = 0x73737373; //If there's "ssss", game client will recognize message as 'WoE broadcast'. + memcpy(WBUFP(buf, 4 + lp), mes, len); + clif_send(buf, WBUFW(buf,2), bl, target); + + if (buf) + aFree(buf); +} + +/*========================================== + * Displays a message on a 'bl' to all it's nearby clients + * Used by npc_globalmessage + *------------------------------------------*/ +void clif_GlobalMessage(struct block_list* bl, const char* message) { + char buf[100]; + int len; + nullpo_retv(bl); + + if(!message) + return; + + len = strlen(message)+1; + + if( len > sizeof(buf)-8 ) { + ShowWarning("clif_GlobalMessage: Truncating too long message '%s' (len=%d).\n", message, len); + len = sizeof(buf)-8; + } + + WBUFW(buf,0)=0x8d; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=bl->id; + safestrncpy((char *) WBUFP(buf,8),message,len); + clif_send((unsigned char *) buf,WBUFW(buf,2),bl,ALL_CLIENT); + +} + +/*========================================== + * Send main chat message [LuzZza] + *------------------------------------------*/ +void clif_MainChatMessage(const char* message) { + uint8 buf[200]; + int len; + + if(!message) + return; + + len = strlen(message)+1; + if (len+8 > sizeof(buf)) { + ShowDebug("clif_MainChatMessage: Received message too long (len %d): %s\n", len, message); + len = sizeof(buf)-8; + } + WBUFW(buf,0)=0x8d; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=0; + safestrncpy((char *) WBUFP(buf,8),message,len); + clif_send(buf,WBUFW(buf,2),NULL,CHAT_MAINCHAT); +} + +/// Send broadcast message with font formatting (ZC_BROADCAST2). +/// 01c3 <packet len>.W <fontColor>.L <fontType>.W <fontSize>.W <fontAlign>.W <fontY>.W <message>.?B +void clif_broadcast2(struct block_list* bl, const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, enum send_target target) +{ + unsigned char *buf = (unsigned char*)aMalloc((16 + len)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x1c3; + WBUFW(buf,2) = len + 16; + WBUFL(buf,4) = fontColor; + WBUFW(buf,8) = fontType; + WBUFW(buf,10) = fontSize; + WBUFW(buf,12) = fontAlign; + WBUFW(buf,14) = fontY; + memcpy(WBUFP(buf,16), mes, len); + clif_send(buf, WBUFW(buf,2), bl, target); + + if (buf) + aFree(buf); +} + + +/// Displays heal effect (ZC_RECOVERY). +/// 013d <var id>.W <amount>.W +/// var id: +/// 5 = HP (SP_HP) +/// 7 = SP (SP_SP) +/// ? = ignored +void clif_heal(int fd,int type,int val) +{ + WFIFOHEAD(fd,packet_len(0x13d)); + WFIFOW(fd,0)=0x13d; + WFIFOW(fd,2)=type; + WFIFOW(fd,4)=cap_value(val,0,INT16_MAX); + WFIFOSET(fd,packet_len(0x13d)); +} + + +/// Displays resurrection effect (ZC_RESURRECTION). +/// 0148 <id>.L <type>.W +/// type: +/// ignored +void clif_resurrection(struct block_list *bl,int type) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x148; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=0; + + clif_send(buf,packet_len(0x148),bl,type==1 ? AREA : AREA_WOS); + if (disguised(bl)) + clif_spawn(bl); +} + + +/// Sets the map property (ZC_NOTIFY_MAPPROPERTY). +/// 0199 <type>.W +void clif_map_property(struct map_session_data* sd, enum map_property property) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x199)); + WFIFOW(fd,0)=0x199; + WFIFOW(fd,2)=property; + WFIFOSET(fd,packet_len(0x199)); +} + + +/// Set the map type (ZC_NOTIFY_MAPPROPERTY2). +/// 01d6 <type>.W +void clif_map_type(struct map_session_data* sd, enum map_type type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1D6)); + WFIFOW(fd,0)=0x1D6; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x1D6)); +} + + +/// Updates PvP ranking (ZC_NOTIFY_RANKING). +/// 019a <id>.L <ranking>.L <total>.L +void clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) +{ + if(type == 2) { + int fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x19a)); + WFIFOW(fd,0) = 0x19a; + WFIFOL(fd,2) = sd->bl.id; + WFIFOL(fd,6) = pvprank; + WFIFOL(fd,10) = pvpnum; + WFIFOSET(fd,packet_len(0x19a)); + } else { + unsigned char buf[32]; + WBUFW(buf,0) = 0x19a; + WBUFL(buf,2) = sd->bl.id; + if(sd->sc.option&(OPTION_HIDE|OPTION_CLOAK)) + WBUFL(buf,6) = UINT32_MAX; //On client displays as -- + else + WBUFL(buf,6) = pvprank; + WBUFL(buf,10) = pvpnum; + if(sd->sc.option&OPTION_INVISIBLE || sd->disguise) //Causes crashes when a 'mob' with pvp info dies. + clif_send(buf,packet_len(0x19a),&sd->bl,SELF); + else if(!type) + clif_send(buf,packet_len(0x19a),&sd->bl,AREA); + else + clif_send(buf,packet_len(0x19a),&sd->bl,ALL_SAMEMAP); + } +} + + +/*========================================== + * + *------------------------------------------*/ +void clif_map_property_mapall(int map, enum map_property property) +{ + struct block_list bl; + unsigned char buf[16]; + + bl.id = 0; + bl.type = BL_NUL; + bl.m = map; + WBUFW(buf,0)=0x199; + WBUFW(buf,2)=property; + clif_send(buf,packet_len(0x199),&bl,ALL_SAMEMAP); +} + + +/// Notifies the client about the result of a refine attempt (ZC_ACK_ITEMREFINING). +/// 0188 <result>.W <index>.W <refine>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = downgrade +void clif_refine(int fd, int fail, int index, int val) +{ + WFIFOHEAD(fd,packet_len(0x188)); + WFIFOW(fd,0)=0x188; + WFIFOW(fd,2)=fail; + WFIFOW(fd,4)=index+2; + WFIFOW(fd,6)=val; + WFIFOSET(fd,packet_len(0x188)); +} + + +/// Notifies the client about the result of a weapon refine attempt (ZC_ACK_WEAPONREFINE). +/// 0223 <result>.L <nameid>.W +/// result: +/// 0 = "weapon upgraded: %s" MsgStringTable[911] in rgb(0,255,255) +/// 1 = "weapon upgraded: %s" MsgStringTable[912] in rgb(0,205,205) +/// 2 = "cannot upgrade %s until you level up the upgrade weapon skill" MsgStringTable[913] in rgb(255,200,200) +/// 3 = "you lack the item %s to upgrade the weapon" MsgStringTable[914] in rgb(255,200,200) +void clif_upgrademessage(int fd, int result, int item_id) +{ + WFIFOHEAD(fd,packet_len(0x223)); + WFIFOW(fd,0)=0x223; + WFIFOL(fd,2)=result; + WFIFOW(fd,6)=item_id; + WFIFOSET(fd,packet_len(0x223)); +} + + +/// Whisper is transmitted to the destination player (ZC_WHISPER). +/// 0097 <packet len>.W <nick>.24B <message>.?B +/// 0097 <packet len>.W <nick>.24B <isAdmin>.L <message>.?B (PACKETVER >= 20091104) +void clif_wis_message(int fd, const char* nick, const char* mes, int mes_len) +{ +#if PACKETVER < 20091104 + WFIFOHEAD(fd, mes_len + NAME_LENGTH + 4); + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + NAME_LENGTH + 4; + safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); + safestrncpy((char*)WFIFOP(fd,28), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); +#else + WFIFOHEAD(fd, mes_len + NAME_LENGTH + 8); + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + NAME_LENGTH + 8; + safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); + WFIFOL(fd,28) = 0; // isAdmin; if nonzero, also displays text above char + // TODO: WFIFOL(fd,28) = pc_get_group_level(ssd); + safestrncpy((char*)WFIFOP(fd,32), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); +#endif +} + + +/// Inform the player about the result of his whisper action (ZC_ACK_WHISPER). +/// 0098 <result>.B +/// result: +/// 0 = success to send wisper +/// 1 = target character is not loged in +/// 2 = ignored by target +/// 3 = everyone ignored by target +void clif_wis_end(int fd, int flag) +{ + WFIFOHEAD(fd,packet_len(0x98)); + WFIFOW(fd,0) = 0x98; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x98)); +} + + +/// Returns character name requested by char_id (ZC_ACK_REQNAME_BYGID). +/// 0194 <char id>.L <name>.24B +void clif_solved_charname(int fd, int charid, const char* name) +{ + WFIFOHEAD(fd,packet_len(0x194)); + WFIFOW(fd,0)=0x194; + WFIFOL(fd,2)=charid; + safestrncpy((char*)WFIFOP(fd,6), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x194)); +} + + +/// Presents a list of items that can be carded/composed (ZC_ITEMCOMPOSITION_LIST). +/// 017b <packet len>.W { <name id>.W }* +void clif_use_card(struct map_session_data *sd,int idx) +{ + int i,c,ep; + int fd=sd->fd; + + nullpo_retv(sd); + if (idx < 0 || idx >= MAX_INVENTORY) //Crash-fix from bad packets. + return; + + if (!sd->inventory_data[idx] || sd->inventory_data[idx]->type != IT_CARD) + return; //Avoid parsing invalid item indexes (no card/no item) + + ep=sd->inventory_data[idx]->equip; + WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x17b; + + for(i=c=0;i<MAX_INVENTORY;i++){ + int j; + + if(sd->inventory_data[i] == NULL) + continue; + if(sd->inventory_data[i]->type!=IT_WEAPON && sd->inventory_data[i]->type!=IT_ARMOR) + continue; + if(itemdb_isspecial(sd->status.inventory[i].card[0])) //Can't slot it + continue; + + if(sd->status.inventory[i].identify==0 ) //Not identified + continue; + + if((sd->inventory_data[i]->equip&ep)==0) //Not equippable on this part. + continue; + + if(sd->inventory_data[i]->type==IT_WEAPON && ep==EQP_SHIELD) //Shield card won't go on left weapon. + continue; + + ARR_FIND( 0, sd->inventory_data[i]->slot, j, sd->status.inventory[i].card[j] == 0 ); + if( j == sd->inventory_data[i]->slot ) // No room + continue; + + WFIFOW(fd,4+c*2)=i+2; + c++; + } + WFIFOW(fd,2)=4+c*2; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies the client about the result of item carding/composition (ZC_ACK_ITEMCOMPOSITION). +/// 017d <equip index>.W <card index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x17d)); + WFIFOW(fd,0)=0x17d; + WFIFOW(fd,2)=idx_equip+2; + WFIFOW(fd,4)=idx_card+2; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,packet_len(0x17d)); +} + + +/// Presents a list of items that can be identified (ZC_ITEMIDENTIFY_LIST). +/// 0177 <packet len>.W { <name id>.W }* +void clif_item_identify_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x177; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && !sd->status.inventory[i].identify){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = MC_IDENTIFY; + sd->menuskill_val = c; + } +} + + +/// Notifies the client about the result of a item identify request (ZC_ACK_ITEMIDENTIFY). +/// 0179 <index>.W <result>.B +void clif_item_identified(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x179)); + WFIFOW(fd, 0)=0x179; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len(0x179)); +} + + +/// Presents a list of items that can be repaired (ZC_REPAIRITEMLIST). +/// 01fc <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_item_repair_list(struct map_session_data *sd,struct map_session_data *dstsd, int lv) +{ + int i,c; + int fd; + int nameid; + + nullpo_retv(sd); + nullpo_retv(dstsd); + + fd=sd->fd; + + WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); + WFIFOW(fd,0)=0x1fc; + for(i=c=0;i<MAX_INVENTORY;i++){ + if((nameid=dstsd->status.inventory[i].nameid) > 0 && dstsd->status.inventory[i].attribute!=0){// && skill_can_repair(sd,nameid)){ + WFIFOW(fd,c*13+4) = i; + WFIFOW(fd,c*13+6) = nameid; + WFIFOB(fd,c*13+8) = dstsd->status.inventory[i].refine; + clif_addcards(WFIFOP(fd,c*13+9), &dstsd->status.inventory[i]); + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*13+4; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = BS_REPAIRWEAPON; + sd->menuskill_val = dstsd->bl.id; + sd->menuskill_val2 = lv; + }else + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); +} + + +/// Notifies the client about the result of a item repair request (ZC_ACK_ITEMREPAIR). +/// 01fe <index>.W <result>.B +/// index: +/// ignored (inventory index) +/// result: +/// 0 = Item repair success. +/// 1 = Item repair failure. +void clif_item_repaireffect(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x1fe)); + WFIFOW(fd, 0)=0x1fe; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len(0x1fe)); + +} + + +/// Displays a message, that an equipment got damaged (ZC_EQUIPITEM_DAMAGED). +/// 02bb <equip location>.W <account id>.L +void clif_item_damaged(struct map_session_data* sd, unsigned short position) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2bb)); + WFIFOW(fd,0) = 0x2bb; + WFIFOW(fd,2) = position; + WFIFOL(fd,4) = sd->bl.id; // TODO: the packet seems to be sent to other people as well, probably party and/or guild. + WFIFOSET(fd,packet_len(0x2bb)); +} + + +/// Presents a list of weapon items that can be refined [Taken from jAthena] (ZC_NOTIFY_WEAPONITEMLIST). +/// 0221 <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_item_refine_list(struct map_session_data *sd) +{ + int i,c; + int fd; + uint16 skill_lv; + int wlv; + int refine_item[5]; + + nullpo_retv(sd); + + skill_lv = pc_checkskill(sd,WS_WEAPONREFINE); + + fd=sd->fd; + + refine_item[0] = -1; + refine_item[1] = pc_search_inventory(sd,1010); + refine_item[2] = pc_search_inventory(sd,1011); + refine_item[3] = refine_item[4] = pc_search_inventory(sd,984); + + WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); + WFIFOW(fd,0)=0x221; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].refine < skill_lv && + sd->status.inventory[i].identify && (wlv=itemdb_wlv(sd->status.inventory[i].nameid)) >=1 && + refine_item[wlv]!=-1 && !(sd->status.inventory[i].equip&EQP_ARMS)){ + WFIFOW(fd,c*13+ 4)=i+2; + WFIFOW(fd,c*13+ 6)=sd->status.inventory[i].nameid; + WFIFOB(fd,c*13+ 8)=sd->status.inventory[i].refine; + clif_addcards(WFIFOP(fd,c*13+9), &sd->status.inventory[i]); + c++; + } + } + WFIFOW(fd,2)=c*13+4; + WFIFOSET(fd,WFIFOW(fd,2)); + if (c > 0) { + sd->menuskill_id = WS_WEAPONREFINE; + sd->menuskill_val = skill_lv; + } +} + + +/// Notification of an auto-casted skill (ZC_AUTORUN_SKILL). +/// 0147 <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B +void clif_item_skill(struct map_session_data *sd,uint16 skill_id,uint16 skill_lv) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x147)); + WFIFOW(fd, 0)=0x147; + WFIFOW(fd, 2)=skill_id; + WFIFOW(fd, 4)=skill_get_inf(skill_id); + WFIFOW(fd, 6)=0; + WFIFOW(fd, 8)=skill_lv; + WFIFOW(fd,10)=skill_get_sp(skill_id,skill_lv); + WFIFOW(fd,12)=skill_get_range2(&sd->bl, skill_id,skill_lv); + safestrncpy((char*)WFIFOP(fd,14),skill_get_name(skill_id),NAME_LENGTH); + WFIFOB(fd,38)=0; + WFIFOSET(fd,packet_len(0x147)); +} + + +/// Adds an item to character's cart. +/// 0124 <index>.W <amount>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART) +/// 01c5 <index>.W <amount>.L <name id>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART2) +void clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail) +{ + int view,fd; + unsigned char *buf; + + nullpo_retv(sd); + + fd=sd->fd; + if(n<0 || n>=MAX_CART || sd->status.cart[n].nameid<=0) + return; + +#if PACKETVER < 5 + WFIFOHEAD(fd,packet_len(0x124)); + buf=WFIFOP(fd,0); + WBUFW(buf,0)=0x124; + WBUFW(buf,2)=n+2; + WBUFL(buf,4)=amount; + if((view = itemdb_viewid(sd->status.cart[n].nameid)) > 0) + WBUFW(buf,8)=view; + else + WBUFW(buf,8)=sd->status.cart[n].nameid; + WBUFB(buf,10)=sd->status.cart[n].identify; + WBUFB(buf,11)=sd->status.cart[n].attribute; + WBUFB(buf,12)=sd->status.cart[n].refine; + clif_addcards(WBUFP(buf,13), &sd->status.cart[n]); + WFIFOSET(fd,packet_len(0x124)); +#else + WFIFOHEAD(fd,packet_len(0x1c5)); + buf=WFIFOP(fd,0); + WBUFW(buf,0)=0x1c5; + WBUFW(buf,2)=n+2; + WBUFL(buf,4)=amount; + if((view = itemdb_viewid(sd->status.cart[n].nameid)) > 0) + WBUFW(buf,8)=view; + else + WBUFW(buf,8)=sd->status.cart[n].nameid; + WBUFB(buf,10)=itemdb_type(sd->status.cart[n].nameid); + WBUFB(buf,11)=sd->status.cart[n].identify; + WBUFB(buf,12)=sd->status.cart[n].attribute; + WBUFB(buf,13)=sd->status.cart[n].refine; + clif_addcards(WBUFP(buf,14), &sd->status.cart[n]); + WFIFOSET(fd,packet_len(0x1c5)); +#endif +} + + +/// Deletes an item from character's cart (ZC_DELETE_ITEM_FROM_CART). +/// 0125 <index>.W <amount>.L +void clif_cart_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd,packet_len(0x125)); + WFIFOW(fd,0)=0x125; + WFIFOW(fd,2)=n+2; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len(0x125)); +} + + +/// Opens the shop creation menu (ZC_OPENSTORE). +/// 012d <num>.W +/// num: +/// number of allowed item slots +void clif_openvendingreq(struct map_session_data* sd, int num) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x12d)); + WFIFOW(fd,0) = 0x12d; + WFIFOW(fd,2) = num; + WFIFOSET(fd,packet_len(0x12d)); +} + + +/// Displays a vending board to target/area (ZC_STORE_ENTRY). +/// 0131 <owner id>.L <message>.80B +void clif_showvendingboard(struct block_list* bl, const char* message, int fd) +{ + unsigned char buf[128]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x131; + WBUFL(buf,2) = bl->id; + safestrncpy((char*)WBUFP(buf,6), message, 80); + + if( fd ) { + WFIFOHEAD(fd,packet_len(0x131)); + memcpy(WFIFOP(fd,0),buf,packet_len(0x131)); + WFIFOSET(fd,packet_len(0x131)); + } else { + clif_send(buf,packet_len(0x131),bl,AREA_WOS); + } +} + + +/// Removes a vending board from screen (ZC_DISAPPEAR_ENTRY). +/// 0132 <owner id>.L +void clif_closevendingboard(struct block_list* bl, int fd) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x132; + WBUFL(buf,2) = bl->id; + if( fd ) { + WFIFOHEAD(fd,packet_len(0x132)); + memcpy(WFIFOP(fd,0),buf,packet_len(0x132)); + WFIFOSET(fd,packet_len(0x132)); + } else { + clif_send(buf,packet_len(0x132),bl,AREA_WOS); + } +} + + +/// Sends a list of items in a shop. +/// R 0133 <packet len>.W <owner id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC) +/// R 0800 <packet len>.W <owner id>.L <unique id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC2) +void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* vending) +{ + int i,fd; + int count; + struct map_session_data* vsd; +#if PACKETVER < 20100105 + const int cmd = 0x133; + const int offset = 8; +#else + const int cmd = 0x800; + const int offset = 12; +#endif + + nullpo_retv(sd); + nullpo_retv(vending); + nullpo_retv(vsd=map_id2sd(id)); + + fd = sd->fd; + count = vsd->vend_num; + + WFIFOHEAD(fd, offset+count*22); + WFIFOW(fd,0) = cmd; + WFIFOW(fd,2) = offset+count*22; + WFIFOL(fd,4) = id; +#if PACKETVER >= 20100105 + WFIFOL(fd,8) = vsd->vender_id; +#endif + + for( i = 0; i < count; i++ ) + { + int index = vending[i].index; + struct item_data* data = itemdb_search(vsd->status.cart[index].nameid); + WFIFOL(fd,offset+ 0+i*22) = vending[i].value; + WFIFOW(fd,offset+ 4+i*22) = vending[i].amount; + WFIFOW(fd,offset+ 6+i*22) = vending[i].index + 2; + WFIFOB(fd,offset+ 8+i*22) = itemtype(data->type); + WFIFOW(fd,offset+ 9+i*22) = ( data->view_id > 0 ) ? data->view_id : vsd->status.cart[index].nameid; + WFIFOB(fd,offset+11+i*22) = vsd->status.cart[index].identify; + WFIFOB(fd,offset+12+i*22) = vsd->status.cart[index].attribute; + WFIFOB(fd,offset+13+i*22) = vsd->status.cart[index].refine; + clif_addcards(WFIFOP(fd,offset+14+i*22), &vsd->status.cart[index]); + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Shop purchase failure (ZC_PC_PURCHASE_RESULT_FROMMC). +/// 0135 <index>.W <amount>.W <result>.B +/// result: +/// 0 = success +/// 1 = not enough zeny +/// 2 = overweight +/// 4 = out of stock +/// 5 = "cannot use an npc shop while in a trade" +/// 6 = Because the store information was incorrect the item was not purchased. +/// 7 = No sales information. +void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x135)); + WFIFOW(fd,0) = 0x135; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOB(fd,6) = fail; + WFIFOSET(fd,packet_len(0x135)); +} + + +/// Shop creation success (ZC_PC_PURCHASE_MYITEMLIST). +/// 0136 <packet len>.W <owner id>.L { <price>.L <index>.W <amount>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_openvending(struct map_session_data* sd, int id, struct s_vending* vending) +{ + int i,fd; + int count; + + nullpo_retv(sd); + + fd = sd->fd; + count = sd->vend_num; + + WFIFOHEAD(fd, 8+count*22); + WFIFOW(fd,0) = 0x136; + WFIFOW(fd,2) = 8+count*22; + WFIFOL(fd,4) = id; + for( i = 0; i < count; i++ ) + { + int index = vending[i].index; + struct item_data* data = itemdb_search(sd->status.cart[index].nameid); + WFIFOL(fd, 8+i*22) = vending[i].value; + WFIFOW(fd,12+i*22) = vending[i].index + 2; + WFIFOW(fd,14+i*22) = vending[i].amount; + WFIFOB(fd,16+i*22) = itemtype(data->type); + WFIFOW(fd,17+i*22) = ( data->view_id > 0 ) ? data->view_id : sd->status.cart[index].nameid; + WFIFOB(fd,19+i*22) = sd->status.cart[index].identify; + WFIFOB(fd,20+i*22) = sd->status.cart[index].attribute; + WFIFOB(fd,21+i*22) = sd->status.cart[index].refine; + clif_addcards(WFIFOP(fd,22+i*22), &sd->status.cart[index]); + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Inform merchant that someone has bought an item (ZC_DELETEITEM_FROM_MCSTORE). +/// 0137 <index>.W <amount>.W +void clif_vendingreport(struct map_session_data* sd, int index, int amount) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x137)); + WFIFOW(fd,0) = 0x137; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOSET(fd,packet_len(0x137)); +} + + +/// Result of organizing a party (ZC_ACK_MAKE_GROUP). +/// 00fa <result>.B +/// result: +/// 0 = opens party window and shows MsgStringTable[77]="party successfully organized" +/// 1 = MsgStringTable[78]="party name already exists" +/// 2 = MsgStringTable[79]="already in a party" +/// 3 = cannot organize parties on this map +/// ? = nothing +void clif_party_created(struct map_session_data *sd,int result) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xfa)); + WFIFOW(fd,0)=0xfa; + WFIFOB(fd,2)=result; + WFIFOSET(fd,packet_len(0xfa)); +} + + +/// Adds new member to a party. +/// 0104 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B (ZC_ADD_MEMBER_TO_GROUP) +/// 01e9 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B <item pickup rule>.B <item share rule>.B (ZC_ADD_MEMBER_TO_GROUP2) +/// role: +/// 0 = leader +/// 1 = normal +/// state: +/// 0 = connected +/// 1 = disconnected +void clif_party_member_info(struct party_data *p, struct map_session_data *sd) +{ + unsigned char buf[81]; + int i; + + if (!sd) { //Pick any party member (this call is used when changing item share rules) + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd != 0 ); + } else { + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); + } + if (i >= MAX_PARTY) return; //Should never happen... + sd = p->data[i].sd; + + WBUFW(buf, 0) = 0x1e9; + WBUFL(buf, 2) = sd->status.account_id; + WBUFL(buf, 6) = (p->party.member[i].leader)?0:1; + WBUFW(buf,10) = sd->bl.x; + WBUFW(buf,12) = sd->bl.y; + WBUFB(buf,14) = (p->party.member[i].online)?0:1; + memcpy(WBUFP(buf,15), p->party.name, NAME_LENGTH); + memcpy(WBUFP(buf,39), sd->status.name, NAME_LENGTH); + mapindex_getmapname_ext(map[sd->bl.m].name, (char*)WBUFP(buf,63)); + WBUFB(buf,79) = (p->party.item&1)?1:0; + WBUFB(buf,80) = (p->party.item&2)?1:0; + clif_send(buf,packet_len(0x1e9),&sd->bl,PARTY); +} + + +/// Sends party information (ZC_GROUP_LIST). +/// 00fb <packet len>.W <party name>.24B { <account id>.L <nick>.24B <map name>.16B <role>.B <state>.B }* +/// role: +/// 0 = leader +/// 1 = normal +/// state: +/// 0 = connected +/// 1 = disconnected +void clif_party_info(struct party_data* p, struct map_session_data *sd) +{ + unsigned char buf[2+2+NAME_LENGTH+(4+NAME_LENGTH+MAP_NAME_LENGTH_EXT+1+1)*MAX_PARTY]; + struct map_session_data* party_sd = NULL; + int i, c; + + nullpo_retv(p); + + WBUFW(buf,0) = 0xfb; + memcpy(WBUFP(buf,4), p->party.name, NAME_LENGTH); + for(i = 0, c = 0; i < MAX_PARTY; i++) + { + struct party_member* m = &p->party.member[i]; + if(!m->account_id) continue; + + if(party_sd == NULL) party_sd = p->data[i].sd; + + WBUFL(buf,28+c*46) = m->account_id; + memcpy(WBUFP(buf,28+c*46+4), m->name, NAME_LENGTH); + mapindex_getmapname_ext(mapindex_id2name(m->map), (char*)WBUFP(buf,28+c*46+28)); + WBUFB(buf,28+c*46+44) = (m->leader) ? 0 : 1; + WBUFB(buf,28+c*46+45) = (m->online) ? 0 : 1; + c++; + } + WBUFW(buf,2) = 28+c*46; + + if(sd) { // send only to self + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } else if (party_sd) { // send to whole party + clif_send(buf, WBUFW(buf,2), &party_sd->bl, PARTY); + } +} + + +/// The player's 'party invite' state, sent during login (ZC_PARTY_CONFIG). +/// 02c9 <flag>.B +/// flag: +/// 0 = allow party invites +/// 1 = auto-deny party invites +void clif_partyinvitationstate(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2c9)); + WFIFOW(fd, 0) = 0x2c9; + WFIFOB(fd, 2) = 0; // not implemented + WFIFOSET(fd, packet_len(0x2c9)); +} + + +/// Party invitation request. +/// 00fe <party id>.L <party name>.24B (ZC_REQ_JOIN_GROUP) +/// 02c6 <party id>.L <party name>.24B (ZC_PARTY_JOIN_REQ) +void clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) +{ +#if PACKETVER < 20070821 + const int cmd = 0xfe; +#else + const int cmd = 0x2c6; +#endif + int fd; + struct party_data *p; + + nullpo_retv(sd); + nullpo_retv(tsd); + + fd=tsd->fd; + + if( (p=party_search(sd->status.party_id))==NULL ) + return; + + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd,0)=cmd; + WFIFOL(fd,2)=sd->status.party_id; + memcpy(WFIFOP(fd,6),p->party.name,NAME_LENGTH); + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Party invite result. +/// 00fd <nick>.24S <result>.B (ZC_ACK_REQ_JOIN_GROUP) +/// 02c5 <nick>.24S <result>.L (ZC_PARTY_JOIN_REQ_ACK) +/// result=0 : char is already in a party -> MsgStringTable[80] +/// result=1 : party invite was rejected -> MsgStringTable[81] +/// result=2 : party invite was accepted -> MsgStringTable[82] +/// result=3 : party is full -> MsgStringTable[83] +/// result=4 : char of the same account already joined the party -> MsgStringTable[608] +/// result=5 : char blocked party invite -> MsgStringTable[1324] (since 20070904) +/// result=7 : char is not online or doesn't exist -> MsgStringTable[71] (since 20070904) +/// result=8 : (%s) TODO instance related? -> MsgStringTable[1388] (since 20080527) +/// return=9 : TODO map prohibits party joining? -> MsgStringTable[1871] (since 20110205) +void clif_party_inviteack(struct map_session_data* sd, const char* nick, int result) +{ + int fd; + nullpo_retv(sd); + fd=sd->fd; + +#if PACKETVER < 20070904 + if( result == 7 ) { + clif_displaymessage(fd, msg_txt(3)); + return; + } +#endif + +#if PACKETVER < 20070821 + WFIFOHEAD(fd,packet_len(0xfd)); + WFIFOW(fd,0) = 0xfd; + safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); + WFIFOB(fd,26) = result; + WFIFOSET(fd,packet_len(0xfd)); +#else + WFIFOHEAD(fd,packet_len(0x2c5)); + WFIFOW(fd,0) = 0x2c5; + safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); + WFIFOL(fd,26) = result; + WFIFOSET(fd,packet_len(0x2c5)); +#endif +} + + +/// Updates party settings. +/// 0101 <exp option>.L (ZC_GROUPINFO_CHANGE) +/// 07d8 <exp option>.L <item pick rule>.B <item share rule>.B (ZC_REQ_GROUPINFO_CHANGE_V2) +/// exp option: +/// 0 = exp sharing disabled +/// 1 = exp sharing enabled +/// 2 = cannot change exp sharing +/// +/// flag: +/// 0 = send to party +/// 1 = send to sd +void clif_party_option(struct party_data *p,struct map_session_data *sd,int flag) +{ + unsigned char buf[16]; +#if PACKETVER < 20090603 + const int cmd = 0x101; +#else + const int cmd = 0x7d8; +#endif + + nullpo_retv(p); + + if(!sd && flag==0){ + int i; + for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); + if (i < MAX_PARTY) + sd = p->data[i].sd; + } + if(!sd) return; + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=((flag&0x01)?2:p->party.exp); +#if PACKETVER >= 20090603 + WBUFB(buf,6)=(p->party.item&1)?1:0; + WBUFB(buf,7)=(p->party.item&2)?1:0; +#endif + if(flag==0) + clif_send(buf,packet_len(cmd),&sd->bl,PARTY); + else + clif_send(buf,packet_len(cmd),&sd->bl,SELF); +} + + +/// 0105 <account id>.L <char name>.24B <result>.B (ZC_DELETE_MEMBER_FROM_GROUP). +/// result: +/// 0 = leave +/// 1 = expel +/// 2 = cannot leave party on this map +/// 3 = cannot expel from party on this map +void clif_party_withdraw(struct party_data* p, struct map_session_data* sd, int account_id, const char* name, int flag) +{ + unsigned char buf[64]; + int i; + + nullpo_retv(p); + + if(!sd && (flag&0xf0)==0) + { + for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); + if (i < MAX_PARTY) + sd = p->data[i].sd; + } + + if(!sd) return; + + WBUFW(buf,0)=0x105; + WBUFL(buf,2)=account_id; + memcpy(WBUFP(buf,6),name,NAME_LENGTH); + WBUFB(buf,30)=flag&0x0f; + if((flag&0xf0)==0) + clif_send(buf,packet_len(0x105),&sd->bl,PARTY); + else + clif_send(buf,packet_len(0x105),&sd->bl,SELF); +} + + +/// Party chat message (ZC_NOTIFY_CHAT_PARTY). +/// 0109 <packet len>.W <account id>.L <message>.?B +void clif_party_message(struct party_data* p, int account_id, const char* mes, int len) +{ + struct map_session_data *sd; + int i; + + nullpo_retv(p); + + for(i=0; i < MAX_PARTY && !p->data[i].sd;i++); + if(i < MAX_PARTY){ + unsigned char buf[1024]; + + if( len > sizeof(buf)-8 ) + { + ShowWarning("clif_party_message: Truncated message '%s' (len=%d, max=%d, party_id=%d).\n", mes, len, sizeof(buf)-8, p->party.party_id); + len = sizeof(buf)-8; + } + + sd = p->data[i].sd; + WBUFW(buf,0)=0x109; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=account_id; + safestrncpy((char *)WBUFP(buf,8), mes, len); + clif_send(buf,len+8,&sd->bl,PARTY); + } +} + + +/// Updates the position of a party member on the minimap (ZC_NOTIFY_POSITION_TO_GROUPM). +/// 0107 <account id>.L <x>.W <y>.W +void clif_party_xy(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x107; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=sd->bl.x; + WBUFW(buf,8)=sd->bl.y; + clif_send(buf,packet_len(0x107),&sd->bl,PARTY_SAMEMAP_WOS); +} + + +/*========================================== + * Sends x/y dot to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_party_xy_single(int fd, struct map_session_data *sd) +{ + WFIFOHEAD(fd,packet_len(0x107)); + WFIFOW(fd,0)=0x107; + WFIFOL(fd,2)=sd->status.account_id; + WFIFOW(fd,6)=sd->bl.x; + WFIFOW(fd,8)=sd->bl.y; + WFIFOSET(fd,packet_len(0x107)); +} + + +/// Updates HP bar of a party member. +/// 0106 <account id>.L <hp>.W <max hp>.W (ZC_NOTIFY_HP_TO_GROUPM) +/// 080e <account id>.L <hp>.L <max hp>.L (ZC_NOTIFY_HP_TO_GROUPM_R2) +void clif_party_hp(struct map_session_data *sd) +{ + unsigned char buf[16]; +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + + nullpo_retv(sd); + + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=sd->status.account_id; +#if PACKETVER < 20100126 + if (sd->battle_status.max_hp > INT16_MAX) { //To correctly display the %hp bar. [Skotlex] + WBUFW(buf,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WBUFW(buf,8) = 100; + } else { + WBUFW(buf,6) = sd->battle_status.hp; + WBUFW(buf,8) = sd->battle_status.max_hp; + } +#else + WBUFL(buf,6) = sd->battle_status.hp; + WBUFL(buf,10) = sd->battle_status.max_hp; +#endif + clif_send(buf,packet_len(cmd),&sd->bl,PARTY_AREA_WOS); +} + + +/*========================================== + * Sends HP bar to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_hpmeter_single(int fd, int id, unsigned int hp, unsigned int maxhp) +{ +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd,0) = cmd; + WFIFOL(fd,2) = id; +#if PACKETVER < 20100126 + if( maxhp > INT16_MAX ) + {// To correctly display the %hp bar. [Skotlex] + WFIFOW(fd,6) = hp/(maxhp/100); + WFIFOW(fd,8) = 100; + } else { + WFIFOW(fd,6) = hp; + WFIFOW(fd,8) = maxhp; + } +#else + WFIFOL(fd,6) = hp; + WFIFOL(fd,10) = maxhp; +#endif + WFIFOSET(fd, packet_len(cmd)); +} + +/// Notifies the client, that it's attack target is too far (ZC_ATTACK_FAILURE_FOR_DISTANCE). +/// 0139 <target id>.L <target x>.W <target y>.W <x>.W <y>.W <atk range>.W +void clif_movetoattack(struct map_session_data *sd,struct block_list *bl) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x139)); + WFIFOW(fd, 0)=0x139; + WFIFOL(fd, 2)=bl->id; + WFIFOW(fd, 6)=bl->x; + WFIFOW(fd, 8)=bl->y; + WFIFOW(fd,10)=sd->bl.x; + WFIFOW(fd,12)=sd->bl.y; + WFIFOW(fd,14)=sd->battle_status.rhw.range; + WFIFOSET(fd,packet_len(0x139)); +} + + +/// Notifies the client about the result of an item produce request (ZC_ACK_REQMAKINGITEM). +/// 018f <result>.W <name id>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = success (alchemist) +/// 3 = failure (alchemist) +void clif_produceeffect(struct map_session_data* sd,int flag,int nameid) +{ + int view,fd; + + nullpo_retv(sd); + + fd = sd->fd; + clif_solved_charname(fd, sd->status.char_id, sd->status.name); + WFIFOHEAD(fd,packet_len(0x18f)); + WFIFOW(fd, 0)=0x18f; + WFIFOW(fd, 2)=flag; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd, 4)=view; + else + WFIFOW(fd, 4)=nameid; + WFIFOSET(fd,packet_len(0x18f)); +} + + +/// Initiates the pet taming process (ZC_START_CAPTURE). +/// 019e +void clif_catch_process(struct map_session_data *sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x19e)); + WFIFOW(fd,0)=0x19e; + WFIFOSET(fd,packet_len(0x19e)); +} + + +/// Displays the result of a pet taming attempt (ZC_TRYCAPTURE_MONSTER). +/// 01a0 <result>.B +/// 0 = failure +/// 1 = success +void clif_pet_roulette(struct map_session_data *sd,int data) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1a0)); + WFIFOW(fd,0)=0x1a0; + WFIFOB(fd,2)=data; + WFIFOSET(fd,packet_len(0x1a0)); +} + + +/// Presents a list of pet eggs that can be hatched (ZC_PETEGG_LIST). +/// 01a6 <packet len>.W { <index>.W }* +void clif_sendegg(struct map_session_data *sd) +{ + int i,n=0,fd; + + nullpo_retv(sd); + + fd=sd->fd; + if (battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m)) + { //Disable pet hatching in GvG grounds during Guild Wars [Skotlex] + clif_displaymessage(fd, msg_txt(666)); + return; + } + WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x1a6; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=IT_PETEGG || + sd->status.inventory[i].amount<=0) + continue; + WFIFOW(fd,n*2+4)=i+2; + n++; + } + WFIFOW(fd,2)=4+n*2; + WFIFOSET(fd,WFIFOW(fd,2)); + + sd->menuskill_id = SA_TAMINGMONSTER; + sd->menuskill_val = -1; +} + + +/// Sends a specific pet data update (ZC_CHANGESTATE_PET). +/// 01a4 <type>.B <id>.L <data>.L +/// type: +/// 0 = pre-init (data = 0) +/// 1 = intimacy (data = 0~4) +/// 2 = hunger (data = 0~4) +/// 3 = accessory +/// 4 = performance (data = 1~3: normal, 4: special) +/// 5 = hairstyle +/// +/// If sd is null, the update is sent to nearby objects, otherwise it is sent only to that player. +void clif_send_petdata(struct map_session_data* sd, struct pet_data* pd, int type, int param) +{ + uint8 buf[16]; + nullpo_retv(pd); + + WBUFW(buf,0) = 0x1a4; + WBUFB(buf,2) = type; + WBUFL(buf,3) = pd->bl.id; + WBUFL(buf,7) = param; + if (sd) + clif_send(buf, packet_len(0x1a4), &sd->bl, SELF); + else + clif_send(buf, packet_len(0x1a4), &pd->bl, AREA); +} + + +/// Pet's base data (ZC_PROPERTY_PET). +/// 01a2 <name>.24B <renamed>.B <level>.W <hunger>.W <intimacy>.W <accessory id>.W <class>.W +void clif_send_petstatus(struct map_session_data *sd) +{ + int fd; + struct s_pet *pet; + + nullpo_retv(sd); + nullpo_retv(sd->pd); + + fd=sd->fd; + pet = &sd->pd->pet; + WFIFOHEAD(fd,packet_len(0x1a2)); + WFIFOW(fd,0)=0x1a2; + memcpy(WFIFOP(fd,2),pet->name,NAME_LENGTH); + WFIFOB(fd,26)=battle_config.pet_rename?0:pet->rename_flag; + WFIFOW(fd,27)=pet->level; + WFIFOW(fd,29)=pet->hungry; + WFIFOW(fd,31)=pet->intimate; + WFIFOW(fd,33)=pet->equip; +#if PACKETVER >= 20081126 + WFIFOW(fd,35)=pet->class_; +#endif + WFIFOSET(fd,packet_len(0x1a2)); +} + + +/// Notification about a pet's emotion/talk (ZC_PET_ACT). +/// 01aa <id>.L <data>.L +/// data: +/// @see CZ_PET_ACT. +void clif_pet_emotion(struct pet_data *pd,int param) +{ + unsigned char buf[16]; + + nullpo_retv(pd); + + memset(buf,0,packet_len(0x1aa)); + + WBUFW(buf,0)=0x1aa; + WBUFL(buf,2)=pd->bl.id; + if(param >= 100 && pd->petDB->talk_convert_class) { + if(pd->petDB->talk_convert_class < 0) + return; + else if(pd->petDB->talk_convert_class > 0) { + // replace mob_id component of talk/act data + param -= (pd->pet.class_ - 100)*100; + param += (pd->petDB->talk_convert_class - 100)*100; + } + } + WBUFL(buf,6)=param; + + clif_send(buf,packet_len(0x1aa),&pd->bl,AREA); +} + + +/// Result of request to feed a pet (ZC_FEED_PET). +/// 01a3 <result>.B <name id>.W +/// result: +/// 0 = failure +/// 1 = success +void clif_pet_food(struct map_session_data *sd,int foodid,int fail) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1a3)); + WFIFOW(fd,0)=0x1a3; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len(0x1a3)); +} + + +/// Presents a list of skills that can be auto-spelled (ZC_AUTOSPELLLIST). +/// 01cd { <skill id>.L }*7 +void clif_autospell(struct map_session_data *sd,uint16 skill_lv) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1cd)); + WFIFOW(fd, 0)=0x1cd; + + if(skill_lv>0 && pc_checkskill(sd,MG_NAPALMBEAT)>0) + WFIFOL(fd,2)= MG_NAPALMBEAT; + else + WFIFOL(fd,2)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_COLDBOLT)>0) + WFIFOL(fd,6)= MG_COLDBOLT; + else + WFIFOL(fd,6)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_FIREBOLT)>0) + WFIFOL(fd,10)= MG_FIREBOLT; + else + WFIFOL(fd,10)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_LIGHTNINGBOLT)>0) + WFIFOL(fd,14)= MG_LIGHTNINGBOLT; + else + WFIFOL(fd,14)= 0x00000000; + if(skill_lv>4 && pc_checkskill(sd,MG_SOULSTRIKE)>0) + WFIFOL(fd,18)= MG_SOULSTRIKE; + else + WFIFOL(fd,18)= 0x00000000; + if(skill_lv>7 && pc_checkskill(sd,MG_FIREBALL)>0) + WFIFOL(fd,22)= MG_FIREBALL; + else + WFIFOL(fd,22)= 0x00000000; + if(skill_lv>9 && pc_checkskill(sd,MG_FROSTDIVER)>0) + WFIFOL(fd,26)= MG_FROSTDIVER; + else + WFIFOL(fd,26)= 0x00000000; + + WFIFOSET(fd,packet_len(0x1cd)); + sd->menuskill_id = SA_AUTOSPELL; + sd->menuskill_val = skill_lv; +} + + +/// Devotion's visual effect (ZC_DEVOTIONLIST). +/// 01cf <devoter id>.L { <devotee id>.L }*5 <max distance>.W +void clif_devotion(struct block_list *src, struct map_session_data *tsd) +{ + unsigned char buf[56]; + int i; + + nullpo_retv(src); + memset(buf,0,packet_len(0x1cf)); + + WBUFW(buf,0) = 0x1cf; + WBUFL(buf,2) = src->id; + if( src->type == BL_MER ) + { + struct mercenary_data *md = BL_CAST(BL_MER,src); + if( md && md->master && md->devotion_flag ) + WBUFL(buf,6) = md->master->bl.id; + + WBUFW(buf,26) = skill_get_range2(src, ML_DEVOTION, mercenary_checkskill(md, ML_DEVOTION)); + } + else + { + struct map_session_data *sd = BL_CAST(BL_PC,src); + if( sd == NULL ) + return; + + for( i = 0; i < 5; i++ ) + WBUFL(buf,6+4*i) = sd->devotion[i]; + WBUFW(buf,26) = skill_get_range2(src, CR_DEVOTION, pc_checkskill(sd, CR_DEVOTION)); + } + + if( tsd ) + clif_send(buf, packet_len(0x1cf), &tsd->bl, SELF); + else + clif_send(buf, packet_len(0x1cf), src, AREA); +} + +/*========================================== + * Server tells clients nearby 'sd' (and himself) to display 'sd->spiritball' number of spiritballs on 'sd' + * Notifies clients in an area of an object's spirits. + * 01d0 <id>.L <amount>.W (ZC_SPIRITS) + * 01e1 <id>.L <amount>.W (ZC_SPIRITS2) + *------------------------------------------*/ +void clif_spiritball(struct block_list *bl) { + unsigned char buf[16]; + TBL_PC *sd = BL_CAST(BL_PC,bl); + TBL_HOM *hd = BL_CAST(BL_HOM,bl); + + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x1d0; + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = 0; //init to 0 + switch(bl->type){ + case BL_PC: WBUFW(buf, 6) = sd->spiritball; break; + case BL_HOM: WBUFW(buf, 6) = hd->homunculus.spiritball; break; + } + clif_send(buf, packet_len(0x1d0), bl, AREA); +} + + +/// Notifies clients in area of a character's combo delay (ZC_COMBODELAY). +/// 01d2 <account id>.L <delay>.L +void clif_combo_delay(struct block_list *bl,int wait) +{ + unsigned char buf[32]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x1d2; + WBUFL(buf,2)=bl->id; + WBUFL(buf,6)=wait; + clif_send(buf,packet_len(0x1d2),bl,AREA); +} + + +/// Notifies clients in area that a character has blade-stopped another (ZC_BLADESTOP). +/// 01d1 <src id>.L <dst id>.L <flag>.L +/// flag: +/// 0 = inactive +/// 1 = active +void clif_bladestop(struct block_list *src, int dst_id, int active) +{ + unsigned char buf[32]; + + nullpo_retv(src); + + WBUFW(buf,0)=0x1d1; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst_id; + WBUFL(buf,10)=active; + + clif_send(buf,packet_len(0x1d1),src,AREA); +} + + +/// MVP effect (ZC_MVP). +/// 010c <account id>.L +void clif_mvp_effect(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x10c; + WBUFL(buf,2)=sd->bl.id; + clif_send(buf,packet_len(0x10c),&sd->bl,AREA); +} + + +/// MVP item reward message (ZC_MVP_GETTING_ITEM). +/// 010a <name id>.W +void clif_mvp_item(struct map_session_data *sd,int nameid) +{ + int view,fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10a)); + WFIFOW(fd,0)=0x10a; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd,2)=view; + else + WFIFOW(fd,2)=nameid; + WFIFOSET(fd,packet_len(0x10a)); +} + + +/// MVP EXP reward message (ZC_MVP_GETTING_SPECIAL_EXP). +/// 010b <exp>.L +void clif_mvp_exp(struct map_session_data *sd, unsigned int exp) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10b)); + WFIFOW(fd,0)=0x10b; + WFIFOL(fd,2)=cap_value(exp,0,INT32_MAX); + WFIFOSET(fd,packet_len(0x10b)); +} + + +/// Dropped MVP item reward message (ZC_THROW_MVPITEM). +/// 010d +/// +/// "You are the MVP, but cannot obtain the reward because +/// you are overweight." +void clif_mvp_noitem(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x10d)); + WFIFOW(fd,0) = 0x10d; + WFIFOSET(fd,packet_len(0x10d)); +} + + +/// Guild creation result (ZC_RESULT_MAKE_GUILD). +/// 0167 <result>.B +/// result: +/// 0 = "Guild has been created." +/// 1 = "You are already in a Guild." +/// 2 = "That Guild Name already exists." +/// 3 = "You need the neccessary item to create a Guild." +void clif_guild_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x167)); + WFIFOW(fd,0)=0x167; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x167)); +} + + +/// Notifies the client that it is belonging to a guild (ZC_UPDATE_GDID). +/// 016c <guild id>.L <emblem id>.L <mode>.L <ismaster>.B <inter sid>.L <guild name>.24B +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +void clif_guild_belonginfo(struct map_session_data *sd, struct guild *g) +{ + int ps,fd; + nullpo_retv(sd); + nullpo_retv(g); + + fd=sd->fd; + ps=guild_getposition(g,sd); + WFIFOHEAD(fd,packet_len(0x16c)); + WFIFOW(fd,0)=0x16c; + WFIFOL(fd,2)=g->guild_id; + WFIFOL(fd,6)=g->emblem_id; + WFIFOL(fd,10)=g->position[ps].mode; + WFIFOB(fd,14)=(bool)(sd->state.gmaster_flag==g); + WFIFOL(fd,15)=0; // InterSID (unknown purpose) + memcpy(WFIFOP(fd,19),g->name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x16c)); +} + + +/// Guild member login notice. +/// 016d <account id>.L <char id>.L <status>.L (ZC_UPDATE_CHARSTAT) +/// 01f2 <account id>.L <char id>.L <status>.L <gender>.W <hair style>.W <hair color>.W (ZC_UPDATE_CHARSTAT2) +/// status: +/// 0 = offline +/// 1 = online +void clif_guild_memberlogin_notice(struct guild *g,int idx,int flag) +{ + unsigned char buf[64]; + struct map_session_data* sd; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x1f2; + WBUFL(buf, 2)=g->member[idx].account_id; + WBUFL(buf, 6)=g->member[idx].char_id; + WBUFL(buf,10)=flag; + + if( ( sd = g->member[idx].sd ) != NULL ) + { + WBUFW(buf,14) = sd->status.sex; + WBUFW(buf,16) = sd->status.hair; + WBUFW(buf,18) = sd->status.hair_color; + clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD_WOS); + } + else if( ( sd = guild_getavailablesd(g) ) != NULL ) + { + WBUFW(buf,14) = 0; + WBUFW(buf,16) = 0; + WBUFW(buf,18) = 0; + clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD); + } +} + +// Function `clif_guild_memberlogin_notice` sends info about +// logins and logouts of a guild member to the rest members. +// But at the 1st time (after a player login or map changing) +// the client won't show the message. +// So I suggest use this function for sending "first-time-info" +// to some player on entering the game or changing location. +// At next time the client would always show the message. +// The function sends all the statuses in the single packet +// to economize traffic. [LuzZza] +void clif_guild_send_onlineinfo(struct map_session_data *sd) +{ + struct guild *g; + unsigned char buf[14*128]; + int i, count=0, p_len; + + nullpo_retv(sd); + + p_len = packet_len(0x16d); + + if(!(g = guild_search(sd->status.guild_id))) + return; + + for(i=0; i<g->max_member; i++) { + + if(g->member[i].account_id > 0 && + g->member[i].account_id != sd->status.account_id) { + + WBUFW(buf,count*p_len) = 0x16d; + WBUFL(buf,count*p_len+2) = g->member[i].account_id; + WBUFL(buf,count*p_len+6) = g->member[i].char_id; + WBUFL(buf,count*p_len+10) = g->member[i].online; + count++; + } + } + + clif_send(buf, p_len*count, &sd->bl, SELF); +} + + +/// Bitmask of enabled guild window tabs (ZC_ACK_GUILD_MENUINTERFACE). +/// 014e <menu flag>.L +/// menu flag: +/// 0x00 = Basic Info (always on) +/// &0x01 = Member manager +/// &0x02 = Positions +/// &0x04 = Skills +/// &0x10 = Expulsion list +/// &0x40 = Unknown (GMENUFLAG_ALLGUILDLIST) +/// &0x80 = Notice +void clif_guild_masterormember(struct map_session_data *sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x14e)); + WFIFOW(fd,0) = 0x14e; + WFIFOL(fd,2) = (sd->state.gmaster_flag) ? 0xd7 : 0x57; + WFIFOSET(fd,packet_len(0x14e)); +} + + +/// Guild basic information (Territories [Valaris]) +/// 0150 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B (ZC_GUILD_INFO) +/// 01b6 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B <zeny>.L (ZC_GUILD_INFO2) +void clif_guild_basicinfo(struct map_session_data *sd) { + int fd; + struct guild *g; + + nullpo_retv(sd); + fd = sd->fd; + + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + WFIFOHEAD(fd,packet_len(0x1b6)); + WFIFOW(fd, 0)=0x1b6;//0x150; + WFIFOL(fd, 2)=g->guild_id; + WFIFOL(fd, 6)=g->guild_lv; + WFIFOL(fd,10)=g->connect_member; + WFIFOL(fd,14)=g->max_member; + WFIFOL(fd,18)=g->average_lv; + WFIFOL(fd,22)=(uint32)cap_value(g->exp,0,INT32_MAX); + WFIFOL(fd,26)=g->next_exp; + WFIFOL(fd,30)=0; // Tax Points + WFIFOL(fd,34)=0; // Honor: (left) Vulgar [-100,100] Famed (right) + WFIFOL(fd,38)=0; // Virtue: (down) Wicked [-100,100] Righteous (up) + WFIFOL(fd,42)=g->emblem_id; + memcpy(WFIFOP(fd,46),g->name, NAME_LENGTH); + memcpy(WFIFOP(fd,70),g->master, NAME_LENGTH); + + safestrncpy((char*)WFIFOP(fd,94),msg_txt(300+guild_checkcastles(g)),16); // "'N' castles" + WFIFOL(fd,110) = 0; // zeny + + WFIFOSET(fd,packet_len(0x1b6)); +} + + +/// Guild alliance and opposition list (ZC_MYGUILD_BASIC_INFO). +/// 014c <packet len>.W { <relation>.L <guild id>.L <guild name>.24B }* +void clif_guild_allianceinfo(struct map_session_data *sd) +{ + int fd,i,c; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDALLIANCE * 32 + 4); + WFIFOW(fd, 0)=0x14c; + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0){ + WFIFOL(fd,c*32+4)=a->opposition; + WFIFOL(fd,c*32+8)=a->guild_id; + memcpy(WFIFOP(fd,c*32+12),a->name,NAME_LENGTH); + c++; + } + } + WFIFOW(fd, 2)=c*32+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild member manager information (ZC_MEMBERMGR_INFO). +/// 0154 <packet len>.W { <account>.L <char id>.L <hair style>.W <hair color>.W <gender>.W <class>.W <level>.W <contrib exp>.L <state>.L <position>.L <memo>.50B <name>.24B }* +/// state: +/// 0 = offline +/// 1 = online +/// memo: +/// probably member's self-introduction (unused, no client UI/packets for editing it) +void clif_guild_memberlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + nullpo_retv(sd); + + if( (fd = sd->fd) == 0 ) + return; + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + WFIFOHEAD(fd, g->max_member * 104 + 4); + WFIFOW(fd, 0)=0x154; + for(i=0,c=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(m->account_id==0) + continue; + WFIFOL(fd,c*104+ 4)=m->account_id; + WFIFOL(fd,c*104+ 8)=m->char_id; + WFIFOW(fd,c*104+12)=m->hair; + WFIFOW(fd,c*104+14)=m->hair_color; + WFIFOW(fd,c*104+16)=m->gender; + WFIFOW(fd,c*104+18)=m->class_; + WFIFOW(fd,c*104+20)=m->lv; + WFIFOL(fd,c*104+22)=(int)cap_value(m->exp,0,INT32_MAX); + WFIFOL(fd,c*104+26)=m->online; + WFIFOL(fd,c*104+30)=m->position; + memset(WFIFOP(fd,c*104+34),0,50); //[Ind] - This is displayed in the 'note' column but being you can't edit it it's sent empty. + memcpy(WFIFOP(fd,c*104+84),m->name,NAME_LENGTH); + c++; + } + WFIFOW(fd, 2)=c*104+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild position name information (ZC_POSITION_ID_NAME_INFO). +/// 0166 <packet len>.W { <position id>.L <position name>.24B }* +void clif_guild_positionnamelist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDPOSITION * 28 + 4); + WFIFOW(fd, 0)=0x166; + for(i=0;i<MAX_GUILDPOSITION;i++){ + WFIFOL(fd,i*28+4)=i; + memcpy(WFIFOP(fd,i*28+8),g->position[i].name,NAME_LENGTH); + } + WFIFOW(fd,2)=i*28+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild position information (ZC_POSITION_INFO). +/// 0160 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L }* +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +/// ranking: +/// TODO +void clif_guild_positioninfolist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDPOSITION * 16 + 4); + WFIFOW(fd, 0)=0x160; + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p=&g->position[i]; + WFIFOL(fd,i*16+ 4)=i; + WFIFOL(fd,i*16+ 8)=p->mode; + WFIFOL(fd,i*16+12)=i; + WFIFOL(fd,i*16+16)=p->exp_mode; + } + WFIFOW(fd, 2)=i*16+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies clients in a guild about updated position information (ZC_ACK_CHANGE_GUILD_POSITIONINFO). +/// 0174 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <position name>.24B }* +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +/// ranking: +/// TODO +void clif_guild_positionchanged(struct guild *g,int idx) +{ + // FIXME: This packet is intended to update the clients after a + // commit of position info changes, not sending one packet per + // position. + struct map_session_data *sd; + unsigned char buf[128]; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x174; + WBUFW(buf, 2)=44; // packet len + // GUILD_REG_POSITION_INFO{ + WBUFL(buf, 4)=idx; + WBUFL(buf, 8)=g->position[idx].mode; + WBUFL(buf,12)=idx; + WBUFL(buf,16)=g->position[idx].exp_mode; + memcpy(WBUFP(buf,20),g->position[idx].name,NAME_LENGTH); + // }* + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); +} + + +/// Notifies clients in a guild about updated member position assignments (ZC_ACK_REQ_CHANGE_MEMBERS). +/// 0156 <packet len>.W { <account id>.L <char id>.L <position id>.L }* +void clif_guild_memberpositionchanged(struct guild *g,int idx) +{ + // FIXME: This packet is intended to update the clients after a + // commit of member position assignment changes, not sending one + // packet per position. + struct map_session_data *sd; + unsigned char buf[64]; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x156; + WBUFW(buf, 2)=16; // packet len + // MEMBER_POSITION_INFO{ + WBUFL(buf, 4)=g->member[idx].account_id; + WBUFL(buf, 8)=g->member[idx].char_id; + WBUFL(buf,12)=g->member[idx].position; + // }* + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); +} + + +/// Sends emblems bitmap data to the client that requested it (ZC_GUILD_EMBLEM_IMG). +/// 0152 <packet len>.W <guild id>.L <emblem id>.L <emblem data>.?B +void clif_guild_emblem(struct map_session_data *sd,struct guild *g) +{ + int fd; + nullpo_retv(sd); + nullpo_retv(g); + + fd = sd->fd; + if( g->emblem_len <= 0 ) + return; + + WFIFOHEAD(fd,g->emblem_len+12); + WFIFOW(fd,0)=0x152; + WFIFOW(fd,2)=g->emblem_len+12; + WFIFOL(fd,4)=g->guild_id; + WFIFOL(fd,8)=g->emblem_id; + memcpy(WFIFOP(fd,12),g->emblem_data,g->emblem_len); + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Sends update of the guild id/emblem id to everyone in the area (ZC_CHANGE_GUILD). +/// 01b4 <id>.L <guild id>.L <emblem id>.W +void clif_guild_emblem_area(struct block_list* bl) +{ + uint8 buf[12]; + + nullpo_retv(bl); + + // TODO this packet doesn't force the update of ui components that have the emblem visible + // (emblem in the flag npcs and emblem over the head in agit maps) [FlavioJS] + WBUFW(buf,0) = 0x1b4; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = status_get_guild_id(bl); + WBUFW(buf,10) = status_get_emblem_id(bl); + clif_send(buf, 12, bl, AREA_WOS); +} + + +/// Sends guild skills (ZC_GUILD_SKILLINFO). +/// 0162 <packet len>.W <skill points>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B }* +void clif_guild_skillinfo(struct map_session_data* sd) +{ + int fd; + struct guild* g; + int i,c; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, 6 + MAX_GUILDSKILL*37); + WFIFOW(fd,0) = 0x0162; + WFIFOW(fd,4) = g->skill_point; + for(i = 0, c = 0; i < MAX_GUILDSKILL; i++) + { + if(g->skill[i].id > 0 && guild_check_skill_require(g, g->skill[i].id)) + { + int id = g->skill[i].id; + int p = 6 + c*37; + WFIFOW(fd,p+0) = id; + WFIFOL(fd,p+2) = skill_get_inf(id); + WFIFOW(fd,p+6) = g->skill[i].lv; + WFIFOW(fd,p+8) = skill_get_sp(id, g->skill[i].lv); + WFIFOW(fd,p+10) = skill_get_range(id, g->skill[i].lv); + safestrncpy((char*)WFIFOP(fd,p+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,p+36)= (g->skill[i].lv < guild_skill_get_max(id) && sd == g->member[0].sd) ? 1 : 0; + c++; + } + } + WFIFOW(fd,2) = 6 + c*37; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Sends guild notice to client (ZC_GUILD_NOTICE). +/// 016f <subject>.60B <notice>.120B +void clif_guild_notice(struct map_session_data* sd, struct guild* g) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(g); + + fd = sd->fd; + + if ( !session_isActive(fd) ) + return; + + if(g->mes1[0] == '\0' && g->mes2[0] == '\0') + return; + + WFIFOHEAD(fd,packet_len(0x16f)); + WFIFOW(fd,0) = 0x16f; + memcpy(WFIFOP(fd,2), g->mes1, MAX_GUILDMES1); + memcpy(WFIFOP(fd,62), g->mes2, MAX_GUILDMES2); + WFIFOSET(fd,packet_len(0x16f)); +} + + +/// Guild invite (ZC_REQ_JOIN_GUILD). +/// 016a <guild id>.L <guild name>.24B +void clif_guild_invite(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(g); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x16a)); + WFIFOW(fd,0)=0x16a; + WFIFOL(fd,2)=g->guild_id; + memcpy(WFIFOP(fd,6),g->name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x16a)); +} + + +/// Reply to invite request (ZC_ACK_REQ_JOIN_GUILD). +/// 0169 <answer>.B +/// answer: +/// 0 = Already in guild. +/// 1 = Offer rejected. +/// 2 = Offer accepted. +/// 3 = Guild full. +void clif_guild_inviteack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x169)); + WFIFOW(fd,0)=0x169; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x169)); +} + + +/// Notifies clients of a guild of a leaving member (ZC_ACK_LEAVE_GUILD). +/// 015a <char name>.24B <reason>.40B +void clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes) +{ + unsigned char buf[128]; + + nullpo_retv(sd); + + WBUFW(buf, 0)=0x15a; + memcpy(WBUFP(buf, 2),name,NAME_LENGTH); + memcpy(WBUFP(buf,26),mes,40); + clif_send(buf,packet_len(0x15a),&sd->bl,GUILD_NOBG); +} + + +/// Notifies clients of a guild of an expelled member. +/// 015c <char name>.24B <reason>.40B <account name>.24B (ZC_ACK_BAN_GUILD) +/// 0839 <char name>.24B <reason>.40B (ZC_ACK_BAN_GUILD_SSO) +void clif_guild_expulsion(struct map_session_data* sd, const char* name, const char* mes, int account_id) +{ + unsigned char buf[128]; +#if PACKETVER < 20100803 + const unsigned short cmd = 0x15c; +#else + const unsigned short cmd = 0x839; +#endif + + nullpo_retv(sd); + + WBUFW(buf,0) = cmd; + safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); + safestrncpy((char*)WBUFP(buf,26), mes, 40); +#if PACKETVER < 20100803 + memset(WBUFP(buf,66), 0, NAME_LENGTH); // account name (not used for security reasons) +#endif + clif_send(buf, packet_len(cmd), &sd->bl, GUILD_NOBG); +} + + +/// Guild expulsion list (ZC_BAN_LIST). +/// 0163 <packet len>.W { <char name>.24B <account name>.24B <reason>.40B }* +/// 0163 <packet len>.W { <char name>.24B <reason>.40B }* (PACKETVER >= 20100803) +void clif_guild_expulsionlist(struct map_session_data* sd) +{ +#if PACKETVER < 20100803 + const int offset = NAME_LENGTH*2+40; +#else + const int offset = NAME_LENGTH+40; +#endif + int fd, i, c = 0; + struct guild* g; + + nullpo_retv(sd); + + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + + WFIFOHEAD(fd,4 + MAX_GUILDEXPULSION * offset); + WFIFOW(fd,0) = 0x163; + + for( i = 0; i < MAX_GUILDEXPULSION; i++ ) + { + struct guild_expulsion* e = &g->expulsion[i]; + + if( e->account_id > 0 ) + { + memcpy(WFIFOP(fd,4 + c*offset), e->name, NAME_LENGTH); +#if PACKETVER < 20100803 + memset(WFIFOP(fd,4 + c*offset+24), 0, NAME_LENGTH); // account name (not used for security reasons) + memcpy(WFIFOP(fd,4 + c*offset+48), e->mes, 40); +#else + memcpy(WFIFOP(fd,4 + c*offset+24), e->mes, 40); +#endif + c++; + } + } + WFIFOW(fd,2) = 4 + c*offset; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild chat message (ZC_GUILD_CHAT). +/// 017f <packet len>.W <message>.?B +void clif_guild_message(struct guild *g,int account_id,const char *mes,int len) +{// TODO: account_id is not used, candidate for deletion? [Ai4rei] + struct map_session_data *sd; + uint8 buf[256]; + + if( len == 0 ) + { + return; + } + else if( len > sizeof(buf)-5 ) + { + ShowWarning("clif_guild_message: Truncated message '%s' (len=%d, max=%d, guild_id=%d).\n", mes, len, sizeof(buf)-5, g->guild_id); + len = sizeof(buf)-5; + } + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 5; + safestrncpy((char*)WBUFP(buf,4), mes, len+1); + + if ((sd = guild_getavailablesd(g)) != NULL) + clif_send(buf, WBUFW(buf,2), &sd->bl, GUILD_NOBG); +} + + +/*========================================== + * Server tells client 'sd' that his guild skill 'skill_id' gone to level 'lv' + *------------------------------------------*/ +int clif_guild_skillup(struct map_session_data *sd,uint16 skill_id,int lv) +{// TODO: Merge with clif_skillup (same packet). + int fd; + + nullpo_ret(sd); + + fd=sd->fd; + WFIFOHEAD(fd,11); + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,lv); + WFIFOW(fd,8) = skill_get_range(skill_id,lv); + WFIFOB(fd,10) = 1; + WFIFOSET(fd,11); + return 0; +} + + +/// Request for guild alliance (ZC_REQ_ALLY_GUILD). +/// 0171 <inviter account id>.L <guild name>.24B +void clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x171)); + WFIFOW(fd,0)=0x171; + WFIFOL(fd,2)=account_id; + memcpy(WFIFOP(fd,6),name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x171)); +} + + +/// Notifies the client about the result of a alliance request (ZC_ACK_REQ_ALLY_GUILD). +/// 0173 <answer>.B +/// answer: +/// 0 = Already allied. +/// 1 = You rejected the offer. +/// 2 = You accepted the offer. +/// 3 = They have too any alliances. +/// 4 = You have too many alliances. +/// 5 = Alliances are disabled. +void clif_guild_allianceack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x173)); + WFIFOW(fd,0)=0x173; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len(0x173)); +} + + +/// Notifies the client that a alliance or opposition has been removed (ZC_DELETE_RELATED_GUILD). +/// 0184 <other guild id>.L <relation>.L +/// relation: +/// 0 = Ally +/// 1 = Enemy +void clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + if (fd <= 0) + return; + WFIFOHEAD(fd,packet_len(0x184)); + WFIFOW(fd,0)=0x184; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=flag; + WFIFOSET(fd,packet_len(0x184)); +} + + +/// Notifies the client about the result of a opposition request (ZC_ACK_REQ_HOSTILE_GUILD). +/// 0181 <result>.B +/// result: +/// 0 = Antagonist has been set. +/// 1 = Guild has too many Antagonists. +/// 2 = Already set as an Antagonist. +/// 3 = Antagonists are disabled. +void clif_guild_oppositionack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x181)); + WFIFOW(fd,0)=0x181; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x181)); +} + + +/// Adds alliance or opposition (ZC_ADD_RELATED_GUILD). +/// 0185 <relation>.L <guild id>.L <guild name>.24B +/* +void clif_guild_allianceadded(struct guild *g,int idx) +{ + unsigned char buf[64]; + WBUFW(buf,0)=0x185; + WBUFL(buf,2)=g->alliance[idx].opposition; + WBUFL(buf,6)=g->alliance[idx].guild_id; + memcpy(WBUFP(buf,10),g->alliance[idx].name,NAME_LENGTH); + clif_send(buf,packet_len(0x185),guild_getavailablesd(g),GUILD); +} +*/ + + +/// Notifies the client about the result of a guild break (ZC_ACK_DISORGANIZE_GUILD_RESULT). +/// 015e <reason>.L +/// 0 = success +/// 1 = invalid key (guild name, @see clif_parse_GuildBreak) +/// 2 = there are still members in the guild +void clif_guild_broken(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x15e)); + WFIFOW(fd,0)=0x15e; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len(0x15e)); +} + + +/// Displays emotion on an object (ZC_EMOTION). +/// 00c0 <id>.L <type>.B +/// type: +/// enum emotion_type +void clif_emotion(struct block_list *bl,int type) +{ + unsigned char buf[8]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0xc0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + clif_send(buf,packet_len(0xc0),bl,AREA); +} + + +/// Displays the contents of a talkiebox trap (ZC_TALKBOX_CHATCONTENTS). +/// 0191 <id>.L <contents>.80B +void clif_talkiebox(struct block_list* bl, const char* talkie) +{ + unsigned char buf[MESSAGE_SIZE+6]; + nullpo_retv(bl); + + WBUFW(buf,0) = 0x191; + WBUFL(buf,2) = bl->id; + safestrncpy((char*)WBUFP(buf,6),talkie,MESSAGE_SIZE); + clif_send(buf,packet_len(0x191),bl,AREA); +} + + +/// Displays wedding effect centered on an object (ZC_CONGRATULATION). +/// 01ea <id>.L +void clif_wedding_effect(struct block_list *bl) +{ + unsigned char buf[6]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1ea; + WBUFL(buf,2) = bl->id; + clif_send(buf, packet_len(0x1ea), bl, AREA); +} + + +/// Notifies the client of the name of the partner character (ZC_COUPLENAME). +/// 01e6 <partner name>.24B +void clif_callpartner(struct map_session_data *sd) +{ + unsigned char buf[26]; + const char *p; + + nullpo_retv(sd); + + WBUFW(buf,0) = 0x1e6; + + if( sd->status.partner_id ) + { + if( ( p = map_charid2nick(sd->status.partner_id) ) != NULL ) + { + memcpy(WBUFP(buf,2), p, NAME_LENGTH); + } + else + { + WBUFB(buf,2) = 0; + } + } + else + {// Send zero-length name if no partner, to initialize the client buffer. + WBUFB(buf,2) = 0; + } + + clif_send(buf, packet_len(0x1e6), &sd->bl, AREA); +} + + +/// Initiates the partner "taming" process [DracoRPG] (ZC_START_COUPLE). +/// 01e4 +/// This packet while still implemented by the client is no longer being officially used. +/* +void clif_marriage_process(struct map_session_data *sd) +{ + int fd; + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1e4)); + WFIFOW(fd,0)=0x1e4; + WFIFOSET(fd,packet_len(0x1e4)); +} +*/ + + +/// Notice of divorce (ZC_DIVORCE). +/// 0205 <partner name>.24B +void clif_divorced(struct map_session_data* sd, const char* name) +{ + int fd; + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x205)); + WFIFOW(fd,0)=0x205; + memcpy(WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x205)); +} + + +/// Marriage proposal (ZC_REQ_COUPLE). +/// 01e2 <account id>.L <char id>.L <char name>.24B +/// This packet while still implemented by the client is no longer being officially used. +/* +void clif_marriage_proposal(int fd, struct map_session_data *sd, struct map_session_data* ssd) +{ + nullpo_retv(sd); + + WFIFOHEAD(fd,packet_len(0x1e2)); + WFIFOW(fd,0) = 0x1e2; + WFIFOL(fd,2) = ssd->status.account_id; + WFIFOL(fd,6) = ssd->status.char_id; + safestrncpy((char*)WFIFOP(fd,10), ssd->status.name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x1e2)); +} +*/ + + +/*========================================== + * + *------------------------------------------*/ +void clif_disp_onlyself(struct map_session_data *sd, const char *mes, int len) +{ + clif_disp_message(&sd->bl, mes, len, SELF); +} + +/*========================================== + * Displays a message using the guild-chat colors to the specified targets. [Skotlex] + *------------------------------------------*/ +void clif_disp_message(struct block_list* src, const char* mes, int len, enum send_target target) +{ + unsigned char buf[256]; + + if( len == 0 ) + { + return; + } + else if( len > sizeof(buf)-5 ) + { + ShowWarning("clif_disp_message: Truncated message '%s' (len=%d, max=%d, aid=%d).\n", mes, len, sizeof(buf)-5, src->id); + len = sizeof(buf)-5; + } + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 5; + safestrncpy((char*)WBUFP(buf,4), mes, len+1); + clif_send(buf, WBUFW(buf,2), src, target); +} + + +/// Notifies the client about the result of a request to disconnect another player (ZC_ACK_DISCONNECT_CHARACTER). +/// 00cd <result>.L (unknown packet version or invalid information at packet_len_table) +/// 00cd <result>.B +/// result: +/// 0 = failure +/// 1 = success +void clif_GM_kickack(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xcd)); + WFIFOW(fd,0) = 0xcd; + WFIFOB(fd,2) = id; // FIXME: this is not account id + WFIFOSET(fd, packet_len(0xcd)); +} + + +void clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd) +{ + int fd = tsd->fd; + + if( fd > 0 ) + clif_authfail_fd(fd, 15); + else + map_quit(tsd); + + if( sd ) + clif_GM_kickack(sd,tsd->status.account_id); +} + + +/// Displays various manner-related status messages (ZC_ACK_GIVE_MANNER_POINT). +/// 014a <result>.L +/// result: +/// 0 = "A manner point has been successfully aligned." +/// 1 = MP_FAILURE_EXHAUST +/// 2 = MP_FAILURE_ALREADY_GIVING +/// 3 = "Chat Block has been applied by GM due to your ill-mannerous action." +/// 4 = "Automated Chat Block has been applied due to Anti-Spam System." +/// 5 = "You got a good point from %s." +void clif_manner_message(struct map_session_data* sd, uint32 type) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x14a)); + WFIFOW(fd,0) = 0x14a; + WFIFOL(fd,2) = type; + WFIFOSET(fd, packet_len(0x14a)); +} + + +/// Followup to 0x14a type 3/5, informs who did the manner adjustment action (ZC_NOTIFY_MANNER_POINT_GIVEN). +/// 014b <type>.B <GM name>.24B +/// type: +/// 0 = positive (unmute) +/// 1 = negative (mute) +void clif_GM_silence(struct map_session_data* sd, struct map_session_data* tsd, uint8 type) +{ + int fd; + nullpo_retv(sd); + nullpo_retv(tsd); + + fd = tsd->fd; + WFIFOHEAD(fd,packet_len(0x14b)); + WFIFOW(fd,0) = 0x14b; + WFIFOB(fd,2) = type; + safestrncpy((char*)WFIFOP(fd,3), sd->status.name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x14b)); +} + + +/// Notifies the client about the result of a request to allow/deny whispers from a player (ZC_SETTING_WHISPER_PC). +/// 00d1 <type>.B <result>.B +/// type: +/// 0 = /ex (deny) +/// 1 = /in (allow) +/// result: +/// 0 = success +/// 1 = failure +/// 2 = too many blocks +void clif_wisexin(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xd1)); + WFIFOW(fd,0)=0xd1; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len(0xd1)); +} + +/// Notifies the client about the result of a request to allow/deny whispers from anyone (ZC_SETTING_WHISPER_STATE). +/// 00d2 <type>.B <result>.B +/// type: +/// 0 = /exall (deny) +/// 1 = /inall (allow) +/// result: +/// 0 = success +/// 1 = failure +void clif_wisall(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xd2)); + WFIFOW(fd,0)=0xd2; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len(0xd2)); +} + + +/// Play a BGM! [Rikter/Yommy] (ZC_PLAY_NPC_BGM). +/// 07fe <bgm>.24B +void clif_playBGM(struct map_session_data* sd, const char* name) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x7fe)); + WFIFOW(fd,0) = 0x7fe; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x7fe)); +} + + +/// Plays/stops a wave sound (ZC_SOUND). +/// 01d3 <file name>.24B <act>.B <term>.L <npc id>.L +/// file name: +/// relative to data\wav +/// act: +/// 0 = play (once) +/// 1 = play (repeat, does not work) +/// 2 = stops all sound instances of file name (does not work) +/// term: +/// unknown purpose, only relevant to act = 1 +/// npc id: +/// The accustic direction of the sound is determined by the +/// relative position of the NPC to the player (3D sound). +void clif_soundeffect(struct map_session_data* sd, struct block_list* bl, const char* name, int type) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x1d3)); + WFIFOW(fd,0) = 0x1d3; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOB(fd,26) = type; + WFIFOL(fd,27) = 0; + WFIFOL(fd,31) = bl->id; + WFIFOSET(fd,packet_len(0x1d3)); +} + +void clif_soundeffectall(struct block_list* bl, const char* name, int type, enum send_target coverage) +{ + unsigned char buf[40]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1d3; + safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); + WBUFB(buf,26) = type; + WBUFL(buf,27) = 0; + WBUFL(buf,31) = bl->id; + clif_send(buf, packet_len(0x1d3), bl, coverage); +} + + +/// Displays special effects (npcs, weather, etc) [Valaris] (ZC_NOTIFY_EFFECT2). +/// 01f3 <id>.L <effect id>.L +/// effect id: +/// @see doc/effect_list.txt +void clif_specialeffect(struct block_list* bl, int type, enum send_target target) +{ + unsigned char buf[24]; + + nullpo_retv(bl); + + memset(buf, 0, packet_len(0x1f3)); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf, packet_len(0x1f3), bl, target); + + if (disguised(bl)) { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x1f3), bl, SELF); + } +} + +void clif_specialeffect_single(struct block_list* bl, int type, int fd) +{ + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x1f3; + WFIFOL(fd,2) = bl->id; + WFIFOL(fd,6) = type; + WFIFOSET(fd,10); +} + + +/// Notifies clients of an special/visual effect that accepts an value (ZC_NOTIFY_EFFECT3). +/// 0284 <id>.L <effect id>.L <num data>.L +/// effect id: +/// @see doc/effect_list.txt +/// num data: +/// effect-dependent value +void clif_specialeffect_value(struct block_list* bl, int effect_id, int num, send_target target) +{ + uint8 buf[14]; + + WBUFW(buf,0) = 0x284; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = effect_id; + WBUFL(buf,10) = num; + + clif_send(buf, packet_len(0x284), bl, target); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x284), bl, SELF); + } +} +// Modification of clif_messagecolor to send colored messages to players to chat log only (doesn't display overhead) +/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B +int clif_colormes(struct map_session_data * sd, enum clif_colors color, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + + WFIFOHEAD(sd->fd,msg_len + 12); + WFIFOW(sd->fd,0) = 0x2C1; + WFIFOW(sd->fd,2) = msg_len + 12; + WFIFOL(sd->fd,4) = 0; + WFIFOL(sd->fd,8) = color_table[color]; + safestrncpy((char*)WFIFOP(sd->fd,12), msg, msg_len); + clif_send(WFIFOP(sd->fd,0), WFIFOW(sd->fd,2), &sd->bl, SELF); + + return 0; +} + +/// Monster/NPC color chat [SnakeDrak] (ZC_NPC_CHAT). +/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B +void clif_messagecolor(struct block_list* bl, unsigned long color, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + uint8 buf[256]; + color = (color & 0x0000FF) << 16 | (color & 0x00FF00) | (color & 0xFF0000) >> 16; // RGB to BGR + + nullpo_retv(bl); + + if( msg_len > sizeof(buf)-12 ) + { + ShowWarning("clif_messagecolor: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-12; + } + + WBUFW(buf,0) = 0x2C1; + WBUFW(buf,2) = msg_len + 12; + WBUFL(buf,4) = bl->id; + WBUFL(buf,8) = color; + memcpy(WBUFP(buf,12), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); +} + +/// Public chat message [Valaris] (ZC_NOTIFY_CHAT). +/// 008d <packet len>.W <id>.L <message>.?B +void clif_message(struct block_list* bl, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + uint8 buf[256]; + nullpo_retv(bl); + + if( msg_len > sizeof(buf)-8 ) { + ShowWarning("clif_message: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-8; + } + + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = msg_len + 8; + WBUFL(buf,4) = bl->id; + safestrncpy((char*)WBUFP(buf,8), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); +} + +// refresh the client's screen, getting rid of any effects +void clif_refresh(struct map_session_data *sd) +{ + int i; + nullpo_retv(sd); + + clif_changemap(sd,sd->mapindex,sd->bl.x,sd->bl.y); + clif_inventorylist(sd); + if(pc_iscarton(sd)) { + clif_cartlist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + clif_updatestatus(sd,SP_WEIGHT); + clif_updatestatus(sd,SP_MAXWEIGHT); + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + if (sd->spiritball) + clif_spiritball_single(sd->fd, sd); + for(i = 1; i < 5; i++){ + if( sd->talisman[i] > 0 ) + clif_talisman_single(sd->fd, sd, i); + } + if (sd->vd.cloth_color) + clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); + if(merc_is_hom_active(sd->hd)) + clif_send_homdata(sd,SP_ACK,0); + if( sd->md ) { + clif_mercenary_info(sd); + clif_mercenary_skillblock(sd); + } + if( sd->ed ) + clif_elemental_info(sd); + map_foreachinrange(clif_getareachar,&sd->bl,AREA_SIZE,BL_ALL,sd); + clif_weather_check(sd); + if( sd->chatID ) + chat_leavechat(sd,0); + if( sd->state.vending ) + clif_openvending(sd, sd->bl.id, sd->vending); + if( pc_issit(sd) ) + clif_sitting(&sd->bl); // FIXME: just send to self, not area + if( pc_isdead(sd) ) // When you refresh, resend the death packet. + clif_clearunit_single(sd->bl.id,CLR_DEAD,sd->fd); + else + clif_changed_dir(&sd->bl, SELF); + + // unlike vending, resuming buyingstore crashes the client. + buyingstore_close(sd); + + mail_clear(sd); +} + + +/// Updates the object's (bl) name on client. +/// 0095 <id>.L <char name>.24B (ZC_ACK_REQNAME) +/// 0195 <id>.L <char name>.24B <party name>.24B <guild name>.24B <position name>.24B (ZC_ACK_REQNAMEALL) +void clif_charnameack (int fd, struct block_list *bl) +{ + unsigned char buf[103]; + int cmd = 0x95, i, ps = -1; + + nullpo_retv(bl); + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = bl->id; + + switch( bl->type ) + { + case BL_PC: + { + struct map_session_data *ssd = (struct map_session_data *)bl; + struct party_data *p = NULL; + struct guild *g = NULL; + + //Requesting your own "shadow" name. [Skotlex] + if (ssd->fd == fd && ssd->disguise) + WBUFL(buf,2) = -bl->id; + + if( ssd->fakename[0] ) + { + WBUFW(buf, 0) = cmd = 0x195; + memcpy(WBUFP(buf,6), ssd->fakename, NAME_LENGTH); + WBUFB(buf,30) = WBUFB(buf,54) = WBUFB(buf,78) = 0; + break; + } + memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); + + if( ssd->status.party_id ) + { + p = party_search(ssd->status.party_id); + } + if( ssd->status.guild_id ) + { + if( ( g = guild_search(ssd->status.guild_id) ) != NULL ) + { + ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); + if( i < g->max_member ) ps = g->member[i].position; + } + } + + if( !battle_config.display_party_name && g == NULL ) + {// do not display party unless the player is also in a guild + p = NULL; + } + + if (p == NULL && g == NULL) + break; + + WBUFW(buf, 0) = cmd = 0x195; + if (p) + memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); + else + WBUFB(buf,30) = 0; + + if (g && ps >= 0 && ps < MAX_GUILDPOSITION) + { + memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); + memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); + } else { //Assume no guild. + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + } + break; + //[blackhole89] + case BL_HOM: + memcpy(WBUFP(buf,6), ((TBL_HOM*)bl)->homunculus.name, NAME_LENGTH); + break; + case BL_MER: + memcpy(WBUFP(buf,6), ((TBL_MER*)bl)->db->name, NAME_LENGTH); + break; + case BL_PET: + memcpy(WBUFP(buf,6), ((TBL_PET*)bl)->pet.name, NAME_LENGTH); + break; + case BL_NPC: + memcpy(WBUFP(buf,6), ((TBL_NPC*)bl)->name, NAME_LENGTH); + break; + case BL_MOB: + { + struct mob_data *md = (struct mob_data *)bl; + nullpo_retv(md); + + memcpy(WBUFP(buf,6), md->name, NAME_LENGTH); + if( md->guardian_data && md->guardian_data->guild_id ) + { + WBUFW(buf, 0) = cmd = 0x195; + WBUFB(buf,30) = 0; + memcpy(WBUFP(buf,54), md->guardian_data->guild_name, NAME_LENGTH); + memcpy(WBUFP(buf,78), md->guardian_data->castle->castle_name, NAME_LENGTH); + } + else if( battle_config.show_mob_info ) + { + char mobhp[50], *str_p = mobhp; + WBUFW(buf, 0) = cmd = 0x195; + if( battle_config.show_mob_info&4 ) + str_p += sprintf(str_p, "Lv. %d | ", md->level); + if( battle_config.show_mob_info&1 ) + str_p += sprintf(str_p, "HP: %u/%u | ", md->status.hp, md->status.max_hp); + if( battle_config.show_mob_info&2 ) + str_p += sprintf(str_p, "HP: %d%% | ", get_percentage(md->status.hp, md->status.max_hp)); + //Even thought mobhp ain't a name, we send it as one so the client + //can parse it. [Skotlex] + if( str_p != mobhp ) + { + *(str_p-3) = '\0'; //Remove trailing space + pipe. + memcpy(WBUFP(buf,30), mobhp, NAME_LENGTH); + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + } + } + break; + case BL_CHAT: //FIXME: Clients DO request this... what should be done about it? The chat's title may not fit... [Skotlex] +// memcpy(WBUFP(buf,6), (struct chat*)->title, NAME_LENGTH); +// break; + return; + case BL_ELEM: + memcpy(WBUFP(buf,6), ((TBL_ELEM*)bl)->db->name, NAME_LENGTH); + break; + default: + ShowError("clif_charnameack: bad type %d(%d)\n", bl->type, bl->id); + return; + } + + // if no receipient specified just update nearby clients + if (fd == 0) + clif_send(buf, packet_len(cmd), bl, AREA); + else { + WFIFOHEAD(fd, packet_len(cmd)); + memcpy(WFIFOP(fd, 0), buf, packet_len(cmd)); + WFIFOSET(fd, packet_len(cmd)); + } +} + + +//Used to update when a char leaves a party/guild. [Skotlex] +//Needed because when you send a 0x95 packet, the client will not remove the cached party/guild info that is not sent. +void clif_charnameupdate (struct map_session_data *ssd) +{ + unsigned char buf[103]; + int cmd = 0x195, ps = -1, i; + struct party_data *p = NULL; + struct guild *g = NULL; + + nullpo_retv(ssd); + + if( ssd->fakename[0] ) + return; //No need to update as the party/guild was not displayed anyway. + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = ssd->bl.id; + + memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); + + if (!battle_config.display_party_name) { + if (ssd->status.party_id > 0 && ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL) + p = party_search(ssd->status.party_id); + }else{ + if (ssd->status.party_id > 0) + p = party_search(ssd->status.party_id); + } + + if( ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL ) + { + ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); + if( i < g->max_member ) ps = g->member[i].position; + } + + if( p ) + memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); + else + WBUFB(buf,30) = 0; + + if( g && ps >= 0 && ps < MAX_GUILDPOSITION ) + { + memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); + memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); + } + else + { + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + + // Update nearby clients + clif_send(buf, packet_len(cmd), &ssd->bl, AREA); +} + + +/// Taekwon Jump (TK_HIGHJUMP) effect (ZC_HIGHJUMP). +/// 01ff <id>.L <x>.W <y>.W +/// +/// Visually moves(instant) a character to x,y. The char moves even +/// when the target cell isn't walkable. If the char is sitting it +/// stays that way. +void clif_slide(struct block_list *bl, int x, int y) +{ + unsigned char buf[10]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x01ff; + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = x; + WBUFW(buf, 8) = y; + clif_send(buf, packet_len(0x1ff), bl, AREA); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x1ff), bl, SELF); + } +} + + +/*------------------------------------------ + * @me command by lordalfa, rewritten implementation by Skotlex + *------------------------------------------*/ +void clif_disp_overhead(struct map_session_data *sd, const char* mes) +{ + unsigned char buf[256]; //This should be more than sufficient, the theorical max is CHAT_SIZE + 8 (pads and extra inserted crap) + int len_mes = strlen(mes)+1; //Account for \0 + + if (len_mes > sizeof(buf)-8) { + ShowError("clif_disp_overhead: Message too long (length %d)\n", len_mes); + len_mes = sizeof(buf)-8; //Trunk it to avoid problems. + } + // send message to others + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = len_mes + 8; // len of message + 8 (command+len+id) + WBUFL(buf,4) = sd->bl.id; + safestrncpy((char*)WBUFP(buf,8), mes, len_mes); + clif_send(buf, WBUFW(buf,2), &sd->bl, AREA_CHAT_WOC); + + // send back message to the speaker + WBUFW(buf,0) = 0x8e; + WBUFW(buf, 2) = len_mes + 4; + safestrncpy((char*)WBUFP(buf,4), mes, len_mes); + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); +} + +/*========================== + * Minimap fix [Kevin] + * Remove dot from minimap + *--------------------------*/ +void clif_party_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[16]; + nullpo_retv(sd); + WBUFW(buf,0)=0x107; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=-1; + WBUFW(buf,8)=-1; + clif_send(buf,packet_len(0x107),&sd->bl,PARTY_SAMEMAP_WOS); +} + + +/// Displays a skill message (thanks to Rayce) (ZC_SKILLMSG). +/// 0215 <msg id>.L +/// msg id: +/// 0x15 = End all negative status (PA_GOSPEL) +/// 0x16 = Immunity to all status (PA_GOSPEL) +/// 0x17 = MaxHP +100% (PA_GOSPEL) +/// 0x18 = MaxSP +100% (PA_GOSPEL) +/// 0x19 = All stats +20 (PA_GOSPEL) +/// 0x1c = Enchant weapon with Holy element (PA_GOSPEL) +/// 0x1d = Enchant armor with Holy element (PA_GOSPEL) +/// 0x1e = DEF +25% (PA_GOSPEL) +/// 0x1f = ATK +100% (PA_GOSPEL) +/// 0x20 = HIT/Flee +50 (PA_GOSPEL) +/// 0x28 = Full strip failed because of coating (ST_FULLSTRIP) +/// ? = nothing +void clif_gospel_info(struct map_session_data *sd, int type) +{ + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x215)); + WFIFOW(fd,0)=0x215; + WFIFOL(fd,2)=type; + WFIFOSET(fd, packet_len(0x215)); + +} + + +/// Multi-purpose mission information packet (ZC_STARSKILL). +/// 020e <mapname>.24B <monster_id>.L <star>.B <result>.B +/// result: +/// 0 = Star Gladiator %s has designed <mapname>'s as the %s. +/// star: +/// 0 = Place of the Sun +/// 1 = Place of the Moon +/// 2 = Place of the Stars +/// 1 = Star Gladiator %s's %s: <mapname> +/// star: +/// 0 = Place of the Sun +/// 1 = Place of the Moon +/// 2 = Place of the Stars +/// 10 = Star Gladiator %s has designed <mapname>'s as the %s. +/// star: +/// 0 = Target of the Sun +/// 1 = Target of the Moon +/// 2 = Target of the Stars +/// 11 = Star Gladiator %s's %s: <mapname used as monster name> +/// star: +/// 0 = Monster of the Sun +/// 1 = Monster of the Moon +/// 2 = Monster of the Stars +/// 20 = [TaeKwon Mission] Target Monster : <mapname used as monster name> (<star>%) +/// 21 = [Taming Mission] Target Monster : <mapname used as monster name> +/// 22 = [Collector Rank] Target Item : <monster_id used as item id> +/// 30 = [Sun, Moon and Stars Angel] Designed places and monsters have been reset. +/// 40 = Target HP : <monster_id used as HP> +void clif_starskill(struct map_session_data* sd, const char* mapname, int monster_id, unsigned char star, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x20e)); + WFIFOW(fd,0) = 0x20e; + safestrncpy((char*)WFIFOP(fd,2), mapname, NAME_LENGTH); + WFIFOL(fd,26) = monster_id; + WFIFOB(fd,30) = star; + WFIFOB(fd,31) = result; + WFIFOSET(fd,packet_len(0x20e)); +} + +/*========================================== + * Info about Star Glaldiator save map [Komurka] + * type: 1: Information, 0: Map registered + *------------------------------------------*/ +void clif_feel_info(struct map_session_data* sd, unsigned char feel_level, unsigned char type) +{ + char mapname[MAP_NAME_LENGTH_EXT]; + + mapindex_getmapname_ext(mapindex_id2name(sd->feel_map[feel_level].index), mapname); + clif_starskill(sd, mapname, 0, feel_level, type ? 1 : 0); +} + +/*========================================== + * Info about Star Glaldiator hate mob [Komurka] + * type: 1: Register mob, 0: Information. + *------------------------------------------*/ +void clif_hate_info(struct map_session_data *sd, unsigned char hate_level,int class_, unsigned char type) +{ + if( pcdb_checkid(class_) ) + { + clif_starskill(sd, job_name(class_), class_, hate_level, type ? 10 : 11); + } + else if( mobdb_checkid(class_) ) + { + clif_starskill(sd, mob_db(class_)->jname, class_, hate_level, type ? 10 : 11); + } + else + { + ShowWarning("clif_hate_info: Received invalid class %d for this packet (char_id=%d, hate_level=%u, type=%u).\n", class_, sd->status.char_id, (unsigned int)hate_level, (unsigned int)type); + } +} + +/*========================================== + * Info about TaeKwon Do TK_MISSION mob [Skotlex] + *------------------------------------------*/ +void clif_mission_info(struct map_session_data *sd, int mob_id, unsigned char progress) +{ + clif_starskill(sd, mob_db(mob_id)->jname, mob_id, progress, 20); +} + +/*========================================== + * Feel/Hate reset (thanks to Rayce) [Skotlex] + *------------------------------------------*/ +void clif_feel_hate_reset(struct map_session_data *sd) +{ + clif_starskill(sd, "", 0, 0, 30); +} + + +/// Equip window (un)tick ack (ZC_CONFIG). +/// 02d9 <type>.L <value>.L +/// type: +/// 0 = open equip window +/// value: +/// 0 = disabled +/// 1 = enabled +void clif_equiptickack(struct map_session_data* sd, int flag) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2d9)); + WFIFOW(fd, 0) = 0x2d9; + WFIFOL(fd, 2) = 0; + WFIFOL(fd, 6) = flag; + WFIFOSET(fd, packet_len(0x2d9)); +} + + +/// The player's 'view equip' state, sent during login (ZC_CONFIG_NOTIFY). +/// 02da <open equip window>.B +/// open equip window: +/// 0 = disabled +/// 1 = enabled +void clif_equipcheckbox(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2da)); + WFIFOW(fd, 0) = 0x2da; + WFIFOB(fd, 2) = (sd->status.show_equip ? 1 : 0); + WFIFOSET(fd, packet_len(0x2da)); +} + + +/// Sends info about a player's equipped items. +/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <up-viewid>.W <mid-viewid>.W <low-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.26B* (ZC_EQUIPWIN_MICROSCOPE) +/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE, PACKETVER >= 20100629) +/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20101124) +/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <robe>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20110111) +void clif_viewequip_ack(struct map_session_data* sd, struct map_session_data* tsd) +{ + uint8* buf; + int i, n, fd, offset = 0; +#if PACKETVER < 20100629 + const int s = 26; +#else + const int s = 28; +#endif + nullpo_retv(sd); + nullpo_retv(tsd); + fd = sd->fd; + + WFIFOHEAD(fd, MAX_INVENTORY * s + 43); + buf = WFIFOP(fd,0); + +#if PACKETVER < 20101124 + WBUFW(buf, 0) = 0x2d7; +#else + WBUFW(buf, 0) = 0x859; +#endif + safestrncpy((char*)WBUFP(buf, 4), tsd->status.name, NAME_LENGTH); + WBUFW(buf,28) = tsd->status.class_; + WBUFW(buf,30) = tsd->vd.hair_style; + WBUFW(buf,32) = tsd->vd.head_bottom; + WBUFW(buf,34) = tsd->vd.head_mid; + WBUFW(buf,36) = tsd->vd.head_top; +#if PACKETVER >= 20110111 + WBUFW(buf,38) = tsd->vd.robe; + offset+= 2; + buf = WBUFP(buf,2); +#endif + WBUFW(buf,38) = tsd->vd.hair_color; + WBUFW(buf,40) = tsd->vd.cloth_color; + WBUFB(buf,42) = tsd->vd.sex; + + for(i=0,n=0; i < MAX_INVENTORY; i++) + { + if (tsd->status.inventory[i].nameid <= 0 || tsd->inventory_data[i] == NULL) // Item doesn't exist + continue; + if (!itemdb_isequip2(tsd->inventory_data[i])) // Is not equippable + continue; + + // Inventory position + WBUFW(buf, n*s+43) = i + 2; + // Add refine, identify flag, element, etc. + clif_item_sub(WBUFP(buf,0), n*s+45, &tsd->status.inventory[i], tsd->inventory_data[i], pc_equippoint(tsd, i)); + // Add cards + clif_addcards(WBUFP(buf, n*s+55), &tsd->status.inventory[i]); + // Expiration date stuff, if all of those are set to 0 then the client doesn't show anything related (6 bytes) + WBUFL(buf, n*s+63) = tsd->status.inventory[i].expire_time; + WBUFW(buf, n*s+67) = 0; +#if PACKETVER >= 20100629 + if (tsd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(buf, n*s+69) = tsd->inventory_data[i]->look; + else + WBUFW(buf, n*s+69) = 0; +#endif + n++; + } + + WFIFOW(fd, 2) = 43+offset+n*s; // Set length + WFIFOSET(fd, WFIFOW(fd, 2)); +} + + +/// Display msgstringtable.txt string (ZC_MSG). +/// 0291 <message>.W +void clif_msg(struct map_session_data* sd, unsigned short id) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x291)); + WFIFOW(fd, 0) = 0x291; + WFIFOW(fd, 2) = id; // zero-based msgstringtable.txt index + WFIFOSET(fd, packet_len(0x291)); +} + + +/// Display msgstringtable.txt string and fill in a valid for %d format (ZC_MSG_VALUE). +/// 0x7e2 <message>.W <value>.L +void clif_msg_value(struct map_session_data* sd, unsigned short id, int value) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7e2)); + WFIFOW(fd,0) = 0x7e2; + WFIFOW(fd,2) = id; + WFIFOL(fd,4) = value; + WFIFOSET(fd, packet_len(0x7e2)); +} + + +/// Displays msgstringtable.txt string, prefixed with a skill name. (ZC_MSG_SKILL). +/// 07e6 <skill id>.W <msg id>.L +/// +/// NOTE: Message has following format and is printed in color 0xCDCDFF (purple): +/// "[SkillName] Message" +void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7e6)); + WFIFOW(fd,0) = 0x7e6; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = msg_id; + WFIFOSET(fd, packet_len(0x7e6)); +} + + +/// View player equip request denied +void clif_viewequip_fail(struct map_session_data* sd) +{ + clif_msg(sd, 0x54d); +} + + +/// Validates one global/guild/party/whisper message packet and tries to recognize its components. +/// Returns true if the packet was parsed successfully. +/// Formats: 0 - <packet id>.w <packet len>.w (<name> : <message>).?B 00 +/// 1 - <packet id>.w <packet len>.w <name>.24B <message>.?B 00 +static bool clif_process_message(struct map_session_data* sd, int format, char** name_, int* namelen_, char** message_, int* messagelen_) +{ + char *text, *name, *message; + unsigned int packetlen, textlen, namelen, messagelen; + int fd = sd->fd; + + *name_ = NULL; + *namelen_ = 0; + *message_ = NULL; + *messagelen_ = 0; + + packetlen = RFIFOW(fd,2); + // basic structure checks + if( packetlen < 4 + 1 ) + { // 4-byte header and at least an empty string is expected + ShowWarning("clif_process_message: Received malformed packet from player '%s' (no message data)!\n", sd->status.name); + return false; + } + + text = (char*)RFIFOP(fd,4); + textlen = packetlen - 4; + + // process <name> part of the packet + if( format == 0 ) + {// name and message are separated by ' : ' + // validate name + name = text; + namelen = strnlen(sd->status.name, NAME_LENGTH-1); // name length (w/o zero byte) + + if( strncmp(name, sd->status.name, namelen) || // the text must start with the speaker's name + name[namelen] != ' ' || name[namelen+1] != ':' || name[namelen+2] != ' ' ) // followed by ' : ' + { + //Hacked message, or infamous "client desynch" issue where they pick one char while loading another. + ShowWarning("clif_process_message: Player '%s' sent a message using an incorrect name! Forcing a relog...\n", sd->status.name); + set_eof(fd); // Just kick them out to correct it. + return false; + } + + message = name + namelen + 3; + messagelen = textlen - namelen - 3; // this should be the message length (w/ zero byte included) + } + else + {// name has fixed width + if( textlen < NAME_LENGTH + 1 ) + { + ShowWarning("clif_process_message: Received malformed packet from player '%s' (packet length is incorrect)!\n", sd->status.name); + return false; + } + + // validate name + name = text; + namelen = strnlen(name, NAME_LENGTH-1); // name length (w/o zero byte) + + if( name[namelen] != '\0' ) + { // only restriction is that the name must be zero-terminated + ShowWarning("clif_process_message: Player '%s' sent an unterminated name!\n", sd->status.name); + return false; + } + + message = name + NAME_LENGTH; + messagelen = textlen - NAME_LENGTH; // this should be the message length (w/ zero byte included) + } + + if( messagelen != strnlen(message, messagelen)+1 ) + { // the declared length must match real length + ShowWarning("clif_process_message: Received malformed packet from player '%s' (length is incorrect)!\n", sd->status.name); + return false; + } + // verify <message> part of the packet + if( message[messagelen-1] != '\0' ) + { // message must be zero-terminated + ShowWarning("clif_process_message: Player '%s' sent an unterminated message string!\n", sd->status.name); + return false; + } + if( messagelen > CHAT_SIZE_MAX-1 ) + { // messages mustn't be too long + // Normally you can only enter CHATBOX_SIZE-1 letters into the chat box, but Frost Joke / Dazzler's text can be longer. + // Also, the physical size of strings that use multibyte encoding can go multiple times over the chatbox capacity. + // Neither the official client nor server place any restriction on the length of the data in the packet, + // but we'll only allow reasonably long strings here. This also makes sure that they fit into the `chatlog` table. + ShowWarning("clif_process_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHAT_SIZE_MAX-1, message); + return false; + } + + *name_ = name; + *namelen_ = namelen; + *message_ = message; + *messagelen_ = messagelen; + return true; +} + +// --------------------- +// clif_guess_PacketVer +// --------------------- +// Parses a WantToConnection packet to try to identify which is the packet version used. [Skotlex] +// error codes: +// 0 - Success +// 1 - Unknown packet_ver +// 2 - Invalid account_id +// 3 - Invalid char_id +// 4 - Invalid login_id1 (reserved) +// 5 - Invalid client_tick (reserved) +// 6 - Invalid sex +// Only the first 'invalid' error that appears is used. +static int clif_guess_PacketVer(int fd, int get_previous, int *error) +{ + static int err = 1; + static int packet_ver = -1; + int cmd, packet_len, value; //Value is used to temporarily store account/char_id/sex + + if (get_previous) + {//For quick reruns, since the normal code flow is to fetch this once to identify the packet version, then again in the wanttoconnect function. [Skotlex] + if( error ) + *error = err; + return packet_ver; + } + + //By default, start searching on the default one. + err = 1; + packet_ver = clif_config.packet_db_ver; + cmd = RFIFOW(fd,0); + packet_len = RFIFOREST(fd); + +#define SET_ERROR(n) \ + if( err == 1 )\ + err = n;\ +//define SET_ERROR + + // FIXME: If the packet is not received at once, this will FAIL. + // Figure out, when it happens, that only part of the packet is + // received, or fix the function to be able to deal with that + // case. +#define CHECK_PACKET_VER() \ + if( cmd != clif_config.connect_cmd[packet_ver] || packet_len != packet_db[packet_ver][cmd].len )\ + ;/* not wanttoconnection or wrong length */\ + else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[0])) < START_ACCOUNT_NUM || value > END_ACCOUNT_NUM )\ + { SET_ERROR(2); }/* invalid account_id */\ + else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[1])) <= 0 )\ + { SET_ERROR(3); }/* invalid char_id */\ + /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]) - don't care about login_id1 */\ + /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]) - don't care about client_tick */\ + else if( (value=(int)RFIFOB(fd, packet_db[packet_ver][cmd].pos[4])) != 0 && value != 1 )\ + { SET_ERROR(6); }/* invalid sex */\ + else\ + {\ + err = 0;\ + if( error )\ + *error = 0;\ + return packet_ver;\ + }\ +//define CHECK_PACKET_VER + + CHECK_PACKET_VER();//Default packet version found. + + for (packet_ver = MAX_PACKET_VER; packet_ver > 0; packet_ver--) + { //Start guessing the version, giving priority to the newer ones. [Skotlex] + CHECK_PACKET_VER(); + } + if( error ) + *error = err; + packet_ver = -1; + return -1; +#undef SET_ERROR +#undef CHECK_PACKET_VER +} + +// ------------ +// clif_parse_* +// ------------ +// Parses incoming (player) connection + + +/// Request to connect to map-server. +/// 0072 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER) +/// 0436 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_WantToConnection(int fd, TBL_PC* sd) +{ + struct block_list* bl; + struct auth_node* node; + int cmd, account_id, char_id, login_id1, sex; + unsigned int client_tick; //The client tick is a tick, therefore it needs be unsigned. [Skotlex] + int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 (by [Yor]) + + if (sd) { + ShowError("clif_parse_WantToConnection : invalid request (character already logged in)\n"); + return; + } + + // Only valid packet version get here + packet_ver = clif_guess_PacketVer(fd, 1, NULL); + + cmd = RFIFOW(fd,0); + account_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[0]); + char_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[1]); + login_id1 = RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]); + client_tick = RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]); + sex = RFIFOB(fd, packet_db[packet_ver][cmd].pos[4]); + + if( packet_ver < 5 || // reject really old client versions + (packet_ver <= 9 && (battle_config.packet_ver_flag & 1) == 0) || // older than 6sept04 + (packet_ver > 9 && (battle_config.packet_ver_flag & 1<<(packet_ver-9)) == 0)) // version not allowed + {// packet version rejected + ShowInfo("Rejected connection attempt, forbidden packet version (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"', Packet Ver: '"CL_WHITE"%d"CL_RESET"', IP: '"CL_WHITE"%s"CL_RESET"').\n", account_id, char_id, packet_ver, ip2str(session[fd]->client_addr, NULL)); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 5; // Your Game's EXE file is not the latest version + WFIFOSET(fd,packet_len(0x6a)); + set_eof(fd); + return; + } + + if( runflag != MAPSERVER_ST_RUNNING ) + {// not allowed + clif_authfail_fd(fd,1);// server closed + return; + } + + //Check for double login. + bl = map_id2bl(account_id); + if(bl && bl->type != BL_PC) { + ShowError("clif_parse_WantToConnection: a non-player object already has id %d, please increase the starting account number\n", account_id); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // Rejected by server + WFIFOSET(fd,packet_len(0x6a)); + set_eof(fd); + return; + } + + if (bl || + ((node=chrif_search(account_id)) && //An already existing node is valid only if it is for this login. + !(node->account_id == account_id && node->char_id == char_id && node->state == ST_LOGIN))) + { + clif_authfail_fd(fd, 8); //Still recognizes last connection + return; + } + + CREATE(sd, TBL_PC, 1); + sd->fd = fd; + sd->packet_ver = packet_ver; + session[fd]->session_data = sd; + + pc_setnewpc(sd, account_id, char_id, login_id1, client_tick, sex, fd); + +#if PACKETVER < 20070521 + WFIFOHEAD(fd,4); + WFIFOL(fd,0) = sd->bl.id; + WFIFOSET(fd,4); +#else + WFIFOHEAD(fd,packet_len(0x283)); + WFIFOW(fd,0) = 0x283; + WFIFOL(fd,2) = sd->bl.id; + WFIFOSET(fd,packet_len(0x283)); +#endif + + chrif_authreq(sd); +} + + +/// Notification from the client, that it has finished map loading and is about to display player's character (CZ_NOTIFY_ACTORINIT). +/// 007d +void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) +{ + if(sd->bl.prev != NULL) + return; + + if (!sd->state.active) + { //Character loading is not complete yet! + //Let pc_reg_received reinvoke this when ready. + sd->state.connect_new = 0; + return; + } + + if (sd->state.rewarp) + { //Rewarp player. + sd->state.rewarp = 0; + clif_changemap(sd, sd->mapindex, sd->bl.x, sd->bl.y); + return; + } + + sd->state.warping = 0; + + // look +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); +#endif + + if(sd->vd.cloth_color) + clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); + + // item + clif_inventorylist(sd); // inventory list first, otherwise deleted items in pc_checkitem show up as 'unknown item' + pc_checkitem(sd); + + // cart + if(pc_iscarton(sd)) { + clif_cartlist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + + // weight + clif_updatestatus(sd,SP_WEIGHT); + clif_updatestatus(sd,SP_MAXWEIGHT); + + // guild + // (needs to go before clif_spawn() to show guild emblems correctly) + if(sd->status.guild_id) + guild_send_memberinfoshort(sd,1); + + if(battle_config.pc_invincible_time > 0) { + if(map_flag_gvg(sd->bl.m)) + pc_setinvincibletimer(sd,battle_config.pc_invincible_time<<1); + else + pc_setinvincibletimer(sd,battle_config.pc_invincible_time); + } + + if( map[sd->bl.m].users++ == 0 && battle_config.dynamic_mobs ) + map_spawnmobs(sd->bl.m); + if( !(sd->sc.option&OPTION_INVISIBLE) ) + {// increment the number of pvp players on the map + map[sd->bl.m].users_pvp++; + } + if( map[sd->bl.m].instance_id ) + { + instance[map[sd->bl.m].instance_id].users++; + instance_check_idle(map[sd->bl.m].instance_id); + } + sd->state.debug_remove_map = 0; // temporary state to track double remove_map's [FlavioJS] + + // reset the callshop flag if the player changes map + sd->state.callshop = 0; + + map_addblock(&sd->bl); + clif_spawn(&sd->bl); + + // Party + // (needs to go after clif_spawn() to show hp bars correctly) + if(sd->status.party_id) { + party_send_movemap(sd); + clif_party_hp(sd); // Show hp after displacement [LuzZza] + } + + if( sd->bg_id ) clif_bg_hp(sd); // BattleGround System + + if(map[sd->bl.m].flag.pvp && !(sd->sc.option&OPTION_INVISIBLE)) { + if(!battle_config.pk_mode) { // remove pvp stuff for pk_mode [Valaris] + if (!map[sd->bl.m].flag.pvp_nocalcrank) + sd->pvp_timer = add_timer(gettick()+200, pc_calc_pvprank_timer, sd->bl.id, 0); + sd->pvp_rank = 0; + sd->pvp_lastusers = 0; + sd->pvp_point = 5; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + } else + // set flag, if it's a duel [LuzZza] + if(sd->duel_group) + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + + if (map[sd->bl.m].flag.gvg_dungeon) + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //TODO: Figure out the real packet to send here. + + if( map_flag_gvg(sd->bl.m) ) + clif_map_property(sd, MAPPROPERTY_AGITZONE); + + // info about nearby objects + // must use foreachinarea (CIRCULAR_AREA interferes with foreachinrange) + map_foreachinarea(clif_getareachar, sd->bl.m, sd->bl.x-AREA_SIZE, sd->bl.y-AREA_SIZE, sd->bl.x+AREA_SIZE, sd->bl.y+AREA_SIZE, BL_ALL, sd); + + // pet + if( sd->pd ) + { + if( battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m) ) + { //Return the pet to egg. [Skotlex] + clif_displaymessage(sd->fd, msg_txt(666)); + pet_menu(sd, 3); //Option 3 is return to egg. + } + else + { + map_addblock(&sd->pd->bl); + clif_spawn(&sd->pd->bl); + clif_send_petdata(sd,sd->pd,0,0); + clif_send_petstatus(sd); +// skill_unit_move(&sd->pd->bl,gettick(),1); + } + } + + //homunculus [blackhole89] + if( merc_is_hom_active(sd->hd) ) + { + map_addblock(&sd->hd->bl); + clif_spawn(&sd->hd->bl); + clif_send_homdata(sd,SP_ACK,0); + clif_hominfo(sd,sd->hd,1); + clif_hominfo(sd,sd->hd,0); //for some reason, at least older clients want this sent twice + clif_homskillinfoblock(sd); + if( battle_config.hom_setting&0x8 ) + status_calc_bl(&sd->hd->bl, SCB_SPEED); //Homunc mimic their master's speed on each map change + if( !(battle_config.hom_setting&0x2) ) + skill_unit_move(&sd->hd->bl,gettick(),1); // apply land skills immediately + } + + if( sd->md ) { + map_addblock(&sd->md->bl); + clif_spawn(&sd->md->bl); + clif_mercenary_info(sd); + clif_mercenary_skillblock(sd); + status_calc_bl(&sd->md->bl, SCB_SPEED); // Mercenary mimic their master's speed on each map change + } + + if( sd->ed ) { + map_addblock(&sd->ed->bl); + clif_spawn(&sd->ed->bl); + clif_elemental_info(sd); + clif_elemental_updatestatus(sd,SP_HP); + clif_hpmeter_single(sd->fd,sd->ed->bl.id,sd->ed->battle_status.hp,sd->ed->battle_status.max_hp); + clif_elemental_updatestatus(sd,SP_SP); + status_calc_bl(&sd->ed->bl, SCB_SPEED); //Elemental mimic their master's speed on each map change + } + + if(sd->state.connect_new) { + int lv; + sd->state.connect_new = 0; + clif_skillinfoblock(sd); + clif_hotkeys_send(sd); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + clif_initialstatus(sd); + + if (sd->sc.option&OPTION_FALCON) + clif_status_load(&sd->bl, SI_FALCON, 1); + + if (sd->sc.option&OPTION_RIDING) + clif_status_load(&sd->bl, SI_RIDING, 1); + else if (sd->sc.option&OPTION_WUGRIDER) + clif_status_load(&sd->bl, SI_WUGRIDER, 1); + + if(sd->status.manner < 0) + sc_start(&sd->bl,SC_NOCHAT,100,0,0); + + //Auron reported that This skill only triggers when you logon on the map o.O [Skotlex] + if ((lv = pc_checkskill(sd,SG_KNOWLEDGE)) > 0) { + if(sd->bl.m == sd->feel_map[0].m + || sd->bl.m == sd->feel_map[1].m + || sd->bl.m == sd->feel_map[2].m) + sc_start(&sd->bl, SC_KNOWLEDGE, 100, lv, skill_get_time(SG_KNOWLEDGE, lv)); + } + + if(sd->pd && sd->pd->pet.intimate > 900) + clif_pet_emotion(sd->pd,(sd->pd->pet.class_ - 100)*100 + 50 + pet_hungry_val(sd->pd)); + + if(merc_is_hom_active(sd->hd)) + merc_hom_init_timers(sd->hd); + + if (night_flag && map[sd->bl.m].flag.nightenabled) { + sd->state.night = 1; + clif_status_load(&sd->bl, SI_NIGHT, 1); + } + + // Notify everyone that this char logged in [Skotlex]. + map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1); + + //Login Event + npc_script_event(sd, NPCE_LOGIN); + } else { + //For some reason the client "loses" these on warp/map-change. + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + // abort currently running script + sd->state.using_fake_npc = 0; + sd->state.menu_or_input = 0; + sd->npc_menu = 0; + + if(sd->npc_id) + npc_event_dequeue(sd); + } + + if( sd->state.changemap ) + {// restore information that gets lost on map-change +#if PACKETVER >= 20070918 + clif_partyinvitationstate(sd); + clif_equipcheckbox(sd); +#endif + if( (battle_config.bg_flee_penalty != 100 || battle_config.gvg_flee_penalty != 100) && + (map_flag_gvg(sd->state.pmap) || map_flag_gvg(sd->bl.m) || map[sd->state.pmap].flag.battleground || map[sd->bl.m].flag.battleground) ) + status_calc_bl(&sd->bl, SCB_FLEE); //Refresh flee penalty + + if( night_flag && map[sd->bl.m].flag.nightenabled ) + { //Display night. + if( !sd->state.night ) + { + sd->state.night = 1; + clif_status_load(&sd->bl, SI_NIGHT, 1); + } + } + else if( sd->state.night ) + { //Clear night display. + sd->state.night = 0; + clif_status_load(&sd->bl, SI_NIGHT, 0); + } + + if( map[sd->bl.m].flag.battleground ) + { + clif_map_type(sd, MAPTYPE_BATTLEFIELD); // Battleground Mode + if( map[sd->bl.m].flag.battleground == 2 ) + clif_bg_updatescore_single(sd); + } + + if( map[sd->bl.m].flag.allowks && !map_flag_ks(sd->bl.m) ) + { + char output[128]; + sprintf(output, "[ Kill Steal Protection Disable. KS is allowed in this map ]"); + clif_broadcast(&sd->bl, output, strlen(output) + 1, 0x10, SELF); + } + + map_iwall_get(sd); // Updates Walls Info on this Map to Client + sd->state.changemap = false; + } + + mail_clear(sd); + + /* Guild Aura Init */ + if( sd->state.gmaster_flag ) { + guild_guildaura_refresh(sd,GD_LEADERSHIP,guild_checkskill(sd->state.gmaster_flag,GD_LEADERSHIP)); + guild_guildaura_refresh(sd,GD_GLORYWOUNDS,guild_checkskill(sd->state.gmaster_flag,GD_GLORYWOUNDS)); + guild_guildaura_refresh(sd,GD_SOULCOLD,guild_checkskill(sd->state.gmaster_flag,GD_SOULCOLD)); + guild_guildaura_refresh(sd,GD_HAWKEYES,guild_checkskill(sd->state.gmaster_flag,GD_HAWKEYES)); + } + + if( sd->state.vending ) { /* show we have a vending */ + clif_openvending(sd,sd->bl.id,sd->vending); + clif_showvendingboard(&sd->bl,sd->message,0); + } + + if(map[sd->bl.m].flag.loadevent) // Lance + npc_script_event(sd, NPCE_LOADMAP); + + if (pc_checkskill(sd, SG_DEVIL) && !pc_nextjobexp(sd)) + clif_status_load(&sd->bl, SI_DEVIL, 1); //blindness [Komurka] + + if (sd->sc.opt2) //Client loses these on warp. + clif_changeoption(&sd->bl); + + clif_weather_check(sd); + + // For automatic triggering of NPCs after map loading (so you don't need to walk 1 step first) + if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNPC)) + npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y); + else + sd->areanpc_id = 0; + + /* it broke at some point (e.g. during a crash), so we make it visibly dead again. */ + if( !sd->status.hp && !pc_isdead(sd) && status_isdead(&sd->bl) ) + pc_setdead(sd); + + // If player is dead, and is spawned (such as @refresh) send death packet. [Valaris] + if(pc_isdead(sd)) + clif_clearunit_area(&sd->bl, CLR_DEAD); + else { + skill_usave_trigger(sd); + clif_changed_dir(&sd->bl, SELF); + } + +// Trigger skill effects if you appear standing on them + if(!battle_config.pc_invincible_time) + skill_unit_move(&sd->bl,gettick(),1); +} + + +/// Server's tick (ZC_NOTIFY_TIME). +/// 007f <time>.L +void clif_notify_time(struct map_session_data* sd, unsigned long time) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x7f)); + WFIFOW(fd,0) = 0x7f; + WFIFOL(fd,2) = time; + WFIFOSET(fd,packet_len(0x7f)); +} + + +/// Request for server's tick. +/// 007e <client tick>.L (CZ_REQUEST_TIME) +/// 0360 <client tick>.L (CZ_REQUEST_TIME2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_TickSend(int fd, struct map_session_data *sd) +{ + sd->client_tick = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + clif_notify_time(sd, gettick()); +} + + +/// Sends hotkey bar. +/// 02b9 { <is skill>.B <id>.L <count>.W }*27 (ZC_SHORTCUT_KEY_LIST) +/// 07d9 { <is skill>.B <id>.L <count>.W }*36 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090603) +/// 07d9 { <is skill>.B <id>.L <count>.W }*38 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090617) +void clif_hotkeys_send(struct map_session_data *sd) { +#ifdef HOTKEY_SAVING + const int fd = sd->fd; + int i; +#if PACKETVER < 20090603 + const int cmd = 0x2b9; +#else + const int cmd = 0x7d9; +#endif + if (!fd) return; + WFIFOHEAD(fd, 2+MAX_HOTKEYS*7); + WFIFOW(fd, 0) = cmd; + for(i = 0; i < MAX_HOTKEYS; i++) { + WFIFOB(fd, 2 + 0 + i * 7) = sd->status.hotkeys[i].type; // type: 0: item, 1: skill + WFIFOL(fd, 2 + 1 + i * 7) = sd->status.hotkeys[i].id; // item or skill ID + WFIFOW(fd, 2 + 5 + i * 7) = sd->status.hotkeys[i].lv; // skill level + } + WFIFOSET(fd, packet_len(cmd)); +#endif +} + + +/// Request to update a position on the hotkey bar (CZ_SHORTCUT_KEY_CHANGE). +/// 02ba <index>.W <is skill>.B <id>.L <count>.W +void clif_parse_Hotkey(int fd, struct map_session_data *sd) { +#ifdef HOTKEY_SAVING + unsigned short idx; + int cmd; + + cmd = RFIFOW(fd, 0); + idx = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[0]); + if (idx >= MAX_HOTKEYS) return; + + sd->status.hotkeys[idx].type = RFIFOB(fd, packet_db[sd->packet_ver][cmd].pos[1]); + sd->status.hotkeys[idx].id = RFIFOL(fd, packet_db[sd->packet_ver][cmd].pos[2]); + sd->status.hotkeys[idx].lv = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[3]); +#endif +} + + +/// Displays cast-like progress bar (ZC_PROGRESS). +/// 02f0 <color>.L <time>.L +void clif_progressbar(struct map_session_data * sd, unsigned long color, unsigned int second) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2f0)); + WFIFOW(fd,0) = 0x2f0; + WFIFOL(fd,2) = color; + WFIFOL(fd,6) = second; + WFIFOSET(fd,packet_len(0x2f0)); +} + + +/// Removes an ongoing progress bar (ZC_PROGRESS_CANCEL). +/// 02f2 +void clif_progressbar_abort(struct map_session_data * sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2f2)); + WFIFOW(fd,0) = 0x2f2; + WFIFOSET(fd,packet_len(0x2f2)); +} + + +/// Notification from the client, that the progress bar has reached 100% (CZ_PROGRESS). +/// 02f1 +void clif_parse_progressbar(int fd, struct map_session_data * sd) +{ + int npc_id = sd->progressbar.npc_id; + + if( gettick() < sd->progressbar.timeout && sd->st ) + sd->st->state = END; + + sd->progressbar.npc_id = sd->progressbar.timeout = 0; + npc_scriptcont(sd, npc_id); +} + + +/// Request to walk to a certain position on the current map. +/// 0085 <dest>.3B (CZ_REQUEST_MOVE) +/// 035f <dest>.3B (CZ_REQUEST_MOVE2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_WalkToXY(int fd, struct map_session_data *sd) +{ + short x, y; + + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + if (sd->sc.opt1 && ( sd->sc.opt1 == OPT1_STONEWAIT || sd->sc.opt1 == OPT1_BURNING )) + ; //You CAN walk on this OPT1 value. + else if( sd->progressbar.npc_id ) + clif_progressbar_abort(sd); + else if (pc_cant_act(sd)) + return; + + if(sd->sc.data[SC_RUN] || sd->sc.data[SC_WUGDASH]) + return; + + pc_delinvincibletimer(sd); + + RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0], &x, &y, NULL); + + //Set last idle time... [Skotlex] + sd->idletime = last_tick; + + unit_walktoxy(&sd->bl, x, y, 4); +} + + +/// Notification about the result of a disconnect request (ZC_ACK_REQ_DISCONNECT). +/// 018b <result>.W +/// result: +/// 0 = disconnect (quit) +/// 1 = cannot disconnect (wait 10 seconds) +/// ? = ignored +void clif_disconnect_ack(struct map_session_data* sd, short result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x18b)); + WFIFOW(fd,0) = 0x18b; + WFIFOW(fd,2) = result; + WFIFOSET(fd,packet_len(0x18b)); +} + + +/// Request to disconnect from server (CZ_REQ_DISCONNECT). +/// 018a <type>.W +/// type: +/// 0 = quit +void clif_parse_QuitGame(int fd, struct map_session_data *sd) +{ + /* Rovert's prevent logout option fixed [Valaris] */ + if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && + (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) + { + set_eof(fd); + clif_disconnect_ack(sd, 0); + } else { + clif_disconnect_ack(sd, 1); + } +} + + +/// Requesting unit's name. +/// 0094 <id>.L (CZ_REQNAME) +/// 0368 <id>.L (CZ_REQNAME2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_GetCharNameRequest(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + struct block_list* bl; + //struct status_change *sc; + + if( id < 0 && -id == sd->bl.id ) // for disguises [Valaris] + id = sd->bl.id; + + bl = map_id2bl(id); + if( bl == NULL ) + return; // Lagged clients could request names of already gone mobs/players. [Skotlex] + + if( sd->bl.m != bl->m || !check_distance_bl(&sd->bl, bl, AREA_SIZE) ) + return; // Block namerequests past view range + + // 'see people in GM hide' cheat detection + /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) + sc = status_get_sc(bl); + if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && + bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple + pc_get_group_level(sd) < battle_config.hack_info_GM_level + ) { + char gm_msg[256]; + sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); + ShowWarning(gm_msg); + // information is sent to all online GMs + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); + return; + } + */ + + clif_charnameack(fd, bl); +} + + +/// Validates and processes global messages +/// 008c <packet len>.W <text>.?B (<name> : <message>) 00 (CZ_REQUEST_CHAT) +/// There are various variants of this packet. +void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message, *fakename = NULL; + int namelen, messagelen; + + bool is_fake; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + /** + * Fake Name Design by FatalEror (bug report #9) + **/ + if( ( is_fake = ( sd->fakename[0] ) ) ) { + fakename = (char*) aMalloc(strlen(sd->fakename)+messagelen+3); + strcpy(fakename, sd->fakename); + strcat(fakename, " : "); + strcat(fakename, message); + textlen = strlen(fakename) + 1; + } + // send message to others (using the send buffer for temp. storage) + WFIFOHEAD(fd, 8 + textlen); + WFIFOW(fd,0) = 0x8d; + WFIFOW(fd,2) = 8 + textlen; + WFIFOL(fd,4) = sd->bl.id; + safestrncpy((char*)WFIFOP(fd,8), is_fake ? fakename : text, textlen); + //FIXME: chat has range of 9 only + clif_send(WFIFOP(fd,0), WFIFOW(fd,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); + + // send back message to the speaker + if( is_fake ) { + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = textlen + 4; + safestrncpy((char*)WFIFOP(fd,4), fakename, textlen); + aFree(fakename); + } else { + memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOW(fd,0) = 0x8e; + } + WFIFOSET(fd, WFIFOW(fd,2)); +#ifdef PCRE_SUPPORT + // trigger listening npcs + map_foreachinrange(npc_chat_sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); +#endif + + // Chat logging type 'O' / Global Chat + log_chat(LOG_CHAT_GLOBAL, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message); +} + + +/// /mm /mapmove (as @rura GM command) (CZ_MOVETO_MAP). +/// Request to warp to a map on given coordinates. +/// 0140 <map name>.16B <x>.W <y>.W +void clif_parse_MapMove(int fd, struct map_session_data *sd) +{ + char command[MAP_NAME_LENGTH_EXT+25]; + char* map_name; + + map_name = (char*)RFIFOP(fd,2); + map_name[MAP_NAME_LENGTH_EXT-1]='\0'; + sprintf(command, "%cmapmove %s %d %d", atcommand_symbol, map_name, RFIFOW(fd,18), RFIFOW(fd,20)); + is_atcommand(fd, sd, command, 1); +} + + +/// Updates body and head direction of an object (ZC_CHANGE_DIRECTION). +/// 009c <id>.L <head dir>.W <dir>.B +/// head dir: +/// 0 = straight +/// 1 = turned CW +/// 2 = turned CCW +/// dir: +/// 0 = north +/// 1 = northwest +/// 2 = west +/// 3 = southwest +/// 4 = south +/// 5 = southeast +/// 6 = east +/// 7 = northeast +void clif_changed_dir(struct block_list *bl, enum send_target target) +{ + unsigned char buf[64]; + + WBUFW(buf,0) = 0x9c; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = bl->type==BL_PC?((TBL_PC*)bl)->head_dir:0; + WBUFB(buf,8) = unit_getdir(bl); + + clif_send(buf, packet_len(0x9c), bl, target); + + if (disguised(bl)) { + WBUFL(buf,2) = -bl->id; + WBUFW(buf,6) = 0; + clif_send(buf, packet_len(0x9c), bl, SELF); + } +} + + +/// Request to change own body and head direction. +/// 009b <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION) +/// 0361 <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_ChangeDir(int fd, struct map_session_data *sd) +{ + unsigned char headdir, dir; + + headdir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + dir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + pc_setdir(sd, dir, headdir); + + clif_changed_dir(&sd->bl, AREA_WOS); +} + + +/// Request to show an emotion (CZ_REQ_EMOTION). +/// 00bf <type>.B +/// type: +/// @see enum emotion_type +void clif_parse_Emotion(int fd, struct map_session_data *sd) +{ + int emoticon = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 2) { + if (emoticon == E_MUTE) {// prevent use of the mute emote [Valaris] + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); + return; + } + // fix flood of emotion icon (ro-proxy): flood only the hacker player + if (sd->emotionlasttime + 1 >= time(NULL)) { // not more than 1 per second + sd->emotionlasttime = time(NULL); + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); + return; + } + sd->emotionlasttime = time(NULL); + + if(battle_config.client_reshuffle_dice && emoticon>=E_DICE1 && emoticon<=E_DICE6) + {// re-roll dice + emoticon = rnd()%6+E_DICE1; + } + + clif_emotion(&sd->bl, emoticon); + } else + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); +} + + +/// Amount of currently online players, reply to /w /who (ZC_USER_COUNT). +/// 00c2 <count>.L +void clif_user_count(struct map_session_data* sd, int count) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xc2)); + WFIFOW(fd,0) = 0xc2; + WFIFOL(fd,2) = count; + WFIFOSET(fd,packet_len(0xc2)); +} + + +/// /w /who (CZ_REQ_USER_COUNT). +/// Request to display amount of currently connected players. +/// 00c1 +void clif_parse_HowManyConnections(int fd, struct map_session_data *sd) +{ + clif_user_count(sd, map_getusers()); +} + + +void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, int target_id, unsigned int tick) +{ + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + if (sd->sc.count && + (sd->sc.data[SC_TRICKDEAD] || + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_BLADESTOP] || + sd->sc.data[SC__MANHOLE] || + sd->sc.data[SC_CURSEDCIRCLE_ATKER] || + sd->sc.data[SC_CURSEDCIRCLE_TARGET] )) + return; + + pc_stop_walking(sd, 1); + pc_stop_attack(sd); + + if(target_id<0 && -target_id == sd->bl.id) // for disguises [Valaris] + target_id = sd->bl.id; + + switch(action_type) + { + case 0x00: // once attack + case 0x07: // continuous attack + + if( pc_cant_act(sd) || sd->sc.option&OPTION_HIDE ) + return; + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] || sd->sc.data[SC__SHADOWFORM] ) + return; + + if (!battle_config.sdelay_attack_enable && pc_checkskill(sd, SA_FREECAST) <= 0) { + if (DIFF_TICK(tick, sd->ud.canact_tick) < 0) { + clif_skill_fail(sd, 1, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + pc_delinvincibletimer(sd); + sd->idletime = last_tick; + unit_attack(&sd->bl, target_id, action_type != 0); + break; + case 0x02: // sitdown + if (battle_config.basic_skill_check && pc_checkskill(sd, NV_BASIC) < 3) { + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 2); + break; + } + + if(pc_issit(sd)) { + //Bugged client? Just refresh them. + clif_sitting(&sd->bl); + return; + } + + if (sd->ud.skilltimer != INVALID_TIMER || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING )) + break; + + if (sd->sc.count && ( + sd->sc.data[SC_DANCING] || + (sd->sc.data[SC_GRAVITATION] && sd->sc.data[SC_GRAVITATION]->val3 == BCT_SELF) + )) //No sitting during these states either. + break; + + pc_setsit(sd); + skill_sit(sd,1); + clif_sitting(&sd->bl); + break; + case 0x03: // standup + if (!pc_issit(sd)) { + //Bugged client? Just refresh them. + clif_standing(&sd->bl); + return; + } + pc_setstand(sd); + skill_sit(sd,0); + clif_standing(&sd->bl); + break; + } +} + + +/// Request for an action. +/// 0089 <target id>.L <action>.B (CZ_REQUEST_ACT) +/// 0437 <target id>.L <action>.B (CZ_REQUEST_ACT2) +/// action: +/// 0 = attack +/// 1 = pick up item +/// 2 = sit down +/// 3 = stand up +/// 7 = continous attack +/// 12 = (touch skill?) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_ActionRequest(int fd, struct map_session_data *sd) +{ + clif_parse_ActionRequest_sub(sd, + RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), + RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), + gettick() + ); +} + + +/// Response to the death/system menu (CZ_RESTART). +/// 00b2 <type>.B +/// type: +/// 0 = restart (respawn) +/// 1 = char-select (disconnect) +void clif_parse_Restart(int fd, struct map_session_data *sd) +{ + switch(RFIFOB(fd,2)) { + case 0x00: + pc_respawn(sd,CLR_RESPAWN); + break; + case 0x01: + /* Rovert's Prevent logout option - Fixed [Valaris] */ + if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && + (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) + { //Send to char-server for character selection. + chrif_charselectreq(sd, session[fd]->client_addr); + } else { + clif_disconnect_ack(sd, 1); + } + break; + } +} + + +/// Validates and processes whispered messages (CZ_WHISPER). +/// 0096 <packet len>.W <nick>.24B <message>.?B +void clif_parse_WisMessage(int fd, struct map_session_data* sd) +{ + struct map_session_data* dstsd; + int i; + + char *target, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 1, &target, &namelen, &message, &messagelen) ) + return; + + if ( is_atcommand(fd, sd, message, 1) ) + return; + + if (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT)) + return; + + if (battle_config.min_chat_delay) { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) { + return; + } + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + // Chat logging type 'W' / Whisper + log_chat(LOG_CHAT_WHISPER, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, target, message); + + //-------------------------------------------------------// + // Lordalfa - Paperboy - To whisper NPC commands // + //-------------------------------------------------------// + if (target[0] && (strncasecmp(target,"NPC:",4) == 0) && (strlen(target) > 4)) + { + char* str = target+4; //Skip the NPC: string part. + struct npc_data* npc; + if ((npc = npc_name2id(str))) { + char split_data[NUM_WHISPER_VAR][CHAT_SIZE_MAX]; + char *split; + char output[256]; + + str = message; + // skip codepage indicator, if detected + if( str[0] == '|' && strlen(str) >= 4 ) + str += 3; + for( i = 0; i < NUM_WHISPER_VAR; ++i ) {// Splits the message using '#' as separators + split = strchr(str,'#'); + if( split == NULL ) { // use the remaining string + safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); + for( ++i; i < NUM_WHISPER_VAR; ++i ) + split_data[i][0] = '\0'; + break; + } + *split = '\0'; + safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); + str = split+1; + } + + for( i = 0; i < NUM_WHISPER_VAR; ++i ) { + sprintf(output, "@whispervar%d$", i); + set_var(sd,output,(char *) split_data[i]); + } + + sprintf(output, "%s::OnWhisperGlobal", npc->exname); + npc_event(sd,output,0); // Calls the NPC label + + return; + } + } else if(strcmpi(target, main_chat_nick) == 0) { // Main chat [LuzZza] + if(!sd->state.mainchat) + clif_displaymessage(fd, msg_txt(388)); // You should enable main chat with "@main on" command. + else { + // send the main message using inter-server system + intif_main_message( sd, message ); + } + + return; + } + + // searching destination character + dstsd = map_nick2sd(target); + + if (dstsd == NULL || strcmp(dstsd->status.name, target) != 0) { + // player is not on this map-server + // At this point, don't send wisp/page if it's not exactly the same name, because (example) + // if there are 'Test' player on an other map-server and 'test' player on this map-server, + // and if we ask for 'Test', we must not contact 'test' player + // so, we send information to inter-server, which is the only one which decide (and copy correct name). + intif_wis_message(sd, target, message, messagelen); + return; + } + + // if player ignores everyone + if (dstsd->state.ignoreAll) { + if (dstsd->sc.option & OPTION_INVISIBLE && pc_get_group_level(sd) < pc_get_group_level(dstsd)) + clif_wis_end(fd, 1); // 1: target character is not loged in + else + clif_wis_end(fd, 3); // 3: everyone ignored by target + return; + } + + // if player is autotrading + if( dstsd->state.autotrade == 1 ) { + char output[256]; + sprintf(output, "%s is in autotrade mode and cannot receive whispered messages.", dstsd->status.name); + clif_wis_message(fd, wisp_server_name, output, strlen(output) + 1); + return; + } + + // if player ignores the source character + ARR_FIND(0, MAX_IGNORE_LIST, i, dstsd->ignore[i].name[0] == '\0' || strcmp(dstsd->ignore[i].name, sd->status.name) == 0); + if(i < MAX_IGNORE_LIST && dstsd->ignore[i].name[0] != '\0') { // source char present in ignore list + clif_wis_end(fd, 2); // 2: ignored by target + return; + } + + // notify sender of success + clif_wis_end(fd, 0); // 0: success to send wisper + + // Normal message + clif_wis_message(dstsd->fd, sd->status.name, message, messagelen); +} + + +/// /b /nb (CZ_BROADCAST). +/// Request to broadcast a message on whole server. +/// 0099 <packet len>.W <text>.?B 00 +void clif_parse_Broadcast(int fd, struct map_session_data* sd) { + char command[CHAT_SIZE_MAX+11]; + char* msg = (char*)RFIFOP(fd,4); + unsigned int len = RFIFOW(fd,2)-4; + + // as the length varies depending on the command used, just block unreasonably long strings + mes_len_check(msg, len, CHAT_SIZE_MAX); + + sprintf(command, "%ckami %s", atcommand_symbol, msg); + is_atcommand(fd, sd, command, 1); +} + + +/// Request to pick up an item. +/// 009f <id>.L (CZ_ITEM_PICKUP) +/// 0362 <id>.L (CZ_ITEM_PICKUP2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_TakeItem(int fd, struct map_session_data *sd) +{ + struct flooritem_data *fitem; + int map_object_id; + + map_object_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + fitem = (struct flooritem_data*)map_id2bl(map_object_id); + + do { + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + break; + } + + if (fitem == NULL || fitem->bl.type != BL_ITEM || fitem->bl.m != sd->bl.m) + break; + + if( sd->sc.cant.pickup ) + break; + + if (pc_cant_act(sd)) + break; + + if (!pc_takeitem(sd, fitem)) + break; + + return; + } while (0); + // Client REQUIRES a fail packet or you can no longer pick items. + clif_additem(sd,0,0,6); +} + + +/// Request to drop an item. +/// 00a2 <index>.W <amount>.W (CZ_ITEM_THROW) +/// 0363 <index>.W <amount>.W (CZ_ITEM_THROW2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_DropItem(int fd, struct map_session_data *sd) +{ + int item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + int item_amount = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + + for(;;) { + if (pc_isdead(sd)) + break; + + if (pc_cant_act(sd)) + break; + + if (sd->sc.count && ( + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_BLADESTOP] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOITEM) + )) + break; + + if (!pc_dropitem(sd, item_index, item_amount)) + break; + + return; + } + + //Because the client does not like being ignored. + clif_dropitem(sd, item_index,0); +} + + +/// Request to use an item. +/// 00a7 <index>.W <account id>.L (CZ_USE_ITEM) +/// 0439 <index>.W <account id>.L (CZ_USE_ITEM2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseItem(int fd, struct map_session_data *sd) +{ + int n; + + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + //This flag enables you to use items while in an NPC. [Skotlex] + if (sd->npc_id) { + if (sd->npc_id != sd->npc_item_flag) + return; + } + else if (pc_istrading(sd)) + return; + + //Whether the item is used or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + n = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + + if(n <0 || n >= MAX_INVENTORY) + return; + if (!pc_useitem(sd,n)) + clif_useitemack(sd,n,0,false); //Send an empty ack packet or the client gets stuck. +} + + +/// Request to equip an item (CZ_REQ_WEAR_EQUIP). +/// 00a9 <index>.W <position>.W +void clif_parse_EquipItem(int fd,struct map_session_data *sd) +{ + int index; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + index = RFIFOW(fd,2)-2; + if (index < 0 || index >= MAX_INVENTORY) + return; //Out of bounds check. + + if(sd->npc_id) { + if (sd->npc_id != sd->npc_item_flag) + return; + } else if (sd->state.storage_flag || sd->sc.opt1) + ; //You can equip/unequip stuff while storage is open/under status changes + else if (pc_cant_act(sd)) + return; + + if(!sd->status.inventory[index].identify) { + clif_equipitemack(sd,index,0,0); // fail + return; + } + + if(!sd->inventory_data[index]) + return; + + if(sd->inventory_data[index]->type == IT_PETARMOR){ + pet_equipitem(sd,index); + return; + } + + //Client doesn't send the position for ammo. + if(sd->inventory_data[index]->type == IT_AMMO) + pc_equipitem(sd,index,EQP_AMMO); + else + pc_equipitem(sd,index,RFIFOW(fd,4)); +} + + +/// Request to take off an equip (CZ_REQ_TAKEOFF_EQUIP). +/// 00ab <index>.W +void clif_parse_UnequipItem(int fd,struct map_session_data *sd) +{ + int index; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + + if (sd->state.storage_flag || sd->sc.opt1) + ; //You can equip/unequip stuff while storage is open/under status changes + else if (pc_cant_act(sd)) + return; + + index = RFIFOW(fd,2)-2; + + pc_unequipitem(sd,index,1); +} + + +/// Request to start a conversation with an NPC (CZ_CONTACTNPC). +/// 0090 <id>.L <type>.B +/// type: +/// 1 = click +void clif_parse_NpcClicked(int fd,struct map_session_data *sd) +{ + struct block_list *bl; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + + if (pc_cant_act(sd)) + return; + + bl = map_id2bl(RFIFOL(fd,2)); + if (!bl) return; + switch (bl->type) { + case BL_MOB: + case BL_PC: + clif_parse_ActionRequest_sub(sd, 0x07, bl->id, gettick()); + break; + case BL_NPC: + if( bl->m != -1 )// the user can't click floating npcs directly (hack attempt) + npc_click(sd,(TBL_NPC*)bl); + break; + } +} + + +/// Selection between buy/sell was made (CZ_ACK_SELECT_DEALTYPE). +/// 00c5 <id>.L <type>.B +/// type: +/// 0 = buy +/// 1 = sell +void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd) +{ + if (sd->state.trading) + return; + npc_buysellsel(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + + +/// Notification about the result of a purchase attempt from an NPC shop (ZC_PC_PURCHASE_RESULT). +/// 00ca <result>.B +/// result: +/// 0 = "The deal has successfully completed." +/// 1 = "You do not have enough zeny." +/// 2 = "You are over your Weight Limit." +/// 3 = "Out of the maximum capacity, you have too many items." +void clif_npc_buy_result(struct map_session_data* sd, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xca)); + WFIFOW(fd,0) = 0xca; + WFIFOB(fd,2) = result; + WFIFOSET(fd,packet_len(0xca)); +} + + +/// Request to buy chosen items from npc shop (CZ_PC_PURCHASE_ITEMLIST). +/// 00c8 <packet len>.W { <amount>.W <name id>.W }* +void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd) +{ + int n = (RFIFOW(fd,2)-4) /4; + unsigned short* item_list = (unsigned short*)RFIFOP(fd,4); + int result; + + if( sd->state.trading || !sd->npc_shopid ) + result = 1; + else + result = npc_buylist(sd,n,item_list); + + sd->npc_shopid = 0; //Clear shop data. + + clif_npc_buy_result(sd, result); +} + + +/// Notification about the result of a sell attempt to an NPC shop (ZC_PC_SELL_RESULT). +/// 00cb <result>.B +/// result: +/// 0 = "The deal has successfully completed." +/// 1 = "The deal has failed." +void clif_npc_sell_result(struct map_session_data* sd, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xcb)); + WFIFOW(fd,0) = 0xcb; + WFIFOB(fd,2) = result; + WFIFOSET(fd,packet_len(0xcb)); +} + + +/// Request to sell chosen items to npc shop (CZ_PC_SELL_ITEMLIST). +/// 00c9 <packet len>.W { <index>.W <amount>.W }* +void clif_parse_NpcSellListSend(int fd,struct map_session_data *sd) +{ + int fail=0,n; + unsigned short *item_list; + + n = (RFIFOW(fd,2)-4) /4; + item_list = (unsigned short*)RFIFOP(fd,4); + + if (sd->state.trading || !sd->npc_shopid) + fail = 1; + else + fail = npc_selllist(sd,n,item_list); + + sd->npc_shopid = 0; //Clear shop data. + + clif_npc_sell_result(sd, fail); +} + + +/// Chatroom creation request (CZ_CREATE_CHATROOM). +/// 00d5 <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B +/// type: +/// 0 = private +/// 1 = public +void clif_parse_CreateChatRoom(int fd, struct map_session_data* sd) +{ + int len = RFIFOW(fd,2)-15; + int limit = RFIFOW(fd,4); + bool pub = (RFIFOB(fd,6) != 0); + const char* password = (char*)RFIFOP(fd,7); //not zero-terminated + const char* title = (char*)RFIFOP(fd,15); // not zero-terminated + char s_password[CHATROOM_PASS_SIZE]; + char s_title[CHATROOM_TITLE_SIZE]; + + if (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) + return; + if(battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 4) { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,3); + return; + } + + if( len <= 0 ) + return; // invalid input + + safestrncpy(s_password, password, CHATROOM_PASS_SIZE); + safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte + + chat_createpcchat(sd, s_title, s_password, limit, pub); +} + + +/// Chatroom join request (CZ_REQ_ENTER_ROOM). +/// 00d9 <chat ID>.L <passwd>.8B +void clif_parse_ChatAddMember(int fd, struct map_session_data* sd) +{ + int chatid = RFIFOL(fd,2); + const char* password = (char*)RFIFOP(fd,6); // not zero-terminated + + chat_joinchat(sd,chatid,password); +} + + +/// Chatroom properties adjustment request (CZ_CHANGE_CHATROOM). +/// 00de <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B +/// type: +/// 0 = private +/// 1 = public +void clif_parse_ChatRoomStatusChange(int fd, struct map_session_data* sd) +{ + int len = RFIFOW(fd,2)-15; + int limit = RFIFOW(fd,4); + bool pub = (RFIFOB(fd,6) != 0); + const char* password = (char*)RFIFOP(fd,7); // not zero-terminated + const char* title = (char*)RFIFOP(fd,15); // not zero-terminated + char s_password[CHATROOM_PASS_SIZE]; + char s_title[CHATROOM_TITLE_SIZE]; + + if( len <= 0 ) + return; // invalid input + + safestrncpy(s_password, password, CHATROOM_PASS_SIZE); + safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte + + chat_changechatstatus(sd, s_title, s_password, limit, pub); +} + + +/// Request to change the chat room ownership (CZ_REQ_ROLE_CHANGE). +/// 00e0 <role>.L <nick>.24B +/// role: +/// 0 = owner +/// 1 = normal +void clif_parse_ChangeChatOwner(int fd, struct map_session_data* sd) +{ + chat_changechatowner(sd,(char*)RFIFOP(fd,6)); +} + + +/// Request to expel a player from chat room (CZ_REQ_EXPEL_MEMBER). +/// 00e2 <name>.24B +void clif_parse_KickFromChat(int fd,struct map_session_data *sd) +{ + chat_kickchat(sd,(char*)RFIFOP(fd,2)); +} + + +/// Request to leave the current chatroom (CZ_EXIT_ROOM). +/// 00e3 +void clif_parse_ChatLeave(int fd, struct map_session_data* sd) +{ + chat_leavechat(sd,0); +} + + +//Handles notifying asker and rejecter of what has just ocurred. +//Type is used to determine the correct msg_txt to use: +//0: +static void clif_noask_sub(struct map_session_data *src, struct map_session_data *target, int type) +{ + const char* msg; + char output[256]; + // Your request has been rejected by autoreject option. + msg = msg_txt(392); + clif_disp_onlyself(src, msg, strlen(msg)); + //Notice that a request was rejected. + snprintf(output, 256, msg_txt(393+type), src->status.name, 256); + clif_disp_onlyself(target, output, strlen(output)); +} + + +/// Request to begin a trade (CZ_REQ_EXCHANGE_ITEM). +/// 00e4 <account id>.L +void clif_parse_TradeRequest(int fd,struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + t_sd = map_id2sd(RFIFOL(fd,2)); + + if(!sd->chatID && pc_cant_act(sd)) + return; //You can trade while in a chatroom. + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 0); + return; + } + + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 1) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,0); + return; + } + + trade_traderequest(sd,t_sd); +} + + +/// Answer to a trade request (CZ_ACK_EXCHANGE_ITEM). +/// 00e6 <result>.B +/// result: +/// 3 = accepted +/// 4 = rejected +void clif_parse_TradeAck(int fd,struct map_session_data *sd) +{ + trade_tradeack(sd,RFIFOB(fd,2)); +} + + +/// Request to add an item to current trade (CZ_ADD_EXCHANGE_ITEM). +/// 00e8 <index>.W <amount>.L +void clif_parse_TradeAddItem(int fd,struct map_session_data *sd) +{ + short index = RFIFOW(fd,2); + int amount = RFIFOL(fd,4); + + if( index == 0 ) + trade_tradeaddzeny(sd, amount); + else + trade_tradeadditem(sd, index, (short)amount); +} + + +/// Request to lock items in current trade (CZ_CONCLUDE_EXCHANGE_ITEM). +/// 00eb +void clif_parse_TradeOk(int fd,struct map_session_data *sd) +{ + trade_tradeok(sd); +} + + +/// Request to cancel current trade (CZ_CANCEL_EXCHANGE_ITEM). +/// 00ed +void clif_parse_TradeCancel(int fd,struct map_session_data *sd) +{ + trade_tradecancel(sd); +} + + +/// Request to commit current trade (CZ_EXEC_EXCHANGE_ITEM). +/// 00ef +void clif_parse_TradeCommit(int fd,struct map_session_data *sd) +{ + trade_tradecommit(sd); +} + + +/// Request to stop chasing/attacking an unit (CZ_CANCEL_LOCKON). +/// 0118 +void clif_parse_StopAttack(int fd,struct map_session_data *sd) +{ + pc_stop_attack(sd); +} + + +/// Request to move an item from inventory to cart (CZ_MOVE_ITEM_FROM_BODY_TO_CART). +/// 0126 <index>.W <amount>.L +void clif_parse_PutItemToCart(int fd,struct map_session_data *sd) +{ + if (pc_istrading(sd)) + return; + if (!pc_iscarton(sd)) + return; + pc_putitemtocart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + + +/// Request to move an item from cart to inventory (CZ_MOVE_ITEM_FROM_CART_TO_BODY). +/// 0127 <index>.W <amount>.L +void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd) +{ + if (!pc_iscarton(sd)) + return; + pc_getitemfromcart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + + +/// Request to remove cart/falcon/peco/dragon (CZ_REQ_CARTOFF). +/// 012a +void clif_parse_RemoveOption(int fd,struct map_session_data *sd) +{ + /** + * Attempts to remove these options when this function is called (will remove all available) + **/ +#ifdef NEW_CARTS + pc_setoption(sd,sd->sc.option&~(OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); + if( sd->sc.data[SC_PUSH_CART] ) + pc_setcart(sd,0); +#else + pc_setoption(sd,sd->sc.option&~(OPTION_CART|OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); +#endif +} + + +/// Request to change cart's visual look (CZ_REQ_CHANGECART). +/// 01af <num>.W +void clif_parse_ChangeCart(int fd,struct map_session_data *sd) +{// TODO: State tracking? + int type; + + if( sd && pc_checkskill(sd, MC_CHANGECART) < 1 ) + return; + + type = (int)RFIFOW(fd,2); +#ifdef NEW_CARTS + if( (type == 9 && sd->status.base_level > 131) || + (type == 8 && sd->status.base_level > 121) || + (type == 7 && sd->status.base_level > 111) || + (type == 6 && sd->status.base_level > 101) || + (type == 5 && sd->status.base_level > 90) || + (type == 4 && sd->status.base_level > 80) || + (type == 3 && sd->status.base_level > 65) || + (type == 2 && sd->status.base_level > 40) || + (type == 1)) +#else + if( (type == 5 && sd->status.base_level > 90) || + (type == 4 && sd->status.base_level > 80) || + (type == 3 && sd->status.base_level > 65) || + (type == 2 && sd->status.base_level > 40) || + (type == 1)) +#endif + pc_setcart(sd,type); +} + + +/// Request to increase status (CZ_STATUS_CHANGE). +/// 00bb <status id>.W <amount>.B +/// status id: +/// SP_STR ~ SP_LUK +/// amount: +/// client sends always 1 for this, even when using /str+ and +/// the like +void clif_parse_StatusUp(int fd,struct map_session_data *sd) +{ + pc_statusup(sd,RFIFOW(fd,2)); +} + + +/// Request to increase level of a skill (CZ_UPGRADE_SKILLLEVEL). +/// 0112 <skill id>.W +void clif_parse_SkillUp(int fd,struct map_session_data *sd) +{ + pc_skillup(sd,RFIFOW(fd,2)); +} + +static void clif_parse_UseSkillToId_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) +{ + int lv; + + if( !hd ) + return; + if( skillnotok_hom(skill_id, hd) ) + return; + if( hd->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) + target_id = hd->bl.id; + if( hd->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } + else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) + return; + + lv = merc_hom_checkskill(hd, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_id(&hd->bl, target_id, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToPos_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) +{ + int lv; + if( !hd ) + return; + if( skillnotok_hom(skill_id, hd) ) + return; + if( hd->ud.skilltimer != INVALID_TIMER ) { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) + return; + + if( hd->sc.data[SC_BASILICA] ) + return; + lv = merc_hom_checkskill(hd, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_pos(&hd->bl, x, y, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToId_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) +{ + int lv; + + if( !md ) + return; + if( skillnotok_mercenary(skill_id, md) ) + return; + if( md->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) + target_id = md->bl.id; + if( md->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } + else if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) + return; + + lv = mercenary_checkskill(md, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_id(&md->bl, target_id, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) +{ + int lv; + if( !md ) + return; + if( skillnotok_mercenary(skill_id, md) ) + return; + if( md->ud.skilltimer != INVALID_TIMER ) + return; + if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) + { + clif_skill_fail(md->master, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + + if( md->sc.data[SC_BASILICA] ) + return; + lv = mercenary_checkskill(md, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_pos(&md->bl, x, y, skill_id, skill_lv); +} + + +/// Request to use a targeted skill. +/// 0113 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL) +/// 0438 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) +{ + uint16 skill_id, skill_lv; + int tmp, target_id; + unsigned int tick = gettick(); + + skill_lv = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + skill_id = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + target_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]); + + if( skill_lv < 1 ) skill_lv = 1; //No clue, I have seen the client do this with guild skills :/ [Skotlex] + + tmp = skill_get_inf(skill_id); + if (tmp&INF_GROUND_SKILL || !tmp) + return; //Using a ground/passive skill on a target? WRONG. + + if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) + { + clif_parse_UseSkillToId_homun(sd->hd, sd, tick, skill_id, skill_lv, target_id); + return; + } + + if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) + { + clif_parse_UseSkillToId_mercenary(sd->md, sd, tick, skill_id, skill_lv, target_id); + return; + } + + // Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + + if( pc_cant_act(sd) && skill_id != RK_REFRESH && !(skill_id == SR_GENTLETOUCH_CURE && (sd->sc.opt1 == OPT1_STONE || sd->sc.opt1 == OPT1_FREEZE || sd->sc.opt1 == OPT1_STUN)) ) + return; + if( pc_issit(sd) ) + return; + + if( skillnotok(skill_id, sd) ) + return; + + if( sd->bl.id != target_id && tmp&INF_SELF_SKILL ) + target_id = sd->bl.id; // never trust the client + + if( target_id < 0 && -target_id == sd->bl.id ) // for disguises [Valaris] + target_id = sd->bl.id; + + if( sd->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) + return; + } + else if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) + { + if( sd->skillitem != skill_id ) + { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) + return; // On basilica only caster can use Basilica again to stop it. + + if( sd->menuskill_id ) { + if( sd->menuskill_id == SA_TAMINGMONSTER ) { + clif_menuskill_clear(sd); //Cancel pet capture. + } else if( sd->menuskill_id != SA_AUTOSPELL ) + return; //Can't use skills while a menu is open. + } + if( sd->skillitem == skill_id ) { + if( skill_lv != sd->skillitemlv ) + skill_lv = sd->skillitemlv; + if( !(tmp&INF_SELF_SKILL) ) + pc_delinvincibletimer(sd); // Target skills thru items cancel invincibility. [Inkfish] + unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); + return; + } + + sd->skillitem = sd->skillitemlv = 0; + + if( skill_id >= GD_SKILLBASE ) { + if( sd->state.gmaster_flag ) + skill_lv = guild_checkskill(sd->state.gmaster_flag, skill_id); + else + skill_lv = 0; + } else { + tmp = pc_checkskill(sd, skill_id); + if( skill_lv > tmp ) + skill_lv = tmp; + } + + pc_delinvincibletimer(sd); + + if( skill_lv ) + unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); +} + +/*========================================== + * Client tells server he'd like to use AoE skill id 'skill_id' of level 'skill_lv' on 'x','y' location + *------------------------------------------*/ +static void clif_parse_UseSkillToPosSub(int fd, struct map_session_data *sd, uint16 skill_lv, uint16 skill_id, short x, short y, int skillmoreinfo) +{ + unsigned int tick = gettick(); + + if( !(skill_get_inf(skill_id)&INF_GROUND_SKILL) ) + return; //Using a target skill on the ground? WRONG. + + if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) { + clif_parse_UseSkillToPos_homun(sd->hd, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); + return; + } + + if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) + { + clif_parse_UseSkillToPos_mercenary(sd->md, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); + return; + } + + //Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + + if( skillnotok(skill_id, sd) ) + return; + if( skillmoreinfo != -1 ) + { + if( pc_issit(sd) ) + { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + return; + } + //You can't use Graffiti/TalkieBox AND have a vending open, so this is safe. + safestrncpy(sd->message, (char*)RFIFOP(fd,skillmoreinfo), MESSAGE_SIZE); + } + + if( sd->ud.skilltimer != INVALID_TIMER ) + return; + + if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) { + if( sd->skillitem != skill_id ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) + return; // On basilica only caster can use Basilica again to stop it. + + if( sd->menuskill_id ) { + if( sd->menuskill_id == SA_TAMINGMONSTER ) { + clif_menuskill_clear(sd); //Cancel pet capture. + } else if( sd->menuskill_id != SA_AUTOSPELL ) + return; //Can't use skills while a menu is open. + } + + pc_delinvincibletimer(sd); + + if( sd->skillitem == skill_id ) { + if( skill_lv != sd->skillitemlv ) + skill_lv = sd->skillitemlv; + unit_skilluse_pos(&sd->bl, x, y, skill_id, skill_lv); + } else { + int lv; + sd->skillitem = sd->skillitemlv = 0; + if( (lv = pc_checkskill(sd, skill_id)) > 0 ) { + if( skill_lv > lv ) + skill_lv = lv; + unit_skilluse_pos(&sd->bl, x, y, skill_id,skill_lv); + } + } +} + + +/// Request to use a ground skill. +/// 0116 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND) +/// 0366 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToPos(int fd, struct map_session_data *sd) +{ + if (pc_cant_act(sd)) + return; + if (pc_issit(sd)) + return; + + clif_parse_UseSkillToPosSub(fd, sd, + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //skill lv + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //skill num + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y + -1 //Skill more info. + ); +} + + +/// Request to use a ground skill with text. +/// 0190 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX) +/// 0367 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToPosMoreInfo(int fd, struct map_session_data *sd) +{ + if (pc_cant_act(sd)) + return; + if (pc_issit(sd)) + return; + + clif_parse_UseSkillToPosSub(fd, sd, + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //Skill lv + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //Skill num + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y + packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[4] //skill more info + ); +} + + +/// Answer to map selection dialog (CZ_SELECT_WARPPOINT). +/// 011b <skill id>.W <map name>.16B +void clif_parse_UseSkillMap(int fd, struct map_session_data* sd) +{ + uint16 skill_id = RFIFOW(fd,2); + char map_name[MAP_NAME_LENGTH]; + mapindex_getmapname((char*)RFIFOP(fd,4), map_name); + + if(skill_id != sd->menuskill_id) + return; + + if( pc_cant_act(sd) ) { + clif_menuskill_clear(sd); + return; + } + + pc_delinvincibletimer(sd); + skill_castend_map(sd,skill_id,map_name); +} + + +/// Request to set a memo on current map (CZ_REMEMBER_WARPPOINT). +/// 011d +void clif_parse_RequestMemo(int fd,struct map_session_data *sd) +{ + if (!pc_isdead(sd)) + pc_memo(sd,-1); +} + + +/// Answer to pharmacy item selection dialog (CZ_REQMAKINGITEM). +/// 018e <name id>.W { <material id>.W }*3 +void clif_parse_ProduceMix(int fd,struct map_session_data *sd) +{ + switch( sd->menuskill_id ) { + case -1: + case AM_PHARMACY: + case RK_RUNEMASTERY: + case GC_RESEARCHNEWPOISON: + break; + default: + return; + } + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + if( skill_can_produce_mix(sd,RFIFOW(fd,2),sd->menuskill_val, 1) ) + skill_produce_mix(sd,0,RFIFOW(fd,2),RFIFOW(fd,4),RFIFOW(fd,6),RFIFOW(fd,8), 1); + clif_menuskill_clear(sd); +} + + +/// Answer to mixing item selection dialog (CZ_REQ_MAKINGITEM). +/// 025b <mk type>.W <name id>.W +/// mk type: +/// 1 = cooking +/// 2 = arrow +/// 3 = elemental +/// 4 = GN_MIX_COOKING +/// 5 = GN_MAKEBOMB +/// 6 = GN_S_PHARMACY +void clif_parse_Cooking(int fd,struct map_session_data *sd) { + int type = RFIFOW(fd,2); + int nameid = RFIFOW(fd,4); + int amount = sd->menuskill_val2?sd->menuskill_val2:1; + if( type == 6 && sd->menuskill_id != GN_MIX_COOKING && sd->menuskill_id != GN_S_PHARMACY ) + return; + + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + if( skill_can_produce_mix(sd,nameid,sd->menuskill_val, amount) ) + skill_produce_mix(sd,sd->menuskill_id,nameid,0,0,0,amount); + clif_menuskill_clear(sd); +} + + +/// Answer to repair weapon item selection dialog (CZ_REQ_ITEMREPAIR). +/// 01fd <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W +void clif_parse_RepairItem(int fd, struct map_session_data *sd) +{ + if (sd->menuskill_id != BS_REPAIRWEAPON) + return; + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + skill_repairweapon(sd,RFIFOW(fd,2)); + clif_menuskill_clear(sd); +} + + +/// Answer to refine weapon item selection dialog (CZ_REQ_WEAPONREFINE). +/// 0222 <index>.L +void clif_parse_WeaponRefine(int fd, struct map_session_data *sd) +{ + int idx; + + if (sd->menuskill_id != WS_WEAPONREFINE) //Packet exploit? + return; + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + idx = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + skill_weaponrefine(sd, idx-2); + clif_menuskill_clear(sd); +} + + +/// Answer to script menu dialog (CZ_CHOOSE_MENU). +/// 00b8 <npc id>.L <choice>.B +/// choice: +/// 1~254 = menu item +/// 255 = cancel +/// NOTE: If there were more than 254 items in the list, choice +/// overflows to choice%256. +void clif_parse_NpcSelectMenu(int fd,struct map_session_data *sd) +{ + int npc_id = RFIFOL(fd,2); + uint8 select = RFIFOB(fd,6); + + if( (select > sd->npc_menu && select != 0xff) || select == 0 ) + { + TBL_NPC* nd = map_id2nd(npc_id); + ShowWarning("Invalid menu selection on npc %d:'%s' - got %d, valid range is [%d..%d] (player AID:%d, CID:%d, name:'%s')!\n", npc_id, (nd)?nd->name:"invalid npc id", select, 1, sd->npc_menu, sd->bl.id, sd->status.char_id, sd->status.name); + clif_GM_kick(NULL,sd); + return; + } + + sd->npc_menu = select; + npc_scriptcont(sd,npc_id); +} + + +/// NPC dialog 'next' click (CZ_REQ_NEXT_SCRIPT). +/// 00b9 <npc id>.L +void clif_parse_NpcNextClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + + +/// NPC numeric input dialog value (CZ_INPUT_EDITDLG). +/// 0143 <npc id>.L <value>.L +void clif_parse_NpcAmountInput(int fd,struct map_session_data *sd) +{ + int npcid = RFIFOL(fd,2); + int amount = (int)RFIFOL(fd,6); + + sd->npc_amount = amount; + npc_scriptcont(sd, npcid); +} + + +/// NPC text input dialog value (CZ_INPUT_EDITDLGSTR). +/// 01d5 <packet len>.W <npc id>.L <string>.?B +void clif_parse_NpcStringInput(int fd, struct map_session_data* sd) +{ + int message_len = RFIFOW(fd,2)-8; + int npcid = RFIFOL(fd,4); + const char* message = (char*)RFIFOP(fd,8); + + if( message_len <= 0 ) + return; // invalid input + + safestrncpy(sd->npc_str, message, min(message_len,CHATBOX_SIZE)); + npc_scriptcont(sd, npcid); +} + + +/// NPC dialog 'close' click (CZ_CLOSE_DIALOG). +/// 0146 <npc id>.L +void clif_parse_NpcCloseClicked(int fd,struct map_session_data *sd) +{ + if (!sd->npc_id) //Avoid parsing anything when the script was done with. [Skotlex] + return; + npc_scriptcont(sd,RFIFOL(fd,2)); +} + + +/// Answer to identify item selection dialog (CZ_REQ_ITEMIDENTIFY). +/// 0178 <index>.W +/// index: +/// -1 = cancel +void clif_parse_ItemIdentify(int fd,struct map_session_data *sd) +{ + short idx = RFIFOW(fd,2); + + if (sd->menuskill_id != MC_IDENTIFY) + return; + if( idx == -1 ) {// cancel pressed + clif_menuskill_clear(sd); + return; + } + skill_identify(sd,idx-2); + clif_menuskill_clear(sd); +} + + +/// Answer to arrow crafting item selection dialog (CZ_REQ_MAKINGARROW). +/// 01ae <name id>.W +void clif_parse_SelectArrow(int fd,struct map_session_data *sd) +{ + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + switch( sd->menuskill_id ) { + case AC_MAKINGARROW: + skill_arrow_create(sd,RFIFOW(fd,2)); + break; + case SA_CREATECON: + skill_produce_mix(sd,SA_CREATECON,RFIFOW(fd,2),0,0,0, 1); + break; + case WL_READING_SB: + skill_spellbook(sd,RFIFOW(fd,2)); + break; + case GC_POISONINGWEAPON: + skill_poisoningweapon(sd,RFIFOW(fd,2)); + break; + case NC_MAGICDECOY: + skill_magicdecoy(sd,RFIFOW(fd,2)); + break; + } + + clif_menuskill_clear(sd); +} + + +/// Answer to SA_AUTOSPELL skill selection dialog (CZ_SELECTAUTOSPELL). +/// 01ce <skill id>.L +void clif_parse_AutoSpell(int fd,struct map_session_data *sd) +{ + if (sd->menuskill_id != SA_AUTOSPELL) + return; + skill_autospell(sd,RFIFOL(fd,2)); + clif_menuskill_clear(sd); +} + + +/// Request to display item carding/composition list (CZ_REQ_ITEMCOMPOSITION_LIST). +/// 017a <card index>.W +void clif_parse_UseCard(int fd,struct map_session_data *sd) +{ + if (sd->state.trading != 0) + return; + clif_use_card(sd,RFIFOW(fd,2)-2); +} + + +/// Answer to carding/composing item selection dialog (CZ_REQ_ITEMCOMPOSITION). +/// 017c <card index>.W <equip index>.W +void clif_parse_InsertCard(int fd,struct map_session_data *sd) +{ + if (sd->state.trading != 0) + return; + pc_insert_card(sd,RFIFOW(fd,2)-2,RFIFOW(fd,4)-2); +} + + +/// Request of character's name by char ID. +/// 0193 <char id>.L (CZ_REQNAME_BYGID) +/// 0369 <char id>.L (CZ_REQNAME_BYGID2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_SolveCharName(int fd, struct map_session_data *sd) +{ + int charid; + + charid = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + map_reqnickdb(sd, charid); +} + + +/// /resetskill /resetstate (CZ_RESET). +/// Request to reset stats or skills. +/// 0197 <type>.W +/// type: +/// 0 = state +/// 1 = skill +void clif_parse_ResetChar(int fd, struct map_session_data *sd) { + char cmd[15]; + + if( RFIFOW(fd,2) ) + sprintf(cmd,"%cresetskill",atcommand_symbol); + else + sprintf(cmd,"%cresetstat",atcommand_symbol); + + is_atcommand(fd, sd, cmd, 1); +} + + +/// /lb /nlb (CZ_LOCALBROADCAST). +/// Request to broadcast a message on current map. +/// 019c <packet len>.W <text>.?B +void clif_parse_LocalBroadcast(int fd, struct map_session_data* sd) +{ + char command[CHAT_SIZE_MAX+16]; + char* msg = (char*)RFIFOP(fd,4); + unsigned int len = RFIFOW(fd,2)-4; + + // as the length varies depending on the command used, just block unreasonably long strings + mes_len_check(msg, len, CHAT_SIZE_MAX); + + sprintf(command, "%clkami %s", atcommand_symbol, msg); + is_atcommand(fd, sd, command, 1); +} + + +/// Request to move an item from inventory to storage. +/// 00f3 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE) +/// 0364 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) +{ + int item_index, item_amount; + + if (pc_istrading(sd)) + return; + + item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + if (item_index < 0 || item_index >= MAX_INVENTORY || item_amount < 1) + return; + + if (sd->state.storage_flag == 1) + storage_storageadd(sd, item_index, item_amount); + else + if (sd->state.storage_flag == 2) + storage_guild_storageadd(sd, item_index, item_amount); +} + + +/// Request to move an item from storage to inventory. +/// 00f5 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY) +/// 0365 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_MoveFromKafra(int fd,struct map_session_data *sd) +{ + int item_index, item_amount; + + item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-1; + item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + + if (sd->state.storage_flag == 1) + storage_storageget(sd, item_index, item_amount); + else + if(sd->state.storage_flag == 2) + storage_guild_storageget(sd, item_index, item_amount); +} + + +/// Request to move an item from cart to storage (CZ_MOVE_ITEM_FROM_CART_TO_STORE). +/// 0129 <index>.W <amount>.L +void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd) +{ + if( sd->state.vending ) + return; + if (!pc_iscarton(sd)) + return; + + if (sd->state.storage_flag == 1) + storage_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); + else + if (sd->state.storage_flag == 2) + storage_guild_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); +} + + +/// Request to move an item from storage to cart (CZ_MOVE_ITEM_FROM_STORE_TO_CART). +/// 0128 <index>.W <amount>.L +void clif_parse_MoveFromKafraToCart(int fd, struct map_session_data *sd) +{ + if( sd->state.vending ) + return; + if (!pc_iscarton(sd)) + return; + + if (sd->state.storage_flag == 1) + storage_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); + else + if (sd->state.storage_flag == 2) + storage_guild_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); +} + + +/// Request to close storage (CZ_CLOSE_STORE). +/// 00f7 +void clif_parse_CloseKafra(int fd, struct map_session_data *sd) +{ + if( sd->state.storage_flag == 1 ) + storage_storageclose(sd); + else + if( sd->state.storage_flag == 2 ) + storage_guild_storageclose(sd); +} + + +/// Displays kafra storage password dialog (ZC_REQ_STORE_PASSWORD). +/// 023a <info>.W +/// info: +/// 0 = password has not been set yet +/// 1 = storage is password-protected +/// 8 = too many wrong passwords +/// ? = ignored +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_storagepassword(struct map_session_data* sd, short info) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x23a)); + WFIFOW(fd,0) = 0x23a; + WFIFOW(fd,2) = info; + WFIFOSET(fd,packet_len(0x23a)); +} + + +/// Answer to the kafra storage password dialog (CZ_ACK_STORE_PASSWORD). +/// 023b <type>.W <password>.16B <new password>.16B +/// type: +/// 2 = change password +/// 3 = check password +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_parse_StoragePassword(int fd, struct map_session_data *sd) +{ + //TODO +} + + +/// Result of kafra storage password validation (ZC_RESULT_STORE_PASSWORD). +/// 023c <result>.W <error count>.W +/// result: +/// 4 = password change success +/// 5 = password change failure +/// 6 = password check success +/// 7 = password check failure +/// 8 = too many wrong passwords +/// ? = ignored +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_storagepassword_result(struct map_session_data* sd, short result, short error_count) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x23c)); + WFIFOW(fd,0) = 0x23c; + WFIFOW(fd,2) = result; + WFIFOW(fd,4) = error_count; + WFIFOSET(fd,packet_len(0x23c)); +} + + +/// Party creation request +/// 00f9 <party name>.24B (CZ_MAKE_GROUP) +/// 01e8 <party name>.24B <item pickup rule>.B <item share rule>.B (CZ_MAKE_GROUP2) +void clif_parse_CreateParty(int fd, struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,2); + name[NAME_LENGTH-1] = '\0'; + + if( map[sd->bl.m].flag.partylock ) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); + return; + } + + party_create(sd,name,0,0); +} + +void clif_parse_CreateParty2(int fd, struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,2); + int item1 = RFIFOB(fd,26); + int item2 = RFIFOB(fd,27); + name[NAME_LENGTH-1] = '\0'; + + if( map[sd->bl.m].flag.partylock ) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); + return; + } + + party_create(sd,name,item1,item2); +} + + +/// Party invitation request +/// 00fc <account id>.L (CZ_REQ_JOIN_GROUP) +/// 02c4 <char name>.24B (CZ_PARTY_JOIN_REQ) +void clif_parse_PartyInvite(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(map[sd->bl.m].flag.partylock) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + if(t_sd && t_sd->state.noask) + {// @noask [LuzZza] + clif_noask_sub(sd, t_sd, 1); + return; + } + + party_invite(sd, t_sd); +} + +void clif_parse_PartyInvite2(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + char *name = (char*)RFIFOP(fd,2); + name[NAME_LENGTH-1] = '\0'; + + if(map[sd->bl.m].flag.partylock) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + + t_sd = map_nick2sd(name); + + if(t_sd && t_sd->state.noask) + {// @noask [LuzZza] + clif_noask_sub(sd, t_sd, 1); + return; + } + + party_invite(sd, t_sd); +} + + +/// Party invitation reply +/// 00ff <party id>.L <flag>.L (CZ_JOIN_GROUP) +/// 02c7 <party id>.L <flag>.B (CZ_PARTY_JOIN_REQ_ACK) +/// flag: +/// 0 = reject +/// 1 = accept +void clif_parse_ReplyPartyInvite(int fd,struct map_session_data *sd) +{ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +void clif_parse_ReplyPartyInvite2(int fd,struct map_session_data *sd) +{ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + + +/// Request to leave party (CZ_REQ_LEAVE_GROUP). +/// 0100 +void clif_parse_LeaveParty(int fd, struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.partylock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + party_leave(sd); +} + + +/// Request to expel a party member (CZ_REQ_EXPEL_GROUP_MEMBER). +/// 0103 <account id>.L <char name>.24B +void clif_parse_RemovePartyMember(int fd, struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.partylock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + party_removemember(sd,RFIFOL(fd,2),(char*)RFIFOP(fd,6)); +} + + +/// Request to change party options. +/// 0102 <exp share rule>.L (CZ_CHANGE_GROUPEXPOPTION) +/// 07d7 <exp share rule>.L <item pickup rule>.B <item share rule>.B (CZ_GROUPINFO_CHANGE_V2) +void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) +{ + struct party_data *p; + int i; + + if( !sd->status.party_id ) + return; + + p = party_search(sd->status.party_id); + if( p == NULL ) + return; + + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); + if( i == MAX_PARTY ) + return; //Shouldn't happen + + if( !p->party.member[i].leader ) + return; + +#if PACKETVER < 20090603 + //Client can't change the item-field + party_changeoption(sd, RFIFOL(fd,2), p->party.item); +#else + party_changeoption(sd, RFIFOL(fd,2), ((RFIFOB(fd,6)?1:0)|(RFIFOB(fd,7)?2:0))); +#endif +} + + +/// Validates and processes party messages (CZ_REQUEST_CHAT_PARTY). +/// 0108 <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_PartyMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + party_send_message(sd, text, textlen); +} + + +/// Changes Party Leader (CZ_CHANGE_GROUP_MASTER). +/// 07da <account id>.L +void clif_parse_PartyChangeLeader(int fd, struct map_session_data* sd) +{ + party_changeleader(sd, map_id2sd(RFIFOL(fd,2))); +} + + +/// Party Booking in KRO [Spiria] +/// + +/// Request to register a party booking advertisment (CZ_PARTY_BOOKING_REQ_REGISTER). +/// 0802 <level>.W <map id>.W { <job>.W }*6 +void clif_parse_PartyBookingRegisterReq(int fd, struct map_session_data* sd) +{ + short level = RFIFOW(fd,2); + short mapid = RFIFOW(fd,4); + short job[PARTY_BOOKING_JOBS]; + int i; + + for(i=0; i<PARTY_BOOKING_JOBS; i++) + job[i] = RFIFOB(fd,6+i*2); + + party_booking_register(sd, level, mapid, job); +} + + +/// Result of request to register a party booking advertisment (ZC_PARTY_BOOKING_ACK_REGISTER). +/// 0803 <result>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = already registered +void clif_PartyBookingRegisterAck(struct map_session_data *sd, int flag) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x803)); + WFIFOW(fd,0) = 0x803; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x803)); +} + + +/// Request to search for party booking advertisments (CZ_PARTY_BOOKING_REQ_SEARCH). +/// 0804 <level>.W <map id>.W <job>.W <last index>.L <result count>.W +void clif_parse_PartyBookingSearchReq(int fd, struct map_session_data* sd) +{ + short level = RFIFOW(fd,2); + short mapid = RFIFOW(fd,4); + short job = RFIFOW(fd,6); + unsigned long lastindex = RFIFOL(fd,8); + short resultcount = RFIFOW(fd,12); + + party_booking_search(sd, level, mapid, job, lastindex, resultcount); +} + + +/// Party booking search results (ZC_PARTY_BOOKING_ACK_SEARCH). +/// 0805 <packet len>.W <more results>.B { <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 }* +/// more results: +/// 0 = no +/// 1 = yes +void clif_PartyBookingSearchAck(int fd, struct party_booking_ad_info** results, int count, bool more_result) +{ + int i, j; + int size = sizeof(struct party_booking_ad_info); // structure size (48) + struct party_booking_ad_info *pb_ad; + WFIFOHEAD(fd,size*count + 5); + WFIFOW(fd,0) = 0x805; + WFIFOW(fd,2) = size*count + 5; + WFIFOB(fd,4) = more_result; + for(i=0; i<count; i++) + { + pb_ad = results[i]; + WFIFOL(fd,i*size+5) = pb_ad->index; + memcpy(WFIFOP(fd,i*size+9),pb_ad->charname,NAME_LENGTH); + WFIFOL(fd,i*size+33) = pb_ad->starttime; // FIXME: This is expire time + WFIFOW(fd,i*size+37) = pb_ad->p_detail.level; + WFIFOW(fd,i*size+39) = pb_ad->p_detail.mapid; + for(j=0; j<PARTY_BOOKING_JOBS; j++) + WFIFOW(fd,i*size+41+j*2) = pb_ad->p_detail.job[j]; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Request to delete own party booking advertisment (CZ_PARTY_BOOKING_REQ_DELETE). +/// 0806 +void clif_parse_PartyBookingDeleteReq(int fd, struct map_session_data* sd) +{ + if(party_booking_delete(sd)) + clif_PartyBookingDeleteAck(sd, 0); +} + + +/// Result of request to delete own party booking advertisment (ZC_PARTY_BOOKING_ACK_DELETE). +/// 0807 <result>.W +/// result: +/// 0 = success +/// 1 = success (auto-removed expired ad) +/// 2 = failure +/// 3 = nothing registered +void clif_PartyBookingDeleteAck(struct map_session_data* sd, int flag) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x807)); + WFIFOW(fd,0) = 0x807; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x807)); +} + + +/// Request to update party booking advertisment (CZ_PARTY_BOOKING_REQ_UPDATE). +/// 0808 { <job>.W }*6 +void clif_parse_PartyBookingUpdateReq(int fd, struct map_session_data* sd) +{ + short job[PARTY_BOOKING_JOBS]; + int i; + + for(i=0; i<PARTY_BOOKING_JOBS; i++) + job[i] = RFIFOW(fd,2+i*2); + + party_booking_update(sd, job); +} + + +/// Notification about new party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_INSERT). +/// 0809 <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 +void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) +{ + int i; + uint8 buf[38+PARTY_BOOKING_JOBS*2]; + + if(pb_ad == NULL) return; + + WBUFW(buf,0) = 0x809; + WBUFL(buf,2) = pb_ad->index; + memcpy(WBUFP(buf,6),pb_ad->charname,NAME_LENGTH); + WBUFL(buf,30) = pb_ad->starttime; // FIXME: This is expire time + WBUFW(buf,34) = pb_ad->p_detail.level; + WBUFW(buf,36) = pb_ad->p_detail.mapid; + for(i=0; i<PARTY_BOOKING_JOBS; i++) + WBUFW(buf,38+i*2) = pb_ad->p_detail.job[i]; + + clif_send(buf, packet_len(0x809), &sd->bl, ALL_CLIENT); +} + + +/// Notification about updated party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_UPDATE). +/// 080a <index>.L { <job>.W }*6 +void clif_PartyBookingUpdateNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) +{ + int i; + uint8 buf[6+PARTY_BOOKING_JOBS*2]; + + if(pb_ad == NULL) return; + + WBUFW(buf,0) = 0x80a; + WBUFL(buf,2) = pb_ad->index; + for(i=0; i<PARTY_BOOKING_JOBS; i++) + WBUFW(buf,6+i*2) = pb_ad->p_detail.job[i]; + clif_send(buf,packet_len(0x80a),&sd->bl,ALL_CLIENT); // Now UPDATE all client. +} + + +/// Notification about deleted party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_DELETE). +/// 080b <index>.L +void clif_PartyBookingDeleteNotify(struct map_session_data* sd, int index) +{ + uint8 buf[6]; + + WBUFW(buf,0) = 0x80b; + WBUFL(buf,2) = index; + + clif_send(buf, packet_len(0x80b), &sd->bl, ALL_CLIENT); // Now UPDATE all client. +} + + +/// Request to close own vending (CZ_REQ_CLOSESTORE). +/// 012e +void clif_parse_CloseVending(int fd, struct map_session_data* sd) +{ + vending_closevending(sd); +} + + +/// Request to open a vending shop (CZ_REQ_BUY_FROMMC). +/// 0130 <account id>.L +void clif_parse_VendingListReq(int fd, struct map_session_data* sd) +{ + if( sd->npc_id ) + {// using an NPC + return; + } + vending_vendinglistreq(sd,RFIFOL(fd,2)); +} + + +/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC). +/// 0134 <packet len>.W <account id>.L { <amount>.W <index>.W }* +void clif_parse_PurchaseReq(int fd, struct map_session_data* sd) +{ + int len = (int)RFIFOW(fd,2) - 8; + int id = (int)RFIFOL(fd,4); + const uint8* data = (uint8*)RFIFOP(fd,8); + + vending_purchasereq(sd, id, sd->vended_id, data, len/4); + + // whether it fails or not, the buy window is closed + sd->vended_id = 0; +} + + +/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC2). +/// 0801 <packet len>.W <account id>.L <unique id>.L { <amount>.W <index>.W }* +void clif_parse_PurchaseReq2(int fd, struct map_session_data* sd) +{ + int len = (int)RFIFOW(fd,2) - 12; + int aid = (int)RFIFOL(fd,4); + int uid = (int)RFIFOL(fd,8); + const uint8* data = (uint8*)RFIFOP(fd,12); + + vending_purchasereq(sd, aid, uid, data, len/4); + + // whether it fails or not, the buy window is closed + sd->vended_id = 0; +} + + +/// Confirm or cancel the shop preparation window. +/// 012f <packet len>.W <shop name>.80B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE) +/// 01b2 <packet len>.W <shop name>.80B <result>.B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE2) +/// result: +/// 0 = canceled +/// 1 = open +void clif_parse_OpenVending(int fd, struct map_session_data* sd) +{ + short len = (short)RFIFOW(fd,2) - 85; + const char* message = (char*)RFIFOP(fd,4); + bool flag = (bool)RFIFOB(fd,84); + const uint8* data = (uint8*)RFIFOP(fd,85); + + if( sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM ) + return; + if( map[sd->bl.m].flag.novending ) { + clif_displaymessage (sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return; + } + if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNOVENDING) ) { + clif_displaymessage (sd->fd, msg_txt(204)); // "You can't open a shop on this cell." + return; + } + + if( vending_checknearnpc(&sd->bl) ) { + char output[150]; + sprintf(output, msg_txt(662), battle_config.min_npc_vending_distance); + clif_displaymessage(sd->fd, output); + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); + return; + } + + if( message[0] == '\0' ) // invalid input + return; + + vending_openvending(sd, message, flag, data, len/8); +} + + +/// Guild creation request (CZ_REQ_MAKE_GUILD). +/// 0165 <char id>.L <guild name>.24B +void clif_parse_CreateGuild(int fd,struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,6); + name[NAME_LENGTH-1] = '\0'; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + guild_create(sd, name); +} + + +/// Request for guild window interface permissions (CZ_REQ_GUILD_MENUINTERFACE). +/// 014d +void clif_parse_GuildCheckMaster(int fd, struct map_session_data *sd) +{ + clif_guild_masterormember(sd); +} + + +/// Request for guild window information (CZ_REQ_GUILD_MENU). +/// 014f <type>.L +/// type: +/// 0 = basic info +/// 1 = member manager +/// 2 = positions +/// 3 = skills +/// 4 = expulsion list +/// 5 = unknown (GM_ALLGUILDLIST) +/// 6 = notice +void clif_parse_GuildRequestInfo(int fd, struct map_session_data *sd) +{ + if( !sd->status.guild_id && !sd->bg_id ) + return; + + switch( RFIFOL(fd,2) ) + { + case 0: // Basic Information Guild, hostile alliance information + clif_guild_basicinfo(sd); + clif_guild_allianceinfo(sd); + break; + case 1: // Members list, list job title + clif_guild_positionnamelist(sd); + clif_guild_memberlist(sd); + break; + case 2: // List job title, title information list + clif_guild_positionnamelist(sd); + clif_guild_positioninfolist(sd); + break; + case 3: // Skill list + clif_guild_skillinfo(sd); + break; + case 4: // Expulsion list + clif_guild_expulsionlist(sd); + break; + default: + ShowError("clif: guild request info: unknown type %d\n", RFIFOL(fd,2)); + break; + } +} + + +/// Request to update guild positions (CZ_REG_CHANGE_GUILD_POSITIONINFO). +/// 0161 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <name>.24B }* +void clif_parse_GuildChangePositionInfo(int fd, struct map_session_data *sd) +{ + int i; + + if(!sd->state.gmaster_flag) + return; + + for(i = 4; i < RFIFOW(fd,2); i += 40 ){ + guild_change_position(sd->status.guild_id, RFIFOL(fd,i), RFIFOL(fd,i+4), RFIFOL(fd,i+12), (char*)RFIFOP(fd,i+16)); + } +} + + +/// Request to update the position of guild members (CZ_REQ_CHANGE_MEMBERPOS). +/// 0155 <packet len>.W { <account id>.L <char id>.L <position id>.L }* +void clif_parse_GuildChangeMemberPosition(int fd, struct map_session_data *sd) +{ + int i; + + if(!sd->state.gmaster_flag) + return; + + for(i=4;i<RFIFOW(fd,2);i+=12){ + guild_change_memberposition(sd->status.guild_id, + RFIFOL(fd,i),RFIFOL(fd,i+4),RFIFOL(fd,i+8)); + } +} + + +/// Request for guild emblem data (CZ_REQ_GUILD_EMBLEM_IMG). +/// 0151 <guild id>.L +void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) +{ + struct guild* g; + int guild_id = RFIFOL(fd,2); + + if( (g = guild_search(guild_id)) != NULL ) + clif_guild_emblem(sd,g); +} + + +/// Validates data of a guild emblem (compressed bitmap) +static bool clif_validate_emblem(const uint8* emblem, unsigned long emblem_len) +{ + bool success; + uint8 buf[1800]; // no well-formed emblem bitmap is larger than 1782 (24 bit) / 1654 (8 bit) bytes + unsigned long buf_len = sizeof(buf); + + success = ( decode_zip(buf, &buf_len, emblem, emblem_len) == 0 && buf_len >= 18 ) // sizeof(BITMAPFILEHEADER) + sizeof(biSize) of the following info header struct + && RBUFW(buf,0) == 0x4d42 // BITMAPFILEHEADER.bfType (signature) + && RBUFL(buf,2) == buf_len // BITMAPFILEHEADER.bfSize (file size) + && RBUFL(buf,10) < buf_len // BITMAPFILEHEADER.bfOffBits (offset to bitmap bits) + ; + + return success; +} + + +/// Request to update the guild emblem (CZ_REGISTER_GUILD_EMBLEM_IMG). +/// 0153 <packet len>.W <emblem data>.?B +void clif_parse_GuildChangeEmblem(int fd,struct map_session_data *sd) +{ + unsigned long emblem_len = RFIFOW(fd,2)-4; + const uint8* emblem = RFIFOP(fd,4); + + if( !emblem_len || !sd->state.gmaster_flag ) + return; + + if( !clif_validate_emblem(emblem, emblem_len) ) + { + ShowWarning("clif_parse_GuildChangeEmblem: Rejected malformed guild emblem (size=%lu, accound_id=%d, char_id=%d, guild_id=%d).\n", emblem_len, sd->status.account_id, sd->status.char_id, sd->status.guild_id); + return; + } + + guild_change_emblem(sd, emblem_len, (const char*)emblem); +} + + +/// Guild notice update request (CZ_GUILD_NOTICE). +/// 016e <guild id>.L <msg1>.60B <msg2>.120B +void clif_parse_GuildChangeNotice(int fd, struct map_session_data* sd) +{ + int guild_id = RFIFOL(fd,2); + char* msg1 = (char*)RFIFOP(fd,6); + char* msg2 = (char*)RFIFOP(fd,66); + + if(!sd->state.gmaster_flag) + return; + + // compensate for some client defects when using multilanguage mode + if (msg1[0] == '|' && msg1[3] == '|') msg1+= 3; // skip duplicate marker + if (msg2[0] == '|' && msg2[3] == '|') msg2+= 3; // skip duplicate marker + if (msg2[0] == '|') msg2[strnlen(msg2, MAX_GUILDMES2)-1] = '\0'; // delete extra space at the end of string + + guild_change_notice(sd, guild_id, msg1, msg2); +} + + +/// Guild invite request (CZ_REQ_JOIN_GUILD). +/// 0168 <account id>.L <inviter account id>.L <inviter char id>.L +void clif_parse_GuildInvite(int fd,struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 2); + return; + } + + guild_invite(sd,t_sd); +} + + +/// Answer to guild invitation (CZ_JOIN_GUILD). +/// 016b <guild id>.L <answer>.L +/// answer: +/// 0 = refuse +/// 1 = accept +void clif_parse_GuildReplyInvite(int fd,struct map_session_data *sd) +{ + guild_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to leave guild (CZ_REQ_LEAVE_GUILD). +/// 0159 <guild id>.L <account id>.L <char id>.L <reason>.40B +void clif_parse_GuildLeave(int fd,struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + if( sd->bg_id ) + { + clif_displaymessage(fd, msg_txt(670)); //"You can't leave battleground guilds." + return; + } + + guild_leave(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); +} + + +/// Request to expel a member of a guild (CZ_REQ_BAN_GUILD). +/// 015b <guild id>.L <account id>.L <char id>.L <reason>.40B +void clif_parse_GuildExpulsion(int fd,struct map_session_data *sd) +{ + if( map[sd->bl.m].flag.guildlock || sd->bg_id ) + { // Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_expulsion(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); +} + + +/// Validates and processes guild messages (CZ_GUILD_CHAT). +/// 017e <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_GuildMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + if( sd->bg_id ) + bg_send_message(sd, text, textlen); + else + guild_send_message(sd, text, textlen); +} + + +/// Guild alliance request (CZ_REQ_ALLY_GUILD). +/// 0170 <account id>.L <inviter account id>.L <inviter char id>.L +void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 3); + return; + } + + guild_reqalliance(sd,t_sd); +} + + +/// Answer to a guild alliance request (CZ_ALLY_GUILD). +/// 0172 <inviter account id>.L <answer>.L +/// answer: +/// 0 = refuse +/// 1 = accept +void clif_parse_GuildReplyAlliance(int fd, struct map_session_data *sd) +{ + guild_reply_reqalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to delete a guild alliance or opposition (CZ_REQ_DELETE_RELATED_GUILD). +/// 0183 <opponent guild id>.L <relation>.L +/// relation: +/// 0 = Ally +/// 1 = Enemy +void clif_parse_GuildDelAlliance(int fd, struct map_session_data *sd) +{ + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_delalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to set a guild as opposition (CZ_REQ_HOSTILE_GUILD). +/// 0180 <account id>.L +void clif_parse_GuildOpposition(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 4); + return; + } + + guild_opposition(sd,t_sd); +} + + +/// Request to delete own guild (CZ_REQ_DISORGANIZE_GUILD). +/// 015d <key>.40B +/// key: +/// now guild name; might have been (intended) email, since the +/// field name and size is same as the one in CH_DELETE_CHAR. +void clif_parse_GuildBreak(int fd, struct map_session_data *sd) +{ + if( map[sd->bl.m].flag.guildlock ) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_break(sd,(char*)RFIFOP(fd,2)); +} + + +/// Pet +/// + +/// Request to invoke a pet menu action (CZ_COMMAND_PET). +/// 01a1 <type>.B +/// type: +/// 0 = pet information +/// 1 = feed +/// 2 = performance +/// 3 = return to egg +/// 4 = unequip accessory +void clif_parse_PetMenu(int fd, struct map_session_data *sd) +{ + pet_menu(sd,RFIFOB(fd,2)); +} + + +/// Attempt to tame a monster (CZ_TRYCAPTURE_MONSTER). +/// 019f <id>.L +void clif_parse_CatchPet(int fd, struct map_session_data *sd) +{ + pet_catch_process2(sd,RFIFOL(fd,2)); +} + + +/// Answer to pet incubator egg selection dialog (CZ_SELECT_PETEGG). +/// 01a7 <index>.W +void clif_parse_SelectEgg(int fd, struct map_session_data *sd) +{ + if (sd->menuskill_id != SA_TAMINGMONSTER || sd->menuskill_val != -1) + { + //Forged packet, disconnect them [Kevin] + clif_authfail_fd(fd, 0); + return; + } + pet_select_egg(sd,RFIFOW(fd,2)-2); + clif_menuskill_clear(sd); +} + + +/// Request to display pet's emotion/talk (CZ_PET_ACT). +/// 01a9 <data>.L +/// data: +/// is either emotion (@see enum emotion_type) or a compound value +/// (((mob id)-100)*100+(act id)*10+(hunger)) that describes an +/// entry (given in parentheses) in data\pettalktable.xml +/// act id: +/// 0 = feeding +/// 1 = hunting +/// 2 = danger +/// 3 = dead +/// 4 = normal (stand) +/// 5 = special performance (perfor_s) +/// 6 = level up (levelup) +/// 7 = performance 1 (perfor_1) +/// 8 = performance 2 (perfor_2) +/// 9 = performance 3 (perfor_3) +/// 10 = log-in greeting (connect) +/// hungry value: +/// 0 = very hungry (hungry) +/// 1 = hungry (bit_hungry) +/// 2 = satisfied (noting) +/// 3 = stuffed (full) +/// 4 = full (so_full) +void clif_parse_SendEmotion(int fd, struct map_session_data *sd) +{ + if(sd->pd) + clif_pet_emotion(sd->pd,RFIFOL(fd,2)); +} + + +/// Request to change pet's name (CZ_RENAME_PET). +/// 01a5 <name>.24B +void clif_parse_ChangePetName(int fd, struct map_session_data *sd) +{ + pet_change_name(sd,(char*)RFIFOP(fd,2)); +} + + +/// /kill (CZ_DISCONNECT_CHARACTER). +/// Request to disconnect a character. +/// 00cc <account id>.L +/// NOTE: Also sent when using GM right click menu "(name) force to quit" +void clif_parse_GMKick(int fd, struct map_session_data *sd) +{ + struct block_list *target; + int tid; + + tid = RFIFOL(fd,2); + target = map_id2bl(tid); + if (!target) { + clif_GM_kickack(sd, 0); + return; + } + + switch (target->type) { + case BL_PC: + { + char command[NAME_LENGTH+6]; + sprintf(command, "%ckick %s", atcommand_symbol, status_get_name(target)); + is_atcommand(fd, sd, command, 1); + } + break; + + /** + * This one does not invoke any atcommand, so we need to check for permissions. + */ + case BL_MOB: + { + char command[100]; + if( !pc_can_use_command(sd, "killmonster", COMMAND_ATCOMMAND)) { + clif_GM_kickack(sd, 0); + return; + } + sprintf(command, "/kick %s (%d)", status_get_name(target), status_get_class(target)); + log_atcommand(sd, command); + status_percent_damage(&sd->bl, target, 100, 0, true); // can invalidate 'target' + } + break; + + case BL_NPC: + { + char command[NAME_LENGTH+11]; + sprintf(command, "%cunloadnpc %s", atcommand_symbol, status_get_name(target)); + is_atcommand(fd, sd, command, 1); + } + break; + + default: + clif_GM_kickack(sd, 0); + } +} + + +/// /killall (CZ_DISCONNECT_ALL_CHARACTER). +/// Request to disconnect all characters. +/// 00ce +void clif_parse_GMKickAll(int fd, struct map_session_data* sd) { + char cmd[15]; + sprintf(cmd,"%ckickall",atcommand_symbol); + is_atcommand(fd, sd, cmd, 1); +} + + +/// /remove (CZ_REMOVE_AID). +/// Request to warp to a character with given login ID. +/// 01ba <account name>.24B + +/// /shift (CZ_SHIFT). +/// Request to warp to a character with given name. +/// 01bb <char name>.24B +void clif_parse_GMShift(int fd, struct map_session_data *sd) +{// FIXME: remove is supposed to receive account name for clients prior 20100803RE + char *player_name; + char command[NAME_LENGTH+8]; + + player_name = (char*)RFIFOP(fd,2); + player_name[NAME_LENGTH-1] = '\0'; + + sprintf(command, "%cjumpto %s", atcommand_symbol, player_name); + is_atcommand(fd, sd, command, 1); +} + + +/// /remove (CZ_REMOVE_AID_SSO). +/// Request to warp to a character with given account ID. +/// 0843 <account id>.L +void clif_parse_GMRemove2(int fd, struct map_session_data* sd) +{ + int account_id; + struct map_session_data* pl_sd; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + if( (pl_sd = map_id2sd(account_id)) != NULL ) + { + char command[NAME_LENGTH+8]; + sprintf(command, "%cjumpto %s", atcommand_symbol, pl_sd->status.name); + is_atcommand(fd, sd, command, 1); + } +} + + +/// /recall (CZ_RECALL). +/// Request to summon a player with given login ID to own position. +/// 01bc <account name>.24B + +/// /summon (CZ_RECALL_GID). +/// Request to summon a player with given name to own position. +/// 01bd <char name>.24B +void clif_parse_GMRecall(int fd, struct map_session_data *sd) +{// FIXME: recall is supposed to receive account name for clients prior 20100803RE + char *player_name; + char command [NAME_LENGTH+8]; + + player_name = (char*)RFIFOP(fd,2); + player_name[NAME_LENGTH-1] = '\0'; + + sprintf(command, "%crecall %s", atcommand_symbol, player_name); + is_atcommand(fd, sd, command, 1); +} + + +/// /recall (CZ_RECALL_SSO). +/// Request to summon a player with given account ID to own position. +/// 0842 <account id>.L +void clif_parse_GMRecall2(int fd, struct map_session_data* sd) +{ + int account_id; + struct map_session_data* pl_sd; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + if( (pl_sd = map_id2sd(account_id)) != NULL ) + { + char command[NAME_LENGTH+8]; + sprintf(command, "%crecall %s", atcommand_symbol, pl_sd->status.name); + is_atcommand(fd, sd, command, 1); + } +} + + +/// /item /monster (CZ_ITEM_CREATE). +/// Request to make items or spawn monsters. +/// 013f <item/mob name>.24B +void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) +{ + char *monster_item_name; + char command[NAME_LENGTH+10]; + + monster_item_name = (char*)RFIFOP(fd,2); + monster_item_name[NAME_LENGTH-1] = '\0'; + + // FIXME: Should look for item first, then for monster. + // FIXME: /monster takes mob_db Sprite_Name as argument + if( mobdb_searchname(monster_item_name) ) { + snprintf(command, sizeof(command)-1, "%cmonster %s", atcommand_symbol, monster_item_name); + is_atcommand(fd, sd, command, 1); + return; + } + // FIXME: Stackables have a quantity of 20. + // FIXME: Equips are supposed to be unidentified. + + if( itemdb_searchname(monster_item_name) ) { + snprintf(command, sizeof(command)-1, "%citem %s", atcommand_symbol, monster_item_name); + is_atcommand(fd, sd, command, 1); + return; + } +} + + +/// /hide (CZ_CHANGE_EFFECTSTATE). +/// 019d <effect state>.L +/// effect state: +/// TODO: Any OPTION_* ? +void clif_parse_GMHide(int fd, struct map_session_data *sd) { + char cmd[6]; + + sprintf(cmd,"%chide",atcommand_symbol); + + is_atcommand(fd, sd, cmd, 1); +} + + +/// Request to adjust player's manner points (CZ_REQ_GIVE_MANNER_POINT). +/// 0149 <account id>.L <type>.B <value>.W +/// type: +/// 0 = positive points +/// 1 = negative points +/// 2 = self mute (+10 minutes) +void clif_parse_GMReqNoChat(int fd,struct map_session_data *sd) +{ + int id, type, value; + struct map_session_data *dstsd; + char command[NAME_LENGTH+15]; + + id = RFIFOL(fd,2); + type = RFIFOB(fd,6); + value = RFIFOW(fd,7); + + if( type == 0 ) + value = -value; + + //If type is 2 and the ids don't match, this is a crafted hacked packet! + //Disabled because clients keep self-muting when you give players public @ commands... [Skotlex] + if (type == 2 /* && (pc_get_group_level(sd) > 0 || sd->bl.id != id)*/) + return; + + dstsd = map_id2sd(id); + if( dstsd == NULL ) + return; + + sprintf(command, "%cmute %d %s", atcommand_symbol, value, dstsd->status.name); + is_atcommand(fd, sd, command, 1); +} + + +/// /rc (CZ_REQ_GIVE_MANNER_BYNAME). +/// GM adjustment of a player's manner value by -60. +/// 0212 <char name>.24B +void clif_parse_GMRc(int fd, struct map_session_data* sd) +{ + char command[NAME_LENGTH+15]; + char *name = (char*)RFIFOP(fd,2); + + name[NAME_LENGTH-1] = '\0'; + sprintf(command, "%cmute %d %s", atcommand_symbol, 60, name); + is_atcommand(fd, sd, command, 1); +} + + +/// Result of request to resolve account name (ZC_ACK_ACCOUNTNAME). +/// 01e0 <account id>.L <account name>.24B +void clif_account_name(struct map_session_data* sd, int account_id, const char* accname) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x1e0)); + WFIFOW(fd,0) = 0x1e0; + WFIFOL(fd,2) = account_id; + safestrncpy((char*)WFIFOP(fd,6), accname, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x1e0)); +} + + +/// GM requesting account name (for right-click gm menu) (CZ_REQ_ACCOUNTNAME). +/// 01df <account id>.L +void clif_parse_GMReqAccountName(int fd, struct map_session_data *sd) +{ + int account_id = RFIFOL(fd,2); + + //TODO: find out if this works for any player or only for authorized GMs + clif_account_name(sd, account_id, ""); // insert account name here >_< +} + + +/// /changemaptype <x> <y> <type> (CZ_CHANGE_MAPTYPE). +/// GM single cell type change request. +/// 0198 <x>.W <y>.W <type>.W +/// type: +/// 0 = not walkable +/// 1 = walkable +void clif_parse_GMChangeMapType(int fd, struct map_session_data *sd) +{ + int x,y,type; + + if( pc_has_permission(sd, PC_PERM_USE_CHANGEMAPTYPE) ) + return; + + x = RFIFOW(fd,2); + y = RFIFOW(fd,4); + type = RFIFOW(fd,6); + + map_setgatcell(sd->bl.m,x,y,type); + clif_changemapcell(0,sd->bl.m,x,y,type,ALL_SAMEMAP); + //FIXME: once players leave the map, the client 'forgets' this information. +} + + +/// /in /ex (CZ_SETTING_WHISPER_PC). +/// Request to allow/deny whispers from a nick. +/// 00cf <nick>.24B <type>.B +/// type: +/// 0 = (/ex nick) deny speech from nick +/// 1 = (/in nick) allow speech from nick +void clif_parse_PMIgnore(int fd, struct map_session_data* sd) +{ + char* nick; + uint8 type; + int i; + + nick = (char*)RFIFOP(fd,2); // speed up + nick[NAME_LENGTH-1] = '\0'; // to be sure that the player name has at most 23 characters + type = RFIFOB(fd,26); + + if( type == 0 ) + { // Add name to ignore list (block) + if (strcmp(wisp_server_name, nick) == 0) { + clif_wisexin(sd, type, 1); // fail + return; + } + + // try to find a free spot, while checking for duplicates at the same time + ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); + if( i == MAX_IGNORE_LIST ) + {// no space for new entry + clif_wisexin(sd, type, 2); // too many blocks + return; + } + if( sd->ignore[i].name[0] != '\0' ) + {// name already exists + clif_wisexin(sd, type, 0); // Aegis reports success. + return; + } + + //Insert in position i + safestrncpy(sd->ignore[i].name, nick, NAME_LENGTH); + } + else + { // Remove name from ignore list (unblock) + + // find entry + ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); + if( i == MAX_IGNORE_LIST || sd->ignore[i].name[i] == '\0' ) + { //Not found + clif_wisexin(sd, type, 1); // fail + return; + } + // move everything one place down to overwrite removed entry + memmove(sd->ignore[i].name, sd->ignore[i+1].name, (MAX_IGNORE_LIST-i-1)*sizeof(sd->ignore[0].name)); + // wipe last entry + memset(sd->ignore[MAX_IGNORE_LIST-1].name, 0, sizeof(sd->ignore[0].name)); + } + + clif_wisexin(sd, type, 0); // success +} + + +/// /inall /exall (CZ_SETTING_WHISPER_STATE). +/// Request to allow/deny all whispers. +/// 00d0 <type>.B +/// type: +/// 0 = (/exall) deny all speech +/// 1 = (/inall) allow all speech +void clif_parse_PMIgnoreAll(int fd, struct map_session_data *sd) +{ + int type = RFIFOB(fd,2), flag; + + if( type == 0 ) + {// Deny all + if( sd->state.ignoreAll ) { + flag = 1; // fail + } else { + sd->state.ignoreAll = 1; + flag = 0; // success + } + } + else + {//Unblock everyone + if( sd->state.ignoreAll ) { + sd->state.ignoreAll = 0; + flag = 0; // success + } else { + if (sd->ignore[0].name[0] != '\0') + { //Wipe the ignore list. + memset(sd->ignore, 0, sizeof(sd->ignore)); + flag = 0; // success + } else { + flag = 1; // fail + } + } + } + + clif_wisall(sd, type, flag); +} + + +/// Whisper ignore list (ZC_WHISPER_LIST). +/// 00d4 <packet len>.W { <char name>.24B }* +void clif_PMIgnoreList(struct map_session_data* sd) +{ + int i, fd = sd->fd; + + WFIFOHEAD(fd,4+ARRAYLENGTH(sd->ignore)*NAME_LENGTH); + WFIFOW(fd,0) = 0xd4; + + for( i = 0; i < ARRAYLENGTH(sd->ignore) && sd->ignore[i].name[0]; i++ ) + { + memcpy(WFIFOP(fd,4+i*NAME_LENGTH), sd->ignore[i].name, NAME_LENGTH); + } + + WFIFOW(fd,2) = 4+i*NAME_LENGTH; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Whisper ignore list request (CZ_REQ_WHISPER_LIST). +/// 00d3 +void clif_parse_PMIgnoreList(int fd,struct map_session_data *sd) +{ + clif_PMIgnoreList(sd); +} + + +/// Request to invoke the /doridori recovery bonus (CZ_DORIDORI). +/// 01e7 +void clif_parse_NoviceDoriDori(int fd, struct map_session_data *sd) +{ + if (sd->state.doridori) return; + + switch (sd->class_&MAPID_UPPERMASK) + { + case MAPID_SOUL_LINKER: + case MAPID_STAR_GLADIATOR: + case MAPID_TAEKWON: + if (!sd->state.rest) + break; + case MAPID_SUPER_NOVICE: + sd->state.doridori=1; + break; + } +} + + +/// Request to invoke the effect of super novice's guardian angel prayer (CZ_CHOPOKGI). +/// 01ed +/// Note: This packet is caused by 7 lines of any text, followed by +/// the prayer and an another line of any text. The prayer is +/// defined by lines 790~793 in data\msgstringtable.txt +/// "Dear angel, can you hear my voice?" +/// "I am" (space separated player name) "Super Novice~" +/// "Help me out~ Please~ T_T" +void clif_parse_NoviceExplosionSpirits(int fd, struct map_session_data *sd) +{ + if( ( sd->class_&MAPID_UPPERMASK ) == MAPID_SUPER_NOVICE ) + { + unsigned int next = pc_nextbaseexp(sd); + if( next == 0 ) next = pc_thisbaseexp(sd); + if( next ) + { + int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); + + if( percent && ( percent%100 ) == 0 ) + {// 10.0%, 20.0%, ..., 90.0% + sc_start(&sd->bl, status_skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill_get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] + clif_skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions + } + } + } +} + + +/// Friends List +/// + +/// Toggles a single friend online/offline [Skotlex] (ZC_FRIENDS_STATE). +/// 0206 <account id>.L <char id>.L <state>.B +/// state: +/// 0 = online +/// 1 = offline +void clif_friendslist_toggle(struct map_session_data *sd,int account_id, int char_id, int online) +{ + int i, fd = sd->fd; + + //Seek friend. + for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + + if(i == MAX_FRIENDS || sd->status.friends[i].char_id == 0) + return; //Not found + + WFIFOHEAD(fd,packet_len(0x206)); + WFIFOW(fd, 0) = 0x206; + WFIFOL(fd, 2) = sd->status.friends[i].account_id; + WFIFOL(fd, 6) = sd->status.friends[i].char_id; + WFIFOB(fd,10) = !online; //Yeah, a 1 here means "logged off", go figure... + WFIFOSET(fd, packet_len(0x206)); +} + + +//Subfunction called from clif_foreachclient to toggle friends on/off [Skotlex] +int clif_friendslist_toggle_sub(struct map_session_data *sd,va_list ap) +{ + int account_id, char_id, online; + account_id = va_arg(ap, int); + char_id = va_arg(ap, int); + online = va_arg(ap, int); + clif_friendslist_toggle(sd, account_id, char_id, online); + return 0; +} + + +/// Sends the whole friends list (ZC_FRIENDS_LIST). +/// 0201 <packet len>.W { <account id>.L <char id>.L <name>.24B }* +void clif_friendslist_send(struct map_session_data *sd) +{ + int i = 0, n, fd = sd->fd; + + // Send friends list + WFIFOHEAD(fd, MAX_FRIENDS * 32 + 4); + WFIFOW(fd, 0) = 0x201; + for(i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id; i++) + { + WFIFOL(fd, 4 + 32 * i + 0) = sd->status.friends[i].account_id; + WFIFOL(fd, 4 + 32 * i + 4) = sd->status.friends[i].char_id; + memcpy(WFIFOP(fd, 4 + 32 * i + 8), &sd->status.friends[i].name, NAME_LENGTH); + } + + if (i) { + WFIFOW(fd,2) = 4 + 32 * i; + WFIFOSET(fd, WFIFOW(fd,2)); + } + + for (n = 0; n < i; n++) + { //Sending the online players + if (map_charid2sd(sd->status.friends[n].char_id)) + clif_friendslist_toggle(sd, sd->status.friends[n].account_id, sd->status.friends[n].char_id, 1); + } +} + + +/// Notification about the result of a friend add request (ZC_ADD_FRIENDS_LIST). +/// 0209 <result>.W <account id>.L <char id>.L <name>.24B +/// result: +/// 0 = MsgStringTable[821]="You have become friends with (%s)." +/// 1 = MsgStringTable[822]="(%s) does not want to be friends with you." +/// 2 = MsgStringTable[819]="Your Friend List is full." +/// 3 = MsgStringTable[820]="(%s)'s Friend List is full." +void clif_friendslist_reqack(struct map_session_data *sd, struct map_session_data *f_sd, int type) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x209)); + WFIFOW(fd,0) = 0x209; + WFIFOW(fd,2) = type; + if (f_sd) + { + WFIFOL(fd,4) = f_sd->status.account_id; + WFIFOL(fd,8) = f_sd->status.char_id; + memcpy(WFIFOP(fd, 12), f_sd->status.name,NAME_LENGTH); + } + WFIFOSET(fd, packet_len(0x209)); +} + + +/// Asks a player for permission to be added as friend (ZC_REQ_ADD_FRIENDS). +/// 0207 <req account id>.L <req char id>.L <req char name>.24B +void clif_friendlist_req(struct map_session_data* sd, int account_id, int char_id, const char* name) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x207)); + WFIFOW(fd,0) = 0x207; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + memcpy(WFIFOP(fd,10), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x207)); +} + + +/// Request to add a player as friend (CZ_ADD_FRIENDS). +/// 0202 <name>.24B +void clif_parse_FriendsListAdd(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd; + int i; + + f_sd = map_nick2sd((char*)RFIFOP(fd,2)); + + // ensure that the request player's friend list is not full + ARR_FIND(0, MAX_FRIENDS, i, sd->status.friends[i].char_id == 0); + + if( i == MAX_FRIENDS ) { + clif_friendslist_reqack(sd, f_sd, 2); + return; + } + + // Friend doesn't exist (no player with this name) + if (f_sd == NULL) { + clif_displaymessage(fd, msg_txt(3)); + return; + } + + if( sd->bl.id == f_sd->bl.id ) + {// adding oneself as friend + return; + } + + // @noask [LuzZza] + if(f_sd->state.noask) { + clif_noask_sub(sd, f_sd, 5); + return; + } + + // Friend already exists + for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id != 0; i++) { + if (sd->status.friends[i].char_id == f_sd->status.char_id) { + clif_displaymessage(fd, msg_txt(671)); //"Friend already exists." + return; + } + } + + f_sd->friend_req = sd->status.char_id; + sd->friend_req = f_sd->status.char_id; + + clif_friendlist_req(f_sd, sd->status.account_id, sd->status.char_id, sd->status.name); +} + + +/// Answer to a friend add request (CZ_ACK_REQ_ADD_FRIENDS). +/// 0208 <inviter account id>.L <inviter char id>.L <result>.B +/// 0208 <inviter account id>.L <inviter char id>.L <result>.L (PACKETVER >= 6) +/// result: +/// 0 = rejected +/// 1 = accepted +void clif_parse_FriendsListReply(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd; + int account_id; + char reply; + + account_id = RFIFOL(fd,2); + //char_id = RFIFOL(fd,6); +#if PACKETVER < 6 + reply = RFIFOB(fd,10); +#else + reply = RFIFOL(fd,10); +#endif + + if( sd->bl.id == account_id ) + {// adding oneself as friend + return; + } + + f_sd = map_id2sd(account_id); //The account id is the same as the bl.id of players. + if (f_sd == NULL) + return; + + if (reply == 0 || !( sd->friend_req == f_sd->status.char_id && f_sd->friend_req == sd->status.char_id ) ) + clif_friendslist_reqack(f_sd, sd, 1); + else { + int i; + // Find an empty slot + for (i = 0; i < MAX_FRIENDS; i++) + if (f_sd->status.friends[i].char_id == 0) + break; + if (i == MAX_FRIENDS) { + clif_friendslist_reqack(f_sd, sd, 2); + return; + } + + f_sd->status.friends[i].account_id = sd->status.account_id; + f_sd->status.friends[i].char_id = sd->status.char_id; + memcpy(f_sd->status.friends[i].name, sd->status.name, NAME_LENGTH); + clif_friendslist_reqack(f_sd, sd, 0); + + if (battle_config.friend_auto_add) { + // Also add f_sd to sd's friendlist. + for (i = 0; i < MAX_FRIENDS; i++) { + if (sd->status.friends[i].char_id == f_sd->status.char_id) + return; //No need to add anything. + if (sd->status.friends[i].char_id == 0) + break; + } + if (i == MAX_FRIENDS) { + clif_friendslist_reqack(sd, f_sd, 2); + return; + } + + sd->status.friends[i].account_id = f_sd->status.account_id; + sd->status.friends[i].char_id = f_sd->status.char_id; + memcpy(sd->status.friends[i].name, f_sd->status.name, NAME_LENGTH); + clif_friendslist_reqack(sd, f_sd, 0); + } + } +} + + +/// Request to delete a friend (CZ_DELETE_FRIENDS). +/// 0203 <account id>.L <char id>.L +void clif_parse_FriendsListRemove(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd = NULL; + int account_id, char_id; + int i, j; + + account_id = RFIFOL(fd,2); + char_id = RFIFOL(fd,6); + + // Search friend + for (i = 0; i < MAX_FRIENDS && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + + if (i == MAX_FRIENDS) { + clif_displaymessage(fd, msg_txt(672)); //"Name not found in list." + return; + } + + //remove from friend's list first + if( (f_sd = map_id2sd(account_id)) && f_sd->status.char_id == char_id) { + for (i = 0; i < MAX_FRIENDS && + (f_sd->status.friends[i].char_id != sd->status.char_id || f_sd->status.friends[i].account_id != sd->status.account_id); i++); + + if (i != MAX_FRIENDS) { + // move all chars up + for(j = i + 1; j < MAX_FRIENDS; j++) + memcpy(&f_sd->status.friends[j-1], &f_sd->status.friends[j], sizeof(f_sd->status.friends[0])); + + memset(&f_sd->status.friends[MAX_FRIENDS-1], 0, sizeof(f_sd->status.friends[MAX_FRIENDS-1])); + //should the guy be notified of some message? we should add it here if so + WFIFOHEAD(f_sd->fd,packet_len(0x20a)); + WFIFOW(f_sd->fd,0) = 0x20a; + WFIFOL(f_sd->fd,2) = sd->status.account_id; + WFIFOL(f_sd->fd,6) = sd->status.char_id; + WFIFOSET(f_sd->fd, packet_len(0x20a)); + } + + } else { //friend not online -- ask char server to delete from his friendlist + if(chrif_removefriend(char_id,sd->status.char_id)) { // char-server offline, abort + clif_displaymessage(fd, msg_txt(673)); //"This action can't be performed at the moment. Please try again later." + return; + } + } + + // We can now delete from original requester + for (i = 0; i < MAX_FRIENDS && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + // move all chars up + for(j = i + 1; j < MAX_FRIENDS; j++) + memcpy(&sd->status.friends[j-1], &sd->status.friends[j], sizeof(sd->status.friends[0])); + + memset(&sd->status.friends[MAX_FRIENDS-1], 0, sizeof(sd->status.friends[MAX_FRIENDS-1])); + clif_displaymessage(fd, msg_txt(674)); //"Friend removed" + + WFIFOHEAD(fd,packet_len(0x20a)); + WFIFOW(fd,0) = 0x20a; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + WFIFOSET(fd, packet_len(0x20a)); +} + + +/// /pvpinfo list (ZC_ACK_PVPPOINT). +/// 0210 <char id>.L <account id>.L <win point>.L <lose point>.L <point>.L +void clif_PVPInfo(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x210)); + WFIFOW(fd,0) = 0x210; + WFIFOL(fd,2) = sd->status.char_id; + WFIFOL(fd,6) = sd->status.account_id; + WFIFOL(fd,10) = sd->pvp_won; // times won + WFIFOL(fd,14) = sd->pvp_lost; // times lost + WFIFOL(fd,18) = sd->pvp_point; + WFIFOSET(fd, packet_len(0x210)); +} + + +/// /pvpinfo (CZ_REQ_PVPPOINT). +/// 020f <char id>.L <account id>.L +void clif_parse_PVPInfo(int fd,struct map_session_data *sd) +{ + // TODO: Is there a way to use this on an another player (char/acc id)? + clif_PVPInfo(sd); +} + + +/// /blacksmith list (ZC_BLACKSMITH_RANK). +/// 0219 { <name>.24B }*10 { <point>.L }*10 +void clif_blacksmith(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x219)); + WFIFOW(fd,0) = 0x219; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (smith_fame_list[i].id > 0) { + if (strcmp(smith_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(smith_fame_list[i].id)) != NULL) + { + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), name, NAME_LENGTH); + } else + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), smith_fame_list[i].name, NAME_LENGTH); + } else + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "None", 5); + WFIFOL(fd, 242 + i * 4) = smith_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "Unavailable", 12); + WFIFOL(fd, 242 + i * 4) = 0; + } + + WFIFOSET(fd, packet_len(0x219)); +} + + +/// /blacksmith (CZ_BLACKSMITH_RANK). +/// 0217 +void clif_parse_Blacksmith(int fd,struct map_session_data *sd) +{ + clif_blacksmith(sd); +} + + +/// Notification about backsmith points (ZC_BLACKSMITH_POINT). +/// 021b <points>.L <total points>.L +void clif_fame_blacksmith(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x21b)); + WFIFOW(fd,0) = 0x21b; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x21b)); +} + + +/// /alchemist list (ZC_ALCHEMIST_RANK). +/// 021a { <name>.24B }*10 { <point>.L }*10 +void clif_alchemist(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x21a)); + WFIFOW(fd,0) = 0x21a; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (chemist_fame_list[i].id > 0) { + if (strcmp(chemist_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(chemist_fame_list[i].id)) != NULL) + { + memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), chemist_fame_list[i].name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = chemist_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = 0; + } + + WFIFOSET(fd, packet_len(0x21a)); +} + + +/// /alchemist (CZ_ALCHEMIST_RANK). +/// 0218 +void clif_parse_Alchemist(int fd,struct map_session_data *sd) +{ + clif_alchemist(sd); +} + + +/// Notification about alchemist points (ZC_ALCHEMIST_POINT). +/// 021c <points>.L <total points>.L +void clif_fame_alchemist(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x21c)); + WFIFOW(fd,0) = 0x21c; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x21c)); +} + + +/// /taekwon list (ZC_TAEKWON_RANK). +/// 0226 { <name>.24B }*10 { <point>.L }*10 +void clif_taekwon(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x226)); + WFIFOW(fd,0) = 0x226; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (taekwon_fame_list[i].id > 0) { + if (strcmp(taekwon_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(taekwon_fame_list[i].id)) != NULL) + { + memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), taekwon_fame_list[i].name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = taekwon_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = 0; + } + WFIFOSET(fd, packet_len(0x226)); +} + + +/// /taekwon (CZ_TAEKWON_RANK). +/// 0225 +void clif_parse_Taekwon(int fd,struct map_session_data *sd) +{ + clif_taekwon(sd); +} + + +/// Notification about taekwon points (ZC_TAEKWON_POINT). +/// 0224 <points>.L <total points>.L +void clif_fame_taekwon(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x224)); + WFIFOW(fd,0) = 0x224; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x224)); +} + + +/// /pk list (ZC_KILLER_RANK). +/// 0238 { <name>.24B }*10 { <point>.L }*10 +void clif_ranking_pk(struct map_session_data* sd) +{ + int i, fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x238)); + WFIFOW(fd,0) = 0x238; + for(i=0;i<10;i++){ + memcpy(WFIFOP(fd,i*24+2), "Unknown", NAME_LENGTH); + WFIFOL(fd,i*4+242) = 0; + } + WFIFOSET(fd, packet_len(0x238)); +} + + +/// /pk (CZ_KILLER_RANK). +/// 0237 +void clif_parse_RankingPk(int fd,struct map_session_data *sd) +{ + clif_ranking_pk(sd); +} + + +/// SG Feel save OK [Komurka] (CZ_AGREE_STARPLACE). +/// 0254 <which>.B +/// which: +/// 0 = sun +/// 1 = moon +/// 2 = star +void clif_parse_FeelSaveOk(int fd,struct map_session_data *sd) +{ + int i; + if (sd->menuskill_id != SG_FEEL) + return; + i = sd->menuskill_val-1; + if (i<0 || i >= MAX_PC_FEELHATE) return; //Bug? + + sd->feel_map[i].index = map_id2index(sd->bl.m); + sd->feel_map[i].m = sd->bl.m; + pc_setglobalreg(sd,sg_info[i].feel_var,sd->feel_map[i].index); + +//Are these really needed? Shouldn't they show up automatically from the feel save packet? +// clif_misceffect2(&sd->bl, 0x1b0); +// clif_misceffect2(&sd->bl, 0x21f); + clif_feel_info(sd, i, 0); + clif_menuskill_clear(sd); +} + + +/// Star Gladiator's Feeling map confirmation prompt (ZC_STARPLACE). +/// 0253 <which>.B +/// which: +/// 0 = sun +/// 1 = moon +/// 2 = star +void clif_feel_req(int fd, struct map_session_data *sd, uint16 skill_lv) +{ + WFIFOHEAD(fd,packet_len(0x253)); + WFIFOW(fd,0)=0x253; + WFIFOB(fd,2)=TOB(skill_lv-1); + WFIFOSET(fd, packet_len(0x253)); + sd->menuskill_id = SG_FEEL; + sd->menuskill_val = skill_lv; +} + + +/// Request to change homunculus' name (CZ_RENAME_MER). +/// 0231 <name>.24B +void clif_parse_ChangeHomunculusName(int fd, struct map_session_data *sd) +{ + merc_hom_change_name(sd,(char*)RFIFOP(fd,2)); +} + + +/// Request to warp/move homunculus/mercenary to it's owner (CZ_REQUEST_MOVETOOWNER). +/// 0234 <id>.L +void clif_parse_HomMoveToMaster(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,2); // Mercenary or Homunculus + struct block_list *bl = NULL; + struct unit_data *ud = NULL; + + if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; + else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; // Moving Homunculus + else + return; + + unit_calc_pos(bl, sd->bl.x, sd->bl.y, sd->ud.dir); + ud = unit_bl2ud(bl); + unit_walktoxy(bl, ud->to_x, ud->to_y, 4); +} + + +/// Request to move homunculus/mercenary (CZ_REQUEST_MOVENPC). +/// 0232 <id>.L <position data>.3B +void clif_parse_HomMoveTo(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,2); // Mercenary or Homunculus + struct block_list *bl = NULL; + short x, y; + + RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1], &x, &y, NULL); + + if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; // Moving Mercenary + else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; // Moving Homunculus + else + return; + + unit_walktoxy(bl, x, y, 4); +} + + +/// Request to do an action with homunculus/mercenary (CZ_REQUEST_ACTNPC). +/// 0233 <id>.L <target id>.L <action>.B +/// action: +/// always 0 +void clif_parse_HomAttack(int fd,struct map_session_data *sd) +{ + struct block_list *bl = NULL; + int id = RFIFOL(fd,2), + target_id = RFIFOL(fd,6), + action_type = RFIFOB(fd,10); + + if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; + else if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; + else return; + + unit_stop_attack(bl); + unit_attack(bl, target_id, action_type != 0); +} + + +/// Request to invoke a homunculus menu action (CZ_COMMAND_MER). +/// 022d <type>.W <command>.B +/// type: +/// always 0 +/// command: +/// 0 = homunculus information +/// 1 = feed +/// 2 = delete +void clif_parse_HomMenu(int fd, struct map_session_data *sd) +{ //[orn] + int cmd; + + cmd = RFIFOW(fd,0); + + if(!merc_is_hom_active(sd->hd)) + return; + + merc_menu(sd,RFIFOB(fd,packet_db[sd->packet_ver][cmd].pos[1])); +} + + +/// Request to resurrect oneself using Token of Siegfried (CZ_STANDING_RESURRECTION). +/// 0292 +void clif_parse_AutoRevive(int fd, struct map_session_data *sd) +{ + int item_position = pc_search_inventory(sd, ITEMID_TOKEN_OF_SIEGFRIED); + + if (item_position < 0) + return; + + if (sd->sc.data[SC_HELLPOWER]) //Cannot res while under the effect of SC_HELLPOWER. + return; + + if (!status_revive(&sd->bl, 100, 100)) + return; + + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + pc_delitem(sd, item_position, 1, 0, 1, LOG_TYPE_CONSUME); +} + + +/// Information about character's status values (ZC_ACK_STATUS_GM). +/// 0214 <str>.B <standardStr>.B <agi>.B <standardAgi>.B <vit>.B <standardVit>.B +/// <int>.B <standardInt>.B <dex>.B <standardDex>.B <luk>.B <standardLuk>.B +/// <attPower>.W <refiningPower>.W <max_mattPower>.W <min_mattPower>.W +/// <itemdefPower>.W <plusdefPower>.W <mdefPower>.W <plusmdefPower>.W +/// <hitSuccessValue>.W <avoidSuccessValue>.W <plusAvoidSuccessValue>.W +/// <criticalSuccessValue>.W <ASPD>.W <plusASPD>.W +void clif_check(int fd, struct map_session_data* pl_sd) +{ + WFIFOHEAD(fd,packet_len(0x214)); + WFIFOW(fd, 0) = 0x214; + WFIFOB(fd, 2) = min(pl_sd->status.str, UINT8_MAX); + WFIFOB(fd, 3) = pc_need_status_point(pl_sd, SP_STR, 1); + WFIFOB(fd, 4) = min(pl_sd->status.agi, UINT8_MAX); + WFIFOB(fd, 5) = pc_need_status_point(pl_sd, SP_AGI, 1); + WFIFOB(fd, 6) = min(pl_sd->status.vit, UINT8_MAX); + WFIFOB(fd, 7) = pc_need_status_point(pl_sd, SP_VIT, 1); + WFIFOB(fd, 8) = min(pl_sd->status.int_, UINT8_MAX); + WFIFOB(fd, 9) = pc_need_status_point(pl_sd, SP_INT, 1); + WFIFOB(fd,10) = min(pl_sd->status.dex, UINT8_MAX); + WFIFOB(fd,11) = pc_need_status_point(pl_sd, SP_DEX, 1); + WFIFOB(fd,12) = min(pl_sd->status.luk, UINT8_MAX); + WFIFOB(fd,13) = pc_need_status_point(pl_sd, SP_LUK, 1); + WFIFOW(fd,14) = pl_sd->battle_status.batk+pl_sd->battle_status.rhw.atk+pl_sd->battle_status.lhw.atk; + WFIFOW(fd,16) = pl_sd->battle_status.rhw.atk2+pl_sd->battle_status.lhw.atk2; + WFIFOW(fd,18) = pl_sd->battle_status.matk_max; + WFIFOW(fd,20) = pl_sd->battle_status.matk_min; + WFIFOW(fd,22) = pl_sd->battle_status.def; + WFIFOW(fd,24) = pl_sd->battle_status.def2; + WFIFOW(fd,26) = pl_sd->battle_status.mdef; + WFIFOW(fd,28) = pl_sd->battle_status.mdef2; + WFIFOW(fd,30) = pl_sd->battle_status.hit; + WFIFOW(fd,32) = pl_sd->battle_status.flee; + WFIFOW(fd,34) = pl_sd->battle_status.flee2/10; + WFIFOW(fd,36) = pl_sd->battle_status.cri/10; + WFIFOW(fd,38) = (2000-pl_sd->battle_status.amotion)/10; // aspd + WFIFOW(fd,40) = 0; // FIXME: What is 'plusASPD' supposed to be? Maybe adelay? + WFIFOSET(fd,packet_len(0x214)); +} + + +/// /check (CZ_REQ_STATUS_GM). +/// Request character's status values. +/// 0213 <char name>.24B +void clif_parse_Check(int fd, struct map_session_data *sd) +{ + char charname[NAME_LENGTH]; + struct map_session_data* pl_sd; + + if(!pc_has_permission(sd, PC_PERM_USE_CHECK)) + return; + + safestrncpy(charname, (const char*)RFIFOP(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), sizeof(charname)); + + if( ( pl_sd = map_nick2sd(charname) ) == NULL || pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + return; + } + + clif_check(fd, pl_sd); +} + + + +/// MAIL SYSTEM +/// By Zephyrus +/// + +/// Notification about the result of adding an item to mail (ZC_ACK_MAIL_ADD_ITEM). +/// 0255 <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_setattachment(int fd, int index, uint8 flag) +{ + WFIFOHEAD(fd,packet_len(0x255)); + WFIFOW(fd,0) = 0x255; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = flag; + WFIFOSET(fd,packet_len(0x255)); +} + + +/// Notification about the result of retrieving a mail attachment (ZC_MAIL_REQ_GET_ITEM). +/// 0245 <result>.B +/// result: +/// 0 = success +/// 1 = failure +/// 2 = too many items +void clif_Mail_getattachment(int fd, uint8 flag) +{ + WFIFOHEAD(fd,packet_len(0x245)); + WFIFOW(fd,0) = 0x245; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0x245)); +} + + +/// Notification about the result of sending a mail (ZC_MAIL_REQ_SEND). +/// 0249 <result>.B +/// result: +/// 0 = success +/// 1 = recipinent does not exist +void clif_Mail_send(int fd, bool fail) +{ + WFIFOHEAD(fd,packet_len(0x249)); + WFIFOW(fd,0) = 0x249; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0x249)); +} + + +/// Notification about the result of deleting a mail (ZC_ACK_MAIL_DELETE). +/// 0257 <mail id>.L <result>.W +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_delete(int fd, int mail_id, short fail) +{ + WFIFOHEAD(fd, packet_len(0x257)); + WFIFOW(fd,0) = 0x257; + WFIFOL(fd,2) = mail_id; + WFIFOW(fd,6) = fail; + WFIFOSET(fd, packet_len(0x257)); +} + + +/// Notification about the result of returning a mail (ZC_ACK_MAIL_RETURN). +/// 0274 <mail id>.L <result>.W +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_return(int fd, int mail_id, short fail) +{ + WFIFOHEAD(fd,packet_len(0x274)); + WFIFOW(fd,0) = 0x274; + WFIFOL(fd,2) = mail_id; + WFIFOW(fd,6) = fail; + WFIFOSET(fd,packet_len(0x274)); +} + + +/// Notification about new mail (ZC_MAIL_RECEIVE). +/// 024a <mail id>.L <title>.40B <sender>.24B +void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title) +{ + WFIFOHEAD(fd,packet_len(0x24a)); + WFIFOW(fd,0) = 0x24a; + WFIFOL(fd,2) = mail_id; + safestrncpy((char*)WFIFOP(fd,6), title, MAIL_TITLE_LENGTH); + safestrncpy((char*)WFIFOP(fd,46), sender, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x24a)); +} + + +/// Opens/closes the mail window (ZC_MAIL_WINDOWS). +/// 0260 <type>.L +/// type: +/// 0 = open +/// 1 = close +void clif_Mail_window(int fd, int flag) +{ + WFIFOHEAD(fd,packet_len(0x260)); + WFIFOW(fd,0) = 0x260; + WFIFOL(fd,2) = flag; + WFIFOSET(fd,packet_len(0x260)); +} + + +/// Lists mails stored in inbox (ZC_MAIL_REQ_GET_LIST). +/// 0240 <packet len>.W <amount>.L { <mail id>.L <title>.40B <read>.B <sender>.24B <time>.L }*amount +/// read: +/// 0 = unread +/// 1 = read +void clif_Mail_refreshinbox(struct map_session_data *sd) +{ + int fd = sd->fd; + struct mail_data *md = &sd->mail.inbox; + struct mail_message *msg; + int len, i, j; + + len = 8 + (73 * md->amount); + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x240; + WFIFOW(fd,2) = len; + WFIFOL(fd,4) = md->amount; + for( i = j = 0; i < MAIL_MAX_INBOX && j < md->amount; i++ ) + { + msg = &md->msg[i]; + if (msg->id < 1) + continue; + + WFIFOL(fd,8+73*j) = msg->id; + memcpy(WFIFOP(fd,12+73*j), msg->title, MAIL_TITLE_LENGTH); + WFIFOB(fd,52+73*j) = (msg->status != MAIL_UNREAD); + memcpy(WFIFOP(fd,53+73*j), msg->send_name, NAME_LENGTH); + WFIFOL(fd,77+73*j) = (uint32)msg->timestamp; + j++; + } + WFIFOSET(fd,len); + + if( md->full ) + {// TODO: is this official? + char output[100]; + sprintf(output, "Inbox is full (Max %d). Delete some mails.", MAIL_MAX_INBOX); + clif_disp_onlyself(sd, output, strlen(output)); + } +} + + +/// Mail inbox list request (CZ_MAIL_GET_LIST). +/// 023f +void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *sd) +{ + struct mail_data* md = &sd->mail.inbox; + + if( md->amount < MAIL_MAX_INBOX && (md->full || sd->mail.changed) ) + intif_Mail_requestinbox(sd->status.char_id, 1); + else + clif_Mail_refreshinbox(sd); + + mail_removeitem(sd, 0); + mail_removezeny(sd, 0); +} + + +/// Opens a mail (ZC_MAIL_REQ_OPEN). +/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L +/// <amount>.L <name id>.W <item type>.W <identified>.B <damaged>.B <refine>.B +/// <card1>.W <card2>.W <card3>.W <card4>.W <message>.?B +void clif_Mail_read(struct map_session_data *sd, int mail_id) +{ + int i, fd = sd->fd; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i == MAIL_MAX_INBOX ) + { + clif_Mail_return(sd->fd, mail_id, 1); // Mail doesn't exist + ShowWarning("clif_parse_Mail_read: char '%s' trying to read a message not the inbox.\n", sd->status.name); + return; + } + else + { + struct mail_message *msg = &sd->mail.inbox.msg[i]; + struct item *item = &msg->item; + struct item_data *data; + int msg_len = strlen(msg->body), len; + + if( msg_len == 0 ) { + strcpy(msg->body, "(no message)"); + msg_len = strlen(msg->body); + } + + len = 101 + msg_len; + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x242; + WFIFOW(fd,2) = len; + WFIFOL(fd,4) = msg->id; + safestrncpy((char*)WFIFOP(fd,8), msg->title, MAIL_TITLE_LENGTH + 1); + safestrncpy((char*)WFIFOP(fd,48), msg->send_name, NAME_LENGTH + 1); + WFIFOL(fd,72) = 0; + WFIFOL(fd,76) = msg->zeny; + + if( item->nameid && (data = itemdb_exists(item->nameid)) != NULL ) + { + WFIFOL(fd,80) = item->amount; + WFIFOW(fd,84) = (data->view_id)?data->view_id:item->nameid; + WFIFOW(fd,86) = data->type; + WFIFOB(fd,88) = item->identify; + WFIFOB(fd,89) = item->attribute; + WFIFOB(fd,90) = item->refine; + WFIFOW(fd,91) = item->card[0]; + WFIFOW(fd,93) = item->card[1]; + WFIFOW(fd,95) = item->card[2]; + WFIFOW(fd,97) = item->card[3]; + } + else // no item, set all to zero + memset(WFIFOP(fd,80), 0x00, 19); + + WFIFOB(fd,99) = (unsigned char)msg_len; + safestrncpy((char*)WFIFOP(fd,100), msg->body, msg_len + 1); + WFIFOSET(fd,len); + + if (msg->status == MAIL_UNREAD) { + msg->status = MAIL_READ; + intif_Mail_read(mail_id); + clif_parse_Mail_refreshinbox(fd, sd); + } + } +} + + +/// Request to open a mail (CZ_MAIL_OPEN). +/// 0241 <mail id>.L +void clif_parse_Mail_read(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + clif_Mail_read(sd, RFIFOL(fd,2)); +} + + +/// Request to receive mail's attachment (CZ_MAIL_GET_ITEM). +/// 0244 <mail id>.L +void clif_parse_Mail_getattach(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + bool fail = false; + + if( !chrif_isconnected() ) + return; + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i == MAIL_MAX_INBOX ) + return; + + if( sd->mail.inbox.msg[i].zeny < 1 && (sd->mail.inbox.msg[i].item.nameid < 1 || sd->mail.inbox.msg[i].item.amount < 1) ) + return; + + if( sd->mail.inbox.msg[i].zeny + sd->status.zeny > MAX_ZENY ) + { + clif_Mail_getattachment(fd, 1); + return; + } + + if( sd->mail.inbox.msg[i].item.nameid > 0 ) + { + struct item_data *data; + unsigned int weight; + + if ((data = itemdb_exists(sd->mail.inbox.msg[i].item.nameid)) == NULL) + return; + + switch( pc_checkadditem(sd, data->nameid, sd->mail.inbox.msg[i].item.amount) ) + { + case ADDITEM_NEW: + fail = ( pc_inventoryblank(sd) == 0 ); + break; + case ADDITEM_OVERAMOUNT: + fail = true; + } + + if( fail ) + { + clif_Mail_getattachment(fd, 1); + return; + } + + weight = data->weight * sd->mail.inbox.msg[i].item.amount; + if( sd->weight + weight > sd->max_weight ) + { + clif_Mail_getattachment(fd, 2); + return; + } + } + + sd->mail.inbox.msg[i].zeny = 0; + memset(&sd->mail.inbox.msg[i].item, 0, sizeof(struct item)); + clif_Mail_read(sd, mail_id); + + intif_Mail_getattach(sd->status.char_id, mail_id); +} + + +/// Request to delete a mail (CZ_MAIL_DELETE). +/// 0243 <mail id>.L +void clif_parse_Mail_delete(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + + if( !chrif_isconnected() ) + return; + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if (i < MAIL_MAX_INBOX) + { + struct mail_message *msg = &sd->mail.inbox.msg[i]; + + if( (msg->item.nameid > 0 && msg->item.amount > 0) || msg->zeny > 0 ) + {// can't delete mail without removing attachment first + clif_Mail_delete(sd->fd, mail_id, 1); + return; + } + + intif_Mail_delete(sd->status.char_id, mail_id); + } +} + + +/// Request to return a mail (CZ_REQ_MAIL_RETURN). +/// 0273 <mail id>.L <receive name>.24B +void clif_parse_Mail_return(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i < MAIL_MAX_INBOX && sd->mail.inbox.msg[i].send_id != 0 ) + intif_Mail_return(sd->status.char_id, mail_id); + else + clif_Mail_return(sd->fd, mail_id, 1); +} + + +/// Request to add an item or Zeny to mail (CZ_MAIL_ADD_ITEM). +/// 0247 <index>.W <amount>.L +void clif_parse_Mail_setattach(int fd, struct map_session_data *sd) +{ + int idx = RFIFOW(fd,2); + int amount = RFIFOL(fd,4); + unsigned char flag; + + if( !chrif_isconnected() ) + return; + if (idx < 0 || amount < 0) + return; + + flag = mail_setitem(sd, idx, amount); + clif_Mail_setattachment(fd,idx,flag); +} + + +/// Request to reset mail item and/or Zeny (CZ_MAIL_RESET_ITEM). +/// 0246 <type>.W +/// type: +/// 0 = reset all +/// 1 = remove item +/// 2 = remove zeny +void clif_parse_Mail_winopen(int fd, struct map_session_data *sd) +{ + int flag = RFIFOW(fd,2); + + if (flag == 0 || flag == 1) + mail_removeitem(sd, 0); + if (flag == 0 || flag == 2) + mail_removezeny(sd, 0); +} + + +/// Request to send mail (CZ_MAIL_SEND). +/// 0248 <packet len>.W <recipient>.24B <title>.40B <body len>.B <body>.?B +void clif_parse_Mail_send(int fd, struct map_session_data *sd) +{ + struct mail_message msg; + int body_len; + + if( !chrif_isconnected() ) + return; + if( sd->state.trading ) + return; + + if( RFIFOW(fd,2) < 69 ) { + ShowWarning("Invalid Msg Len from account %d.\n", sd->status.account_id); + return; + } + + if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) + { + clif_displaymessage(sd->fd,msg_txt(675)); //"Cannot send mails too fast!!." + clif_Mail_send(fd, true); // fail + return; + } + + body_len = RFIFOB(fd,68); + + if (body_len > MAIL_BODY_LENGTH) + body_len = MAIL_BODY_LENGTH; + + if( !mail_setattachment(sd, &msg) ) + { // Invalid Append condition + clif_Mail_send(sd->fd, true); // fail + mail_removeitem(sd,0); + mail_removezeny(sd,0); + return; + } + + msg.id = 0; // id will be assigned by charserver + msg.send_id = sd->status.char_id; + msg.dest_id = 0; // will attempt to resolve name + safestrncpy(msg.send_name, sd->status.name, NAME_LENGTH); + safestrncpy(msg.dest_name, (char*)RFIFOP(fd,4), NAME_LENGTH); + safestrncpy(msg.title, (char*)RFIFOP(fd,28), MAIL_TITLE_LENGTH); + + if (msg.title[0] == '\0') { + return; // Message has no length and somehow client verification was skipped. + } + + if (body_len) + safestrncpy(msg.body, (char*)RFIFOP(fd,69), body_len + 1); + else + memset(msg.body, 0x00, MAIL_BODY_LENGTH); + + msg.timestamp = time(NULL); + if( !intif_Mail_send(sd->status.account_id, &msg) ) + mail_deliveryfail(sd, &msg); + + sd->cansendmail_tick = gettick() + 1000; // 1 Second flood Protection +} + + +/// AUCTION SYSTEM +/// By Zephyrus +/// + +/// Opens/closes the auction window (ZC_AUCTION_WINDOWS). +/// 025f <type>.L +/// type: +/// 0 = open +/// 1 = close +void clif_Auction_openwindow(struct map_session_data *sd) +{ + int fd = sd->fd; + + if( sd->state.storage_flag || sd->state.vending || sd->state.buyingstore || sd->state.trading ) + return; + + WFIFOHEAD(fd,packet_len(0x25f)); + WFIFOW(fd,0) = 0x25f; + WFIFOL(fd,2) = 0; + WFIFOSET(fd,packet_len(0x25f)); +} + + +/// Returns auction item search results (ZC_AUCTION_ITEM_REQ_SEARCH). +/// 0252 <packet len>.W <pages>.L <count>.L { <auction id>.L <seller name>.24B <name id>.W <type>.L <amount>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <now price>.L <max price>.L <buyer name>.24B <delete time>.L }* +void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf) +{ + int i, fd = sd->fd, len = sizeof(struct auction_data); + struct auction_data auction; + struct item_data *item; + int k; + + WFIFOHEAD(fd,12 + (count * 83)); + WFIFOW(fd,0) = 0x252; + WFIFOW(fd,2) = 12 + (count * 83); + WFIFOL(fd,4) = pages; + WFIFOL(fd,8) = count; + + for( i = 0; i < count; i++ ) + { + memcpy(&auction, RBUFP(buf,i * len), len); + k = 12 + (i * 83); + + WFIFOL(fd,k) = auction.auction_id; + safestrncpy((char*)WFIFOP(fd,4+k), auction.seller_name, NAME_LENGTH); + + if( (item = itemdb_exists(auction.item.nameid)) != NULL && item->view_id > 0 ) + WFIFOW(fd,28+k) = item->view_id; + else + WFIFOW(fd,28+k) = auction.item.nameid; + + WFIFOL(fd,30+k) = auction.type; + WFIFOW(fd,34+k) = auction.item.amount; // Always 1 + WFIFOB(fd,36+k) = auction.item.identify; + WFIFOB(fd,37+k) = auction.item.attribute; + WFIFOB(fd,38+k) = auction.item.refine; + WFIFOW(fd,39+k) = auction.item.card[0]; + WFIFOW(fd,41+k) = auction.item.card[1]; + WFIFOW(fd,43+k) = auction.item.card[2]; + WFIFOW(fd,45+k) = auction.item.card[3]; + WFIFOL(fd,47+k) = auction.price; + WFIFOL(fd,51+k) = auction.buynow; + safestrncpy((char*)WFIFOP(fd,55+k), auction.buyer_name, NAME_LENGTH); + WFIFOL(fd,79+k) = (uint32)auction.timestamp; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Result from request to add an item (ZC_ACK_AUCTION_ADD_ITEM). +/// 0256 <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +static void clif_Auction_setitem(int fd, int index, bool fail) +{ + WFIFOHEAD(fd,packet_len(0x256)); + WFIFOW(fd,0) = 0x256; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = fail; + WFIFOSET(fd,packet_len(0x256)); +} + + +/// Request to initialize 'new auction' data (CZ_AUCTION_CREATE). +/// 024b <type>.W +/// type: +/// 0 = create (any other action in auction window) +/// 1 = cancel (cancel pressed on register tab) +/// ? = junk, uninitialized value (ex. when switching between list filters) +void clif_parse_Auction_cancelreg(int fd, struct map_session_data *sd) +{ + if( sd->auction.amount > 0 ) + clif_additem(sd, sd->auction.index, sd->auction.amount, 0); + + sd->auction.amount = 0; +} + + +/// Request to add an item to the action (CZ_AUCTION_ADD_ITEM). +/// 024c <index>.W <count>.L +void clif_parse_Auction_setitem(int fd, struct map_session_data *sd) +{ + int idx = RFIFOW(fd,2) - 2; + int amount = RFIFOL(fd,4); // Always 1 + struct item_data *item; + + if( sd->auction.amount > 0 ) + sd->auction.amount = 0; + + if( idx < 0 || idx >= MAX_INVENTORY ) + { + ShowWarning("Character %s trying to set invalid item index in auctions.\n", sd->status.name); + return; + } + + if( amount != 1 || amount > sd->status.inventory[idx].amount ) + { // By client, amount is always set to 1. Maybe this is a future implementation. + ShowWarning("Character %s trying to set invalid amount in auctions.\n", sd->status.name); + return; + } + + if( (item = itemdb_exists(sd->status.inventory[idx].nameid)) != NULL && !(item->type == IT_ARMOR || item->type == IT_PETARMOR || item->type == IT_WEAPON || item->type == IT_CARD || item->type == IT_ETC) ) + { // Consumable or pets are not allowed + clif_Auction_setitem(sd->fd, idx, true); + return; + } + + if( !pc_can_give_items(sd) || sd->status.inventory[idx].expire_time || + !sd->status.inventory[idx].identify || + !itemdb_canauction(&sd->status.inventory[idx],pc_get_group_level(sd)) ) { // Quest Item or something else + clif_Auction_setitem(sd->fd, idx, true); + return; + } + + sd->auction.index = idx; + sd->auction.amount = amount; + clif_Auction_setitem(fd, idx + 2, false); +} + +/// Result from an auction action (ZC_AUCTION_RESULT). +/// 0250 <result>.B +/// result: +/// 0 = You have failed to bid into the auction +/// 1 = You have successfully bid in the auction +/// 2 = The auction has been canceled +/// 3 = An auction with at least one bidder cannot be canceled +/// 4 = You cannot register more than 5 items in an auction at a time +/// 5 = You do not have enough Zeny to pay the Auction Fee +/// 6 = You have won the auction +/// 7 = You have failed to win the auction +/// 8 = You do not have enough Zeny +/// 9 = You cannot place more than 5 bids at a time +void clif_Auction_message(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,packet_len(0x250)); + WFIFOW(fd,0) = 0x250; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0x250)); +} + + +/// Result of the auction close request (ZC_AUCTION_ACK_MY_SELL_STOP). +/// 025e <result>.W +/// result: +/// 0 = You have ended the auction +/// 1 = You cannot end the auction +/// 2 = Auction ID is incorrect +void clif_Auction_close(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,packet_len(0x25e)); + WFIFOW(fd,0) = 0x25d; // BUG: The client identifies this packet as 0x25d (CZ_AUCTION_REQ_MY_SELL_STOP) + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x25e)); +} + + +/// Request to add an auction (CZ_AUCTION_ADD). +/// 024d <now money>.L <max money>.L <delete hour>.W +void clif_parse_Auction_register(int fd, struct map_session_data *sd) +{ + struct auction_data auction; + struct item_data *item; + + auction.price = RFIFOL(fd,2); + auction.buynow = RFIFOL(fd,6); + auction.hours = RFIFOW(fd,10); + + // Invalid Situations... + if( sd->auction.amount < 1 ) + { + ShowWarning("Character %s trying to register auction without item.\n", sd->status.name); + return; + } + + if( auction.price >= auction.buynow ) + { + ShowWarning("Character %s trying to alter auction prices.\n", sd->status.name); + return; + } + + if( auction.hours < 1 || auction.hours > 48 ) + { + ShowWarning("Character %s trying to enter an invalid time for auction.\n", sd->status.name); + return; + } + + // Auction checks... + if( sd->status.zeny < (auction.hours * battle_config.auction_feeperhour) ) + { + clif_Auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee. + return; + } + + if( auction.buynow > battle_config.auction_maximumprice ) + { // Zeny Limits + auction.buynow = battle_config.auction_maximumprice; + if( auction.price >= auction.buynow ) + auction.price = auction.buynow - 1; + } + + auction.auction_id = 0; + auction.seller_id = sd->status.char_id; + safestrncpy(auction.seller_name, sd->status.name, sizeof(auction.seller_name)); + auction.buyer_id = 0; + memset(auction.buyer_name, '\0', sizeof(auction.buyer_name)); + + if( sd->status.inventory[sd->auction.index].nameid == 0 || sd->status.inventory[sd->auction.index].amount < sd->auction.amount ) + { + clif_Auction_message(fd, 2); // The auction has been canceled + return; + } + + if( (item = itemdb_exists(sd->status.inventory[sd->auction.index].nameid)) == NULL ) + { // Just in case + clif_Auction_message(fd, 2); // The auction has been canceled + return; + } + + safestrncpy(auction.item_name, item->jname, sizeof(auction.item_name)); + auction.type = item->type; + memcpy(&auction.item, &sd->status.inventory[sd->auction.index], sizeof(struct item)); + auction.item.amount = 1; + auction.timestamp = 0; + + if( !intif_Auction_register(&auction) ) + clif_Auction_message(fd, 4); // No Char Server? lets say something to the client + else + { + int zeny = auction.hours*battle_config.auction_feeperhour; + + pc_delitem(sd, sd->auction.index, sd->auction.amount, 1, 6, LOG_TYPE_AUCTION); + sd->auction.amount = 0; + + pc_payzeny(sd, zeny, LOG_TYPE_AUCTION, NULL); + } +} + + +/// Cancels an auction (CZ_AUCTION_ADD_CANCEL). +/// 024e <auction id>.L +void clif_parse_Auction_cancel(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + + intif_Auction_cancel(sd->status.char_id, auction_id); +} + + +/// Closes an auction (CZ_AUCTION_REQ_MY_SELL_STOP). +/// 025d <auction id>.L +void clif_parse_Auction_close(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + + intif_Auction_close(sd->status.char_id, auction_id); +} + + +/// Places a bid on an auction (CZ_AUCTION_BUY). +/// 024f <auction id>.L <money>.L +void clif_parse_Auction_bid(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + int bid = RFIFOL(fd,6); + + if( !pc_can_give_items(sd) ) { //They aren't supposed to give zeny [Inkfish] + clif_displaymessage(sd->fd, msg_txt(246)); + return; + } + + if( bid <= 0 ) + clif_Auction_message(fd, 0); // You have failed to bid into the auction + else if( bid > sd->status.zeny ) + clif_Auction_message(fd, 8); // You do not have enough zeny + else if ( CheckForCharServer() ) // char server is down (bugreport:1138) + clif_Auction_message(fd, 0); // You have failed to bid into the auction + else { + pc_payzeny(sd, bid, LOG_TYPE_AUCTION, NULL); + intif_Auction_bid(sd->status.char_id, sd->status.name, auction_id, bid); + } +} + + +/// Auction Search (CZ_AUCTION_ITEM_SEARCH). +/// 0251 <search type>.W <auction id>.L <search text>.24B <page number>.W +/// search type: +/// 0 = armor +/// 1 = weapon +/// 2 = card +/// 3 = misc +/// 4 = name search +/// 5 = auction id search +void clif_parse_Auction_search(int fd, struct map_session_data* sd) +{ + char search_text[NAME_LENGTH]; + short type = RFIFOW(fd,2), page = RFIFOW(fd,32); + int price = RFIFOL(fd,4); // FIXME: bug #5071 + + clif_parse_Auction_cancelreg(fd, sd); + + safestrncpy(search_text, (char*)RFIFOP(fd,8), sizeof(search_text)); + intif_Auction_requestlist(sd->status.char_id, type, price, search_text, page); +} + + +/// Requests list of own currently active bids or auctions (CZ_AUCTION_REQ_MY_INFO). +/// 025c <type>.W +/// type: +/// 0 = sell (own auctions) +/// 1 = buy (own bids) +void clif_parse_Auction_buysell(int fd, struct map_session_data* sd) +{ + short type = RFIFOW(fd,2) + 6; + clif_parse_Auction_cancelreg(fd, sd); + + intif_Auction_requestlist(sd->status.char_id, type, 0, "", 1); +} + + +/// CASH/POINT SHOP +/// + +/// List of items offered in a cash shop (ZC_PC_CASH_POINT_ITEMLIST). +/// 0287 <packet len>.W <cash point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* +/// 0287 <packet len>.W <cash point>.L <kafra point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* (PACKETVER >= 20070711) +void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) +{ + int fd,i; +#if PACKETVER < 20070711 + const int offset = 8; +#else + const int offset = 12; +#endif + + nullpo_retv(sd); + nullpo_retv(nd); + + fd = sd->fd; + sd->npc_shopid = nd->bl.id; + WFIFOHEAD(fd,offset+nd->u.shop.count*11); + WFIFOW(fd,0) = 0x287; + WFIFOW(fd,2) = offset+nd->u.shop.count*11; + WFIFOL(fd,4) = sd->cashPoints; // Cash Points +#if PACKETVER >= 20070711 + WFIFOL(fd,8) = sd->kafraPoints; // Kafra Points +#endif + + for( i = 0; i < nd->u.shop.count; i++ ) + { + struct item_data* id = itemdb_search(nd->u.shop.shop_item[i].nameid); + WFIFOL(fd,offset+0+i*11) = nd->u.shop.shop_item[i].value; + WFIFOL(fd,offset+4+i*11) = nd->u.shop.shop_item[i].value; // Discount Price + WFIFOB(fd,offset+8+i*11) = itemtype(id->type); + WFIFOW(fd,offset+9+i*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Cashshop Buy Ack (ZC_PC_CASH_POINT_UPDATE). +/// 0289 <cash point>.L <error>.W +/// 0289 <cash point>.L <kafra point>.L <error>.W (PACKETVER >= 20070711) +/// error: +/// 0 = The deal has successfully completed. (ERROR_TYPE_NONE) +/// 1 = The Purchase has failed because the NPC does not exist. (ERROR_TYPE_NPC) +/// 2 = The Purchase has failed because the Kafra Shop System is not working correctly. (ERROR_TYPE_SYSTEM) +/// 3 = You are over your Weight Limit. (ERROR_TYPE_INVENTORY_WEIGHT) +/// 4 = You cannot purchase items while you are in a trade. (ERROR_TYPE_EXCHANGE) +/// 5 = The Purchase has failed because the Item Information was incorrect. (ERROR_TYPE_ITEM_ID) +/// 6 = You do not have enough Kafra Credit Points. (ERROR_TYPE_MONEY) +/// 7 = You can purchase up to 10 items. +/// 8 = Some items could not be purchased. +void clif_cashshop_ack(struct map_session_data* sd, int error) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x289)); + WFIFOW(fd,0) = 0x289; + WFIFOL(fd,2) = sd->cashPoints; +#if PACKETVER < 20070711 + WFIFOW(fd,6) = TOW(error); +#else + WFIFOL(fd,6) = sd->kafraPoints; + WFIFOW(fd,10) = TOW(error); +#endif + WFIFOSET(fd, packet_len(0x289)); +} + + +/// Request to buy item(s) from cash shop (CZ_PC_BUY_CASH_POINT_ITEM). +/// 0288 <name id>.W <amount>.W +/// 0288 <name id>.W <amount>.W <kafra points>.L (PACKETVER >= 20070711) +/// 0288 <packet len>.W <kafra points>.L <count>.W { <amount>.W <name id>.W }.4B*count (PACKETVER >= 20100803) +void clif_parse_cashshop_buy(int fd, struct map_session_data *sd) +{ + int fail = 0; + nullpo_retv(sd); + + if( sd->state.trading || !sd->npc_shopid ) + fail = 1; + else + { +#if PACKETVER < 20101116 + short nameid = RFIFOW(fd,2); + short amount = RFIFOW(fd,4); + int points = RFIFOL(fd,6); + + fail = npc_cashshop_buy(sd, nameid, amount, points); +#else + int len = RFIFOW(fd,2); + int points = RFIFOL(fd,4); + int count = RFIFOW(fd,8); + unsigned short* item_list = (unsigned short*)RFIFOP(fd,10); + + if( len < 10 || len != 10 + count * 4) + { + ShowWarning("Player %u sent incorrect cash shop buy packet (len %u:%u)!\n", sd->status.char_id, len, 10 + count * 4); + return; + } + fail = npc_cashshop_buylist(sd,points,count,item_list); +#endif + } + + clif_cashshop_ack(sd,fail); +} + + +/// Adoption System +/// + +/// Adoption message (ZC_BABYMSG). +/// 0216 <msg>.L +/// msg: +/// 0 = "You cannot adopt more than 1 child." +/// 1 = "You must be at least character level 70 in order to adopt someone." +/// 2 = "You cannot adopt a married person." +void clif_Adopt_reply(struct map_session_data *sd, int type) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,6); + WFIFOW(fd,0) = 0x216; + WFIFOL(fd,2) = type; + WFIFOSET(fd,6); +} + + +/// Adoption confirmation (ZC_REQ_BABY). +/// 01f6 <account id>.L <char id>.L <name>.B +void clif_Adopt_request(struct map_session_data *sd, struct map_session_data *src, int p_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,34); + WFIFOW(fd,0) = 0x1f6; + WFIFOL(fd,2) = src->status.account_id; + WFIFOL(fd,6) = p_id; + memcpy(WFIFOP(fd,10), src->status.name, NAME_LENGTH); + WFIFOSET(fd,34); +} + + +/// Request to adopt a player (CZ_REQ_JOIN_BABY). +/// 01f9 <account id>.L +void clif_parse_Adopt_request(int fd, struct map_session_data *sd) +{ + struct map_session_data *tsd = map_id2sd(RFIFOL(fd,2)), *p_sd = map_charid2sd(sd->status.partner_id); + + if( pc_can_Adopt(sd, p_sd, tsd) ) + { + tsd->adopt_invite = sd->status.account_id; + clif_Adopt_request(tsd, sd, p_sd->status.account_id); + } +} + + +/// Answer to adopt confirmation (CZ_JOIN_BABY). +/// 01f7 <account id>.L <char id>.L <answer>.L +/// answer: +/// 0 = rejected +/// 1 = accepted +void clif_parse_Adopt_reply(int fd, struct map_session_data *sd) +{ + int p1_id = RFIFOL(fd,2); + int p2_id = RFIFOL(fd,6); + int result = RFIFOL(fd,10); + struct map_session_data* p1_sd = map_id2sd(p1_id); + struct map_session_data* p2_sd = map_id2sd(p2_id); + + int pid = sd->adopt_invite; + sd->adopt_invite = 0; + + if( p1_sd == NULL || p2_sd == NULL ) + return; // Both players need to be online + + if( pid != p1_sd->status.account_id ) + return; // Incorrect values + + if( result == 0 ) + return; // Rejected + + pc_adoption(p1_sd, p2_sd, sd); +} + + +/// Convex Mirror (ZC_BOSS_INFO). +/// 0293 <infoType>.B <x>.L <y>.L <minHours>.W <minMinutes>.W <maxHours>.W <maxMinutes>.W <monster name>.51B +/// infoType: +/// 0 = No boss on this map (BOSS_INFO_NOT). +/// 1 = Boss is alive (position update) (BOSS_INFO_ALIVE). +/// 2 = Boss is alive (initial announce) (BOSS_INFO_ALIVE_WITHMSG). +/// 3 = Boss is dead (BOSS_INFO_DEAD). +void clif_bossmapinfo(int fd, struct mob_data *md, short flag) +{ + WFIFOHEAD(fd,70); + memset(WFIFOP(fd,0),0,70); + WFIFOW(fd,0) = 0x293; + + if( md != NULL ) + { + if( md->bl.prev != NULL ) + { // Boss on This Map + if( flag ) + { + WFIFOB(fd,2) = 1; + WFIFOL(fd,3) = md->bl.x; + WFIFOL(fd,7) = md->bl.y; + } + else + WFIFOB(fd,2) = 2; // First Time + } + else if (md->spawn_timer != INVALID_TIMER) + { // Boss is Dead + const struct TimerData * timer_data = get_timer(md->spawn_timer); + unsigned int seconds; + int hours, minutes; + + seconds = DIFF_TICK(timer_data->tick, gettick()) / 1000 + 60; + hours = seconds / (60 * 60); + seconds = seconds - (60 * 60 * hours); + minutes = seconds / 60; + + WFIFOB(fd,2) = 3; + WFIFOW(fd,11) = hours; // Hours + WFIFOW(fd,13) = minutes; // Minutes + } + safestrncpy((char*)WFIFOP(fd,19), md->db->jname, NAME_LENGTH); + } + + WFIFOSET(fd,70); +} + + +/// Requesting equip of a player (CZ_EQUIPWIN_MICROSCOPE). +/// 02d6 <account id>.L +void clif_parse_ViewPlayerEquip(int fd, struct map_session_data* sd) +{ + int charid = RFIFOL(fd, 2); + struct map_session_data* tsd = map_id2sd(charid); + + if (!tsd) + return; + + if( tsd->status.show_equip || pc_has_permission(sd, PC_PERM_VIEW_EQUIPMENT) ) + clif_viewequip_ack(sd, tsd); + else + clif_viewequip_fail(sd); +} + + +/// Request to change equip window tick (CZ_CONFIG). +/// 02d8 <type>.L <value>.L +/// type: +/// 0 = open equip window +/// value: +/// 0 = disabled +/// 1 = enabled +void clif_parse_EquipTick(int fd, struct map_session_data* sd) +{ + bool flag = (bool)RFIFOL(fd,6); + sd->status.show_equip = flag; + clif_equiptickack(sd, flag); +} + + +/// Questlog System [Kevin] [Inkfish] +/// + +/// Sends list of all quest states (ZC_ALL_QUEST_LIST). +/// 02b1 <packet len>.W <num>.L { <quest id>.L <active>.B }*num +void clif_quest_send_list(struct map_session_data * sd) +{ + int fd = sd->fd; + int i; + int len = sd->avail_quests*5+8; + + WFIFOHEAD(fd,len); + WFIFOW(fd, 0) = 0x2b1; + WFIFOW(fd, 2) = len; + WFIFOL(fd, 4) = sd->avail_quests; + + for( i = 0; i < sd->avail_quests; i++ ) + { + WFIFOL(fd, i*5+8) = sd->quest_log[i].quest_id; + WFIFOB(fd, i*5+12) = sd->quest_log[i].state; + } + + WFIFOSET(fd, len); +} + + +/// Sends list of all quest missions (ZC_ALL_QUEST_MISSION). +/// 02b2 <packet len>.W <num>.L { <quest id>.L <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 }*num +void clif_quest_send_mission(struct map_session_data * sd) +{ + int fd = sd->fd; + int i, j; + int len = sd->avail_quests*104+8; + struct mob_db *mob; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = 0x2b2; + WFIFOW(fd, 2) = len; + WFIFOL(fd, 4) = sd->avail_quests; + + for( i = 0; i < sd->avail_quests; i++ ) + { + WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id; + WFIFOL(fd, i*104+12) = sd->quest_log[i].time - quest_db[sd->quest_index[i]].time; + WFIFOL(fd, i*104+16) = sd->quest_log[i].time; + WFIFOW(fd, i*104+20) = quest_db[sd->quest_index[i]].num_objectives; + + for( j = 0 ; j < quest_db[sd->quest_index[i]].num_objectives; j++ ) + { + WFIFOL(fd, i*104+22+j*30) = quest_db[sd->quest_index[i]].mob[j]; + WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j]; + mob = mob_db(quest_db[sd->quest_index[i]].mob[j]); + memcpy(WFIFOP(fd, i*104+28+j*30), mob?mob->jname:"NULL", NAME_LENGTH); + } + } + + WFIFOSET(fd, len); +} + + +/// Notification about a new quest (ZC_ADD_QUEST). +/// 02b3 <quest id>.L <active>.B <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 +void clif_quest_add(struct map_session_data * sd, struct quest * qd, int index) +{ + int fd = sd->fd; + int i; + struct mob_db *mob; + + WFIFOHEAD(fd, packet_len(0x2b3)); + WFIFOW(fd, 0) = 0x2b3; + WFIFOL(fd, 2) = qd->quest_id; + WFIFOB(fd, 6) = qd->state; + WFIFOB(fd, 7) = qd->time - quest_db[index].time; + WFIFOL(fd, 11) = qd->time; + WFIFOW(fd, 15) = quest_db[index].num_objectives; + + for( i = 0; i < quest_db[index].num_objectives; i++ ) + { + WFIFOL(fd, i*30+17) = quest_db[index].mob[i]; + WFIFOW(fd, i*30+21) = qd->count[i]; + mob = mob_db(quest_db[index].mob[i]); + memcpy(WFIFOP(fd, i*30+23), mob?mob->jname:"NULL", NAME_LENGTH); + } + + WFIFOSET(fd, packet_len(0x2b3)); +} + + +/// Notification about a quest being removed (ZC_DEL_QUEST). +/// 02b4 <quest id>.L +void clif_quest_delete(struct map_session_data * sd, int quest_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2b4)); + WFIFOW(fd, 0) = 0x2b4; + WFIFOL(fd, 2) = quest_id; + WFIFOSET(fd, packet_len(0x2b4)); +} + + +/// Notification of an update to the hunting mission counter (ZC_UPDATE_MISSION_HUNT). +/// 02b5 <packet len>.W <mobs>.W { <quest id>.L <mob id>.L <total count>.W <current count>.W }*3 +void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int index) +{ + int fd = sd->fd; + int i; + int len = quest_db[index].num_objectives*12+6; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = 0x2b5; + WFIFOW(fd, 2) = len; + WFIFOW(fd, 4) = quest_db[index].num_objectives; + + for( i = 0; i < quest_db[index].num_objectives; i++ ) + { + WFIFOL(fd, i*12+6) = qd->quest_id; + WFIFOL(fd, i*12+10) = quest_db[index].mob[i]; + WFIFOW(fd, i*12+14) = quest_db[index].count[i]; + WFIFOW(fd, i*12+16) = qd->count[i]; + } + + WFIFOSET(fd, len); +} + + +/// Request to change the state of a quest (CZ_ACTIVE_QUEST). +/// 02b6 <quest id>.L <active>.B +void clif_parse_questStateAck(int fd, struct map_session_data * sd) +{ + quest_update_status(sd, RFIFOL(fd,2), RFIFOB(fd,6)?Q_ACTIVE:Q_INACTIVE); +} + + +/// Notification about the change of a quest state (ZC_ACTIVE_QUEST). +/// 02b7 <quest id>.L <active>.B +void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2b7)); + WFIFOW(fd, 0) = 0x2b7; + WFIFOL(fd, 2) = quest_id; + WFIFOB(fd, 6) = active; + WFIFOSET(fd, packet_len(0x2b7)); +} + + +/// Notification about an NPC's quest state (ZC_QUEST_NOTIFY_EFFECT). +/// 0446 <npc id>.L <x>.W <y>.W <effect>.W <type>.W +/// effect: +/// 0 = none +/// 1 = exclamation mark icon +/// 2 = question mark icon +/// type: +/// 0 = yellow +/// 1 = orange +/// 2 = green +/// 3 = purple +void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color) +{ +#if PACKETVER >= 20090218 + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x446)); + WFIFOW(fd, 0) = 0x446; + WFIFOL(fd, 2) = bl->id; + WFIFOW(fd, 6) = bl->x; + WFIFOW(fd, 8) = bl->y; + WFIFOW(fd, 10) = state; + WFIFOW(fd, 12) = color; + WFIFOSET(fd, packet_len(0x446)); +#endif +} + + +/// Mercenary System +/// + +/// Notification about a mercenary status parameter change (ZC_MER_PAR_CHANGE). +/// 02a2 <var id>.W <value>.L +void clif_mercenary_updatestatus(struct map_session_data *sd, int type) +{ + struct mercenary_data *md; + struct status_data *status; + int fd; + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + status = &md->battle_status; + WFIFOHEAD(fd,packet_len(0x2a2)); + WFIFOW(fd,0) = 0x2a2; + WFIFOW(fd,2) = type; + switch( type ) + { + case SP_ATK1: + { + int atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; + WFIFOL(fd,4) = cap_value(atk, 0, INT16_MAX); + } + break; + case SP_MATK1: + WFIFOL(fd,4) = cap_value(status->matk_max, 0, INT16_MAX); + break; + case SP_HIT: + WFIFOL(fd,4) = status->hit; + break; + case SP_CRITICAL: + WFIFOL(fd,4) = status->cri/10; + break; + case SP_DEF1: + WFIFOL(fd,4) = status->def; + break; + case SP_MDEF1: + WFIFOL(fd,4) = status->mdef; + break; + case SP_MERCFLEE: + WFIFOL(fd,4) = status->flee; + break; + case SP_ASPD: + WFIFOL(fd,4) = status->amotion; + break; + case SP_HP: + WFIFOL(fd,4) = status->hp; + break; + case SP_MAXHP: + WFIFOL(fd,4) = status->max_hp; + break; + case SP_SP: + WFIFOL(fd,4) = status->sp; + break; + case SP_MAXSP: + WFIFOL(fd,4) = status->max_sp; + break; + case SP_MERCKILLS: + WFIFOL(fd,4) = md->mercenary.kill_count; + break; + case SP_MERCFAITH: + WFIFOL(fd,4) = mercenary_get_faith(md); + break; + } + WFIFOSET(fd,packet_len(0x2a2)); +} + + +/// Mercenary base status data (ZC_MER_INIT). +/// 029b <id>.L <atk>.W <matk>.W <hit>.W <crit>.W <def>.W <mdef>.W <flee>.W <aspd>.W +/// <name>.24B <level>.W <hp>.L <maxhp>.L <sp>.L <maxsp>.L <expire time>.L <faith>.W +/// <calls>.L <kills>.L <atk range>.W +void clif_mercenary_info(struct map_session_data *sd) +{ + int fd; + struct mercenary_data *md; + struct status_data *status; + int atk; + + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + status = &md->battle_status; + + WFIFOHEAD(fd,packet_len(0x29b)); + WFIFOW(fd,0) = 0x29b; + WFIFOL(fd,2) = md->bl.id; + + // Mercenary shows ATK as a random value between ATK ~ ATK2 + atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; + WFIFOW(fd,6) = cap_value(atk, 0, INT16_MAX); + WFIFOW(fd,8) = cap_value(status->matk_max, 0, INT16_MAX); + WFIFOW(fd,10) = status->hit; + WFIFOW(fd,12) = status->cri/10; + WFIFOW(fd,14) = status->def; + WFIFOW(fd,16) = status->mdef; + WFIFOW(fd,18) = status->flee; + WFIFOW(fd,20) = status->amotion; + safestrncpy((char*)WFIFOP(fd,22), md->db->name, NAME_LENGTH); + WFIFOW(fd,46) = md->db->lv; + WFIFOL(fd,48) = status->hp; + WFIFOL(fd,52) = status->max_hp; + WFIFOL(fd,56) = status->sp; + WFIFOL(fd,60) = status->max_sp; + WFIFOL(fd,64) = (int)time(NULL) + (mercenary_get_lifetime(md) / 1000); + WFIFOW(fd,68) = mercenary_get_faith(md); + WFIFOL(fd,70) = mercenary_get_calls(md); + WFIFOL(fd,74) = md->mercenary.kill_count; + WFIFOW(fd,78) = md->battle_status.rhw.range; + WFIFOSET(fd,packet_len(0x29b)); +} + + +/// Mercenary skill tree (ZC_MER_SKILLINFO_LIST). +/// 029d <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* +void clif_mercenary_skillblock(struct map_session_data *sd) +{ + struct mercenary_data *md; + int fd, i, len = 4, id, j; + + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd,4+37*MAX_MERCSKILL); + WFIFOW(fd,0) = 0x29d; + for( i = 0; i < MAX_MERCSKILL; i++ ) + { + if( (id = md->db->skill[i].id) == 0 ) + continue; + j = id - MC_SKILLBASE; + WFIFOW(fd,len) = id; + WFIFOL(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+6) = md->db->skill[j].lv; + WFIFOW(fd,len+8) = skill_get_sp(id, md->db->skill[j].lv); + WFIFOW(fd,len+10) = skill_get_range2(&md->bl, id, md->db->skill[j].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,len+36) = 0; // Skillable for Mercenary? + len += 37; + } + + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); +} + + +/// Request to invoke a mercenary menu action (CZ_MER_COMMAND). +/// 029f <command>.B +/// 1 = mercenary information +/// 2 = delete +void clif_parse_mercenary_action(int fd, struct map_session_data* sd) +{ + int option = RFIFOB(fd,2); + if( sd->md == NULL ) + return; + + if( option == 2 ) merc_delete(sd->md, 2); +} + + +/// Mercenary Message +/// message: +/// 0 = Mercenary soldier's duty hour is over. +/// 1 = Your mercenary soldier has been killed. +/// 2 = Your mercenary soldier has been fired. +/// 3 = Your mercenary soldier has ran away. +void clif_mercenary_message(struct map_session_data* sd, int message) +{ + clif_msg(sd, 1266 + message); +} + + +/// Notification about the remaining time of a rental item (ZC_CASH_TIME_COUNTER). +/// 0298 <name id>.W <seconds>.L +void clif_rental_time(int fd, int nameid, int seconds) +{ // '<ItemName>' item will disappear in <seconds/60> minutes. + WFIFOHEAD(fd,packet_len(0x298)); + WFIFOW(fd,0) = 0x298; + WFIFOW(fd,2) = nameid; + WFIFOL(fd,4) = seconds; + WFIFOSET(fd,packet_len(0x298)); +} + + +/// Deletes a rental item from client's inventory (ZC_CASH_ITEM_DELETE). +/// 0299 <index>.W <name id>.W +void clif_rental_expired(int fd, int index, int nameid) +{ // '<ItemName>' item has been deleted from the Inventory + WFIFOHEAD(fd,packet_len(0x299)); + WFIFOW(fd,0) = 0x299; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = nameid; + WFIFOSET(fd,packet_len(0x299)); +} + + +/// Book Reading (ZC_READ_BOOK). +/// 0294 <book id>.L <page>.L +void clif_readbook(int fd, int book_id, int page) +{ + WFIFOHEAD(fd,packet_len(0x294)); + WFIFOW(fd,0) = 0x294; + WFIFOL(fd,2) = book_id; + WFIFOL(fd,6) = page; + WFIFOSET(fd,packet_len(0x294)); +} + + +/// Battlegrounds +/// + +/// Updates HP bar of a camp member (ZC_BATTLEFIELD_NOTIFY_HP). +/// 02e0 <account id>.L <name>.24B <hp>.W <max hp>.W +void clif_bg_hp(struct map_session_data *sd) +{ + unsigned char buf[34]; + const int cmd = 0x2e0; + nullpo_retv(sd); + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = sd->status.account_id; + memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); + + if( sd->battle_status.max_hp > INT16_MAX ) + { // To correctly display the %hp bar. [Skotlex] + WBUFW(buf,30) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WBUFW(buf,32) = 100; + } + else + { + WBUFW(buf,30) = sd->battle_status.hp; + WBUFW(buf,32) = sd->battle_status.max_hp; + } + + clif_send(buf, packet_len(cmd), &sd->bl, BG_AREA_WOS); +} + + +/// Updates the position of a camp member on the minimap (ZC_BATTLEFIELD_NOTIFY_POSITION). +/// 02df <account id>.L <name>.24B <class>.W <x>.W <y>.W +void clif_bg_xy(struct map_session_data *sd) +{ + unsigned char buf[36]; + nullpo_retv(sd); + + WBUFW(buf,0)=0x2df; + WBUFL(buf,2)=sd->status.account_id; + memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); + WBUFW(buf,30)=sd->status.class_; + WBUFW(buf,32)=sd->bl.x; + WBUFW(buf,34)=sd->bl.y; + + clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); +} + +void clif_bg_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[36]; + nullpo_retv(sd); + + WBUFW(buf,0)=0x2df; + WBUFL(buf,2)=sd->status.account_id; + memset(WBUFP(buf,6), 0, NAME_LENGTH); + WBUFW(buf,30)=0; + WBUFW(buf,32)=-1; + WBUFW(buf,34)=-1; + + clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); +} + + +/// Notifies clients of a battleground message (ZC_BATTLEFIELD_CHAT). +/// 02dc <packet len>.W <account id>.L <name>.24B <message>.?B +void clif_bg_message(struct battleground_data *bg, int src_id, const char *name, const char *mes, int len) +{ + struct map_session_data *sd; + unsigned char *buf; + if( (sd = bg_getavailablesd(bg)) == NULL ) + return; + + buf = (unsigned char*)aMalloc((len + NAME_LENGTH + 8)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x2dc; + WBUFW(buf,2) = len + NAME_LENGTH + 8; + WBUFL(buf,4) = src_id; + memcpy(WBUFP(buf,8), name, NAME_LENGTH); + memcpy(WBUFP(buf,32), mes, len); + clif_send(buf,WBUFW(buf,2), &sd->bl, BG); + + if( buf ) + aFree(buf); +} + + +/// Validates and processes battlechat messages [pakpil] (CZ_BATTLEFIELD_CHAT). +/// 0x2db <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_BattleChat(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { + if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + bg_send_message(sd, text, textlen); +} + + +/// Notifies client of a battleground score change (ZC_BATTLEFIELD_NOTIFY_POINT). +/// 02de <camp A points>.W <camp B points>.W +void clif_bg_updatescore(int16 m) +{ + struct block_list bl; + unsigned char buf[6]; + + bl.id = 0; + bl.type = BL_NUL; + bl.m = m; + + WBUFW(buf,0) = 0x2de; + WBUFW(buf,2) = map[m].bgscore_lion; + WBUFW(buf,4) = map[m].bgscore_eagle; + clif_send(buf,packet_len(0x2de),&bl,ALL_SAMEMAP); +} + +void clif_bg_updatescore_single(struct map_session_data *sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2de)); + WFIFOW(fd,0) = 0x2de; + WFIFOW(fd,2) = map[sd->bl.m].bgscore_lion; + WFIFOW(fd,4) = map[sd->bl.m].bgscore_eagle; + WFIFOSET(fd,packet_len(0x2de)); +} + + +/// Battleground camp belong-information (ZC_BATTLEFIELD_NOTIFY_CAMPINFO). +/// 02dd <account id>.L <name>.24B <camp>.W +void clif_sendbgemblem_area(struct map_session_data *sd) +{ + unsigned char buf[33]; + nullpo_retv(sd); + + WBUFW(buf, 0) = 0x2dd; + WBUFL(buf,2) = sd->bl.id; + safestrncpy((char*)WBUFP(buf,6), sd->status.name, NAME_LENGTH); // name don't show in screen. + WBUFW(buf,30) = sd->bg_id; + clif_send(buf,packet_len(0x2dd), &sd->bl, AREA); +} + +void clif_sendbgemblem_single(int fd, struct map_session_data *sd) +{ + nullpo_retv(sd); + WFIFOHEAD(fd,32); + WFIFOW(fd,0) = 0x2dd; + WFIFOL(fd,2) = sd->bl.id; + safestrncpy((char*)WFIFOP(fd,6), sd->status.name, NAME_LENGTH); + WFIFOW(fd,30) = sd->bg_id; + WFIFOSET(fd,packet_len(0x2dd)); +} + + +/// Custom Fonts (ZC_NOTIFY_FONT). +/// 02ef <account_id>.L <font id>.W +void clif_font(struct map_session_data *sd) +{ +#if PACKETVER >= 20080102 + unsigned char buf[8]; + nullpo_retv(sd); + WBUFW(buf,0) = 0x2ef; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = sd->user_font; + clif_send(buf, packet_len(0x2ef), &sd->bl, AREA); +#endif +} + + +/*========================================== + * Instancing Window + *------------------------------------------*/ +int clif_instance(int instance_id, int type, int flag) +{ + struct map_session_data *sd; + struct party_data *p; + unsigned char buf[255]; + + if( (p = party_search(instance[instance_id].party_id)) == NULL || (sd = party_getavailablesd(p)) == NULL ) + return 0; + + switch( type ) + { + case 1: + // S 0x2cb <Instance name>.61B <Standby Position>.W + // Required to start the instancing information window on Client + // This window re-appear each "refresh" of client automatically until type 4 is send to client. + WBUFW(buf,0) = 0x02CB; + memcpy(WBUFP(buf,2),instance[instance_id].name,INSTANCE_NAME_LENGTH); + WBUFW(buf,63) = flag; + clif_send(buf,packet_len(0x02CB),&sd->bl,PARTY); + break; + case 2: + // S 0x2cc <Standby Position>.W + // To announce Instancing queue creation if no maps available + WBUFW(buf,0) = 0x02CC; + WBUFW(buf,2) = flag; + clif_send(buf,packet_len(0x02CC),&sd->bl,PARTY); + break; + case 3: + case 4: + // S 0x2cd <Instance Name>.61B <Instance Remaining Time>.L <Instance Noplayers close time>.L + WBUFW(buf,0) = 0x02CD; + memcpy(WBUFP(buf,2),instance[instance_id].name,61); + if( type == 3 ) + { + WBUFL(buf,63) = (uint32)instance[instance_id].progress_timeout; + WBUFL(buf,67) = 0; + } + else + { + WBUFL(buf,63) = 0; + WBUFL(buf,67) = (uint32)instance[instance_id].idle_timeout; + } + clif_send(buf,packet_len(0x02CD),&sd->bl,PARTY); + break; + case 5: + // S 0x2ce <Message ID>.L + // 0 = Notification (EnterLimitDate update?) + // 1 = The Memorial Dungeon expired; it has been destroyed + // 2 = The Memorial Dungeon's entry time limit expired; it has been destroyed + // 3 = The Memorial Dungeon has been removed. + // 4 = Create failure (removes the instance window) + WBUFW(buf,0) = 0x02CE; + WBUFL(buf,2) = flag; + //WBUFL(buf,6) = EnterLimitDate; + clif_send(buf,packet_len(0x02CE),&sd->bl,PARTY); + break; + } + return 0; +} + +void clif_instance_join(int fd, int instance_id) +{ + if( instance[instance_id].idle_timer != INVALID_TIMER ) + { + WFIFOHEAD(fd,packet_len(0x02CD)); + WFIFOW(fd,0) = 0x02CD; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOL(fd,63) = 0; + WFIFOL(fd,67) = (uint32)instance[instance_id].idle_timeout; + WFIFOSET(fd,packet_len(0x02CD)); + } + else if( instance[instance_id].progress_timer != INVALID_TIMER ) + { + WFIFOHEAD(fd,packet_len(0x02CD)); + WFIFOW(fd,0) = 0x02CD; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOL(fd,63) = (uint32)instance[instance_id].progress_timeout;; + WFIFOL(fd,67) = 0; + WFIFOSET(fd,packet_len(0x02CD)); + } + else + { + WFIFOHEAD(fd,packet_len(0x02CB)); + WFIFOW(fd,0) = 0x02CB; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOW(fd,63) = 0; + WFIFOSET(fd,packet_len(0x02CB)); + } +} + +void clif_instance_leave(int fd) +{ + WFIFOHEAD(fd,packet_len(0x02CE)); + WFIFOW(fd,0) = 0x02ce; + WFIFOL(fd,2) = 4; + WFIFOSET(fd,packet_len(0x02CE)); +} + + +/// Notifies clients about item picked up by a party member (ZC_ITEM_PICKUP_PARTY). +/// 02b8 <account id>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B +void clif_party_show_picker(struct map_session_data * sd, struct item * item_data) +{ +#if PACKETVER >= 20071002 + unsigned char buf[22]; + struct item_data* id = itemdb_search(item_data->nameid); + + WBUFW(buf,0) = 0x2b8; + WBUFL(buf,2) = sd->status.account_id; + WBUFW(buf,6) = item_data->nameid; + WBUFB(buf,8) = item_data->identify; + WBUFB(buf,9) = item_data->attribute; + WBUFB(buf,10) = item_data->refine; + clif_addcards(WBUFP(buf,11), item_data); + WBUFW(buf,19) = id->equip; // equip location + WBUFB(buf,21) = itemtype(id->type); // item type + clif_send(buf, packet_len(0x2b8), &sd->bl, PARTY_SAMEMAP_WOS); +#endif +} + + +/// Display gained exp (ZC_NOTIFY_EXP). +/// 07f6 <account id>.L <amount>.L <var id>.W <exp type>.W +/// var id: +/// SP_BASEEXP, SP_JOBEXP +/// exp type: +/// 0 = normal exp gain/loss +/// 1 = quest exp gain/loss +void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7f6)); + WFIFOW(fd,0) = 0x7f6; + WFIFOL(fd,2) = sd->bl.id; + WFIFOL(fd,6) = exp; + WFIFOW(fd,10) = type; + WFIFOW(fd,12) = quest?1:0;// Normal exp is shown in yellow, quest exp is shown in purple. + WFIFOSET(fd,packet_len(0x7f6)); +} + + +/// Displays digital clock digits on top of the screen (ZC_SHOWDIGIT). +/// type: +/// 0 = Displays 'value' for 5 seconds. +/// 1 = Incremental counter (1 tick/second), negated 'value' specifies start value (e.g. using -10 lets the counter start at 10). +/// 2 = Decremental counter (1 tick/second), negated 'value' specifies start value (does not stop when reaching 0, but overflows). +/// 3 = Decremental counter (1 tick/second), 'value' specifies start value (stops when reaching 0, displays at most 2 digits). +/// value: +/// Except for type 3 it is interpreted as seconds for displaying as DD:HH:MM:SS, HH:MM:SS, MM:SS or SS (leftmost '00' is not displayed). +void clif_showdigit(struct map_session_data* sd, unsigned char type, int value) +{ + WFIFOHEAD(sd->fd, packet_len(0x1b1)); + WFIFOW(sd->fd,0) = 0x1b1; + WFIFOB(sd->fd,2) = type; + WFIFOL(sd->fd,3) = value; + WFIFOSET(sd->fd, packet_len(0x1b1)); +} + + +/// Notification of the state of client command /effect (CZ_LESSEFFECT). +/// 021d <state>.L +/// state: +/// 0 = Full effects +/// 1 = Reduced effects +/// +/// NOTE: The state is used on Aegis for sending skill unit packet +/// 0x11f (ZC_SKILL_ENTRY) instead of 0x1c9 (ZC_SKILL_ENTRY2) +/// whenever possible. Due to the way the decision check is +/// constructed, this state tracking was rendered useless, +/// as the only skill unit, that is sent with 0x1c9 is +/// Graffiti. +void clif_parse_LessEffect(int fd, struct map_session_data* sd) +{ + int isLess = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + sd->state.lesseffect = ( isLess != 0 ); +} + +/// S 07e4 <length>.w <option>.l <val>.l {<index>.w <amount>.w).4b* +void clif_parse_ItemListWindowSelected(int fd, struct map_session_data* sd) { + int n = (RFIFOW(fd,2)-12) / 4; + int type = RFIFOL(fd,4); + int flag = RFIFOL(fd,8); // Button clicked: 0 = Cancel, 1 = OK + unsigned short* item_list = (unsigned short*)RFIFOP(fd,12); + + if( sd->state.trading || sd->npc_shopid ) + return; + + if( flag == 0 || n == 0) { + clif_menuskill_clear(sd); + return; // Canceled by player. + } + + if( sd->menuskill_id != SO_EL_ANALYSIS && sd->menuskill_id != GN_CHANGEMATERIAL ) { + clif_menuskill_clear(sd); + return; // Prevent hacking. + } + + switch( type ) { + case 0: // Change Material + skill_changematerial(sd,n,item_list); + break; + case 1: // Level 1: Pure to Rough + case 2: // Level 2: Rough to Pure + skill_elementalanalysis(sd,n,type,item_list); + break; + } + clif_menuskill_clear(sd); + + return; +} + +/*========================================== + * Elemental System + *==========================================*/ +void clif_elemental_updatestatus(struct map_session_data *sd, int type) { + struct elemental_data *ed; + struct status_data *status; + int fd; + + if( sd == NULL || (ed = sd->ed) == NULL ) + return; + + fd = sd->fd; + status = &ed->battle_status; + WFIFOHEAD(fd,8); + WFIFOW(fd,0) = 0x81e; + WFIFOW(fd,2) = type; + switch( type ) { + case SP_HP: + WFIFOL(fd,4) = status->hp; + break; + case SP_MAXHP: + WFIFOL(fd,4) = status->max_hp; + break; + case SP_SP: + WFIFOL(fd,4) = status->sp; + break; + case SP_MAXSP: + WFIFOL(fd,4) = status->max_sp; + break; + } + WFIFOSET(fd,8); +} + +void clif_elemental_info(struct map_session_data *sd) { + int fd; + struct elemental_data *ed; + struct status_data *status; + + if( sd == NULL || (ed = sd->ed) == NULL ) + return; + + fd = sd->fd; + status = &ed->battle_status; + + WFIFOHEAD(fd,22); + WFIFOW(fd, 0) = 0x81d; + WFIFOL(fd, 2) = ed->bl.id; + WFIFOL(fd, 6) = status->hp; + WFIFOL(fd,10) = status->max_hp; + WFIFOL(fd,14) = status->sp; + WFIFOL(fd,18) = status->max_sp; + WFIFOSET(fd,22); +} + + +/// Buying Store System +/// + +/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE). +/// 0810 <slots>.B +void clif_buyingstore_open(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x810)); + WFIFOW(fd,0) = 0x810; + WFIFOB(fd,2) = sd->buyingstore.slots; + WFIFOSET(fd,packet_len(0x810)); +} + + +/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE). +/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }* +/// result: +/// 0 = cancel +/// 1 = open +static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 8; + uint8* itemlist; + char storename[MESSAGE_SIZE]; + unsigned char result; + int zenylimit; + unsigned int count, packet_len; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + // TODO: Make this check global for all variable length packets. + if( packet_len < 89 ) + {// minimum packet length + ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id); + return; + } + + zenylimit = RFIFOL(fd,info->pos[1]); + result = RFIFOL(fd,info->pos[2]); + safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename)); + itemlist = RFIFOP(fd,info->pos[4]); + + // so that buyingstore_create knows, how many elements it has access to + packet_len-= info->pos[4]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_create(sd, zenylimit, result, storename, itemlist, count); +} + + +/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER). +/// 0812 <result>.W <total weight>.L +/// result: +/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED) +/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT) +/// 8 = "No sale (purchase) information available." (0x705) +/// ? = nothing +void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x812)); + WFIFOW(fd,0) = 0x812; + WFIFOW(fd,2) = result; + WFIFOL(fd,4) = weight; + WFIFOSET(fd,packet_len(0x812)); +} + + +/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE). +/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }* +void clif_buyingstore_myitemlist(struct map_session_data* sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,12+sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x813; + WFIFOW(fd,2) = 12+sd->buyingstore.slots*9; + WFIFOL(fd,4) = sd->bl.id; + WFIFOL(fd,8) = sd->buyingstore.zenylimit; + + for( i = 0; i < sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price; + WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount; + WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid)); + WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY). +/// 0814 <account id>.L <store name>.80B +void clif_buyingstore_entry(struct map_session_data* sd) +{ + uint8 buf[86]; + + WBUFW(buf,0) = 0x814; + WBUFL(buf,2) = sd->bl.id; + memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE); + + clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS); +} +void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x814)); + WFIFOW(fd,0) = 0x814; + WFIFOL(fd,2) = pl_sd->bl.id; + memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE); + WFIFOSET(fd,packet_len(0x814)); +} + + +/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE). +/// 0815 +static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd) +{ + buyingstore_close(sd); +} + + +/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY). +/// 0816 <account id>.L +void clif_buyingstore_disappear_entry(struct map_session_data* sd) +{ + uint8 buf[6]; + + WBUFW(buf,0) = 0x816; + WBUFL(buf,2) = sd->bl.id; + + clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS); +} +void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x816)); + WFIFOW(fd,0) = 0x816; + WFIFOL(fd,2) = pl_sd->bl.id; + WFIFOSET(fd,packet_len(0x816)); +} + + +/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE). +/// 0817 <account id>.L +static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd) +{ + int account_id; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + buyingstore_open(sd, account_id); +} + + +/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE). +/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }* +void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x818; + WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9; + WFIFOL(fd,4) = pl_sd->bl.id; + WFIFOL(fd,8) = pl_sd->buyer_id; + WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit; + + for( i = 0; i < pl_sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price; + WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official. + WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid)); + WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE). +/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }* +static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 6; + uint8* itemlist; + int account_id; + unsigned int count, packet_len, buyer_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 12 ) + {// minimum packet length + ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id); + return; + } + + account_id = RFIFOL(fd,info->pos[1]); + buyer_id = RFIFOL(fd,info->pos[2]); + itemlist = RFIFOP(fd,info->pos[3]); + + // so that buyingstore_trade knows, how many elements it has access to + packet_len-= info->pos[3]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_trade(sd, account_id, buyer_id, itemlist, count); +} + + +/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER). +/// 081a <result>.W +/// result: +/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY) +/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE) +/// ? = nothing +void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81a)); + WFIFOW(fd,0) = 0x81a; + WFIFOW(fd,2) = result; + WFIFOSET(fd,packet_len(0x81a)); +} + + +/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE). +/// 081b <name id>.W <amount>.W <limit zeny>.L +void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81b)); + WFIFOW(fd,0) = 0x81b; + WFIFOW(fd,2) = nameid; + WFIFOW(fd,4) = amount; // amount of nameid received + WFIFOL(fd,6) = sd->buyingstore.zenylimit; + WFIFOSET(fd,packet_len(0x81b)); +} + + +/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE). +/// 081c <index>.W <amount>.W <price>.L +/// message: +/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE) +/// +/// NOTE: This function has to be called _instead_ of clif_delitem/clif_dropitem. +void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81c)); + WFIFOW(fd,0) = 0x81c; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself + WFIFOSET(fd,packet_len(0x81c)); +} + + +/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER). +/// 0824 <result>.W <name id>.W +/// result: +/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL) +/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT) +/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY) +/// ? = nothing +void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x824)); + WFIFOW(fd,0) = 0x824; + WFIFOW(fd,2) = result; + WFIFOW(fd,4) = nameid; + WFIFOSET(fd,packet_len(0x824)); +} + + +/// Search Store Info System +/// + +/// Request to search for stores (CZ_SEARCH_STORE_INFO). +/// 0835 <packet len>.W <type>.B <max price>.L <min price>.L <name id count>.B <card count>.B { <name id>.W }* { <card>.W }* +/// type: +/// 0 = Vending +/// 1 = Buying Store +/// +/// NOTE: The client determines the item ids by specifying a name and optionally, +/// amount of card slots. If the client does not know about the item it +/// cannot be searched. +static void clif_parse_SearchStoreInfo(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 2; + const uint8* itemlist; + const uint8* cardlist; + unsigned char type; + unsigned int min_price, max_price, packet_len, count, item_count, card_count; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 15 ) + {// minimum packet length + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 15, packet_len, sd->bl.id); + return; + } + + type = RFIFOB(fd,info->pos[1]); + max_price = RFIFOL(fd,info->pos[2]); + min_price = RFIFOL(fd,info->pos[3]); + item_count = RFIFOB(fd,info->pos[4]); + card_count = RFIFOB(fd,info->pos[5]); + itemlist = RFIFOP(fd,info->pos[6]); + cardlist = RFIFOP(fd,info->pos[6]+blocksize*item_count); + + // check, if there is enough data for the claimed count of items + packet_len-= info->pos[6]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_SearchStoreInfo: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + if( count < item_count+card_count ) + { + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected count=%u, count=%u, account_id=%d).\n", item_count+card_count, count, sd->bl.id); + return; + } + + searchstore_query(sd, type, min_price, max_price, (const unsigned short*)itemlist, item_count, (const unsigned short*)cardlist, card_count); +} + + +/// Results for a store search request (ZC_SEARCH_STORE_INFO_ACK). +/// 0836 <packet len>.W <is first page>.B <is next page>.B <remaining uses>.B { <store id>.L <account id>.L <shop name>.80B <nameid>.W <item type>.B <price>.L <amount>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +/// is first page: +/// 0 = appends to existing results +/// 1 = clears previous results before displaying this result set +/// is next page: +/// 0 = no "next" label +/// 1 = "next" label to retrieve more results +void clif_search_store_info_ack(struct map_session_data* sd) +{ + const unsigned int blocksize = MESSAGE_SIZE+26; + int fd = sd->fd; + unsigned int i, start, end; + + start = sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE; + end = min(sd->searchstore.count, start+SEARCHSTORE_RESULTS_PER_PAGE); + + WFIFOHEAD(fd,7+(end-start)*blocksize); + WFIFOW(fd,0) = 0x836; + WFIFOW(fd,2) = 7+(end-start)*blocksize; + WFIFOB(fd,4) = !sd->searchstore.pages; + WFIFOB(fd,5) = searchstore_querynext(sd); + WFIFOB(fd,6) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); + + for( i = start; i < end; i++ ) + { + struct s_search_store_info_item* ssitem = &sd->searchstore.items[i]; + struct item it; + + WFIFOL(fd,i*blocksize+ 7) = ssitem->store_id; + WFIFOL(fd,i*blocksize+11) = ssitem->account_id; + memcpy(WFIFOP(fd,i*blocksize+15), ssitem->store_name, MESSAGE_SIZE); + WFIFOW(fd,i*blocksize+15+MESSAGE_SIZE) = ssitem->nameid; + WFIFOB(fd,i*blocksize+17+MESSAGE_SIZE) = itemtype(itemdb_type(ssitem->nameid)); + WFIFOL(fd,i*blocksize+18+MESSAGE_SIZE) = ssitem->price; + WFIFOW(fd,i*blocksize+22+MESSAGE_SIZE) = ssitem->amount; + WFIFOB(fd,i*blocksize+24+MESSAGE_SIZE) = ssitem->refine; + + // make-up an item for clif_addcards + memset(&it, 0, sizeof(it)); + memcpy(&it.card, &ssitem->card, sizeof(it.card)); + it.nameid = ssitem->nameid; + it.amount = ssitem->amount; + + clif_addcards(WFIFOP(fd,i*blocksize+25+MESSAGE_SIZE), &it); + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notification of failure when searching for stores (ZC_SEARCH_STORE_INFO_FAILED). +/// 0837 <reason>.B +/// reason: +/// 0 = "No matching stores were found." (0x70b) +/// 1 = "There are too many results. Please enter more detailed search term." (0x6f8) +/// 2 = "You cannot search anymore." (0x706) +/// 3 = "You cannot search yet." (0x708) +/// 4 = "No sale (purchase) information available." (0x705) +void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x837)); + WFIFOW(fd,0) = 0x837; + WFIFOB(fd,2) = reason; + WFIFOSET(fd,packet_len(0x837)); +} + + +/// Request to display next page of results (CZ_SEARCH_STORE_INFO_NEXT_PAGE). +/// 0838 +static void clif_parse_SearchStoreInfoNextPage(int fd, struct map_session_data* sd) +{ + searchstore_next(sd); +} + + +/// Opens the search store window (ZC_OPEN_SEARCH_STORE_INFO). +/// 083a <type>.W <remaining uses>.B +/// type: +/// 0 = Search Stores +/// 1 = Search Stores (Cash), asks for confirmation, when clicking a store +void clif_open_search_store_info(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83a)); + WFIFOW(fd,0) = 0x83a; + WFIFOW(fd,2) = sd->searchstore.effect; +#if PACKETVER > 20100701 + WFIFOB(fd,4) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); +#endif + WFIFOSET(fd,packet_len(0x83a)); +} + + +/// Request to close the store search window (CZ_CLOSE_SEARCH_STORE_INFO). +/// 083b +static void clif_parse_CloseSearchStoreInfo(int fd, struct map_session_data* sd) +{ + searchstore_close(sd); +} + + +/// Request to invoke catalog effect on a store from search results (CZ_SSILIST_ITEM_CLICK). +/// 083c <account id>.L <store id>.L <nameid>.W +static void clif_parse_SearchStoreInfoListItemClick(int fd, struct map_session_data* sd) +{ + unsigned short nameid; + int account_id, store_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + account_id = RFIFOL(fd,info->pos[0]); + store_id = RFIFOL(fd,info->pos[1]); + nameid = RFIFOW(fd,info->pos[2]); + + searchstore_click(sd, account_id, store_id, nameid); +} + + +/// Notification of the store position on current map (ZC_SSILIST_ITEM_CLICK_ACK). +/// 083d <xPos>.W <yPos>.W +void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83d)); + WFIFOW(fd,0) = 0x83d; + WFIFOW(fd,2) = x; + WFIFOW(fd,4) = y; + WFIFOSET(fd,packet_len(0x83d)); +} + + +/// Parse function for packet debugging. +void clif_parse_debug(int fd,struct map_session_data *sd) +{ + int cmd, packet_len; + + // clif_parse ensures, that there is at least 2 bytes of data + cmd = RFIFOW(fd,0); + + if( sd ) + { + packet_len = packet_db[sd->packet_ver][cmd].len; + + if( packet_len == 0 ) + {// unknown + packet_len = RFIFOREST(fd); + } + else if( packet_len == -1 ) + {// variable length + packet_len = RFIFOW(fd,2); // clif_parse ensures, that this amount of data is already received + } + ShowDebug("Packet debug of 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } + else + { + packet_len = RFIFOREST(fd); + ShowDebug("Packet debug of 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + ShowDump(RFIFOP(fd,0), packet_len); +} +/*========================================== + * Server tells client to display a window similar to Magnifier (item) one + * Server populates the window with avilable elemental converter options according to player's inventory + *------------------------------------------*/ +int clif_elementalconverter_list(struct map_session_data *sd) { + int i,c,view,fd; + + nullpo_ret(sd); + + +/// Main client packet processing function + fd=sd->fd; + WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB *2+4); + WFIFOW(fd, 0)=0x1ad; + + for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if( skill_can_produce_mix(sd,skill_produce_db[i].nameid,23, 1) ){ + if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) + WFIFOW(fd,c*2+ 4)= view; + else + WFIFOW(fd,c*2+ 4)= skill_produce_db[i].nameid; + c++; + } + } + WFIFOW(fd,2) = c*2+4; + WFIFOSET(fd, WFIFOW(fd,2)); + if (c > 0) { + sd->menuskill_id = SA_CREATECON; + sd->menuskill_val = c; + } + + return 0; +} +/** + * Rune Knight + **/ +void clif_millenniumshield(struct map_session_data *sd, short shields ) { +#if PACKETVER >= 20081217 + unsigned char buf[10]; + + WBUFW(buf,0) = 0x440; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = shields; + WBUFW(buf,8) = 0; + clif_send(buf,packet_len(0x440),&sd->bl,AREA); +#endif +} +/** + * Warlock + **/ +/*========================================== + * Spellbook list [LimitLine/3CeAM] + *------------------------------------------*/ +int clif_spellbook_list(struct map_session_data *sd) +{ + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) + { + if( itemdb_is_spellbook(sd->status.inventory[i].nameid) ) + { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + + if( c > 0 ) + { + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + sd->menuskill_id = WL_READING_SB; + sd->menuskill_val = c; + } + else{ + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK, 0); + } + + return 1; +} +/** + * Mechanic + **/ +/*========================================== + * Magic Decoy Material List + *------------------------------------------*/ +int clif_magicdecoy_list(struct map_session_data *sd, uint16 skill_lv, short x, short y) { + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { + if( itemdb_is_element(sd->status.inventory[i].nameid) ) { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + if( c > 0 ) { + sd->menuskill_id = NC_MAGICDECOY; + sd->menuskill_val = skill_lv; + sd->sc.comet_x = x; + sd->sc.comet_y = y; + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + } else { + clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); + return 0; + } + + return 1; +} +/** + * Guilotine Cross + **/ +/*========================================== + * Guillotine Cross Poisons List + *------------------------------------------*/ +int clif_poison_list(struct map_session_data *sd, uint16 skill_lv) { + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { + if( itemdb_is_poison(sd->status.inventory[i].nameid) ) { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + if( c > 0 ) { + sd->menuskill_id = GC_POISONINGWEAPON; + sd->menuskill_val = skill_lv; + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + } else { + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_GUILLONTINE_POISON,0); + return 0; + } + + return 1; +} +int clif_autoshadowspell_list(struct map_session_data *sd) { + int fd, i, c; + nullpo_ret(sd); + fd = sd->fd; + if( !fd ) return 0; + + if( sd->menuskill_id == SC_AUTOSHADOWSPELL ) + return 0; + + WFIFOHEAD(fd, 2 * 6 + 4); + WFIFOW(fd,0) = 0x442; + for( i = 0, c = 0; i < MAX_SKILL; i++ ) + if( sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED && sd->status.skill[i].id > 0 && + sd->status.skill[i].id < GS_GLITTERING && skill_get_type(sd->status.skill[i].id) == BF_MAGIC ) + { // Can't auto cast both Extended class and 3rd class skills. + WFIFOW(fd,8+c*2) = sd->status.skill[i].id; + c++; + } + + if( c > 0 ) { + WFIFOW(fd,2) = 8 + c * 2; + WFIFOL(fd,4) = c; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = SC_AUTOSHADOWSPELL; + sd->menuskill_val = c; + } else { + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + clif_skill_fail(sd,SC_AUTOSHADOWSPELL,USESKILL_FAIL_IMITATION_SKILL_NONE,0); + } + + return 1; +} +/*=========================================== + * Skill list for Four Elemental Analysis + * and Change Material skills. + *------------------------------------------*/ +int clif_skill_itemlistwindow( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv ) +{ +#if PACKETVER >= 20090922 + int fd; + + nullpo_ret(sd); + + sd->menuskill_id = skill_id; // To prevent hacking. + sd->menuskill_val = skill_lv; + + if( skill_id == GN_CHANGEMATERIAL ) + skill_lv = 0; // Changematerial + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x7e3)); + WFIFOW(fd,0) = 0x7e3; + WFIFOL(fd,2) = skill_lv; + WFIFOL(fd,4) = 0; + WFIFOSET(fd,packet_len(0x7e3)); + +#endif + + return 1; + +} +/** + * Sends a new status without a tick (currently used by the new mounts) + **/ +int clif_status_load_notick(struct block_list *bl,int type,int flag,int val1, int val2, int val3) { + unsigned char buf[32]; + + nullpo_ret(bl); + + WBUFW(buf,0)=0x043f; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; + WBUFL(buf,9) = 0; + WBUFL(buf,13) = val1; + WBUFL(buf,17) = val2; + WBUFL(buf,21) = val3; + + clif_send(buf,packet_len(0x043f),bl,AREA); + return 0; +} +//Notifies FD of ID's type +int clif_status_load_single(int fd, int id,int type,int flag,int val1, int val2, int val3) { + WFIFOHEAD(fd, packet_len(0x043f)); + WFIFOW(fd,0)=0x043f; + WFIFOW(fd,2)=type; + WFIFOL(fd,4)=id; + WFIFOB(fd,8)=flag; + WFIFOL(fd,9) = 0; + WFIFOL(fd,13) = val1; + WFIFOL(fd,17) = val2; + WFIFOL(fd,21) = val3; + WFIFOSET(fd, packet_len(0x043f)); + return 0; +} +// msgstringtable.txt +// 0x291 <line>.W +void clif_msgtable(int fd, int line) { + WFIFOHEAD(fd, packet_len(0x291)); + WFIFOW(fd, 0) = 0x291; + WFIFOW(fd, 2) = line; + WFIFOSET(fd, packet_len(0x291)); +} + +// msgstringtable.txt +// 0x7e2 <line>.W <value>.L +void clif_msgtable_num(int fd, int line, int num) { +#if PACKETVER >= 20090805 + WFIFOHEAD(fd, packet_len(0x7e2)); + WFIFOW(fd, 0) = 0x7e2; + WFIFOW(fd, 2) = line; + WFIFOL(fd, 4) = num; + WFIFOSET(fd, packet_len(0x7e2)); +#endif +} +/*========================================== + * used by SC_AUTOSHADOWSPELL + * RFIFOL(fd,2) - flag (currently not used) + *------------------------------------------*/ +void clif_parse_SkillSelectMenu(int fd, struct map_session_data *sd) { + + if( sd->menuskill_id != SC_AUTOSHADOWSPELL ) + return; + + if( pc_istrading(sd) ) { + clif_skill_fail(sd,sd->ud.skill_id,0,0); + clif_menuskill_clear(sd); + return; + } + + skill_select_menu(sd,RFIFOW(fd,6)); + + clif_menuskill_clear(sd); +} +/*========================================== + * Kagerou/Oboro amulet spirit + *------------------------------------------*/ +void clif_talisman(struct map_session_data *sd,short type) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x08cf; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=type; + WBUFW(buf,8)=sd->talisman[type]; + clif_send(buf,packet_len(0x08cf),&sd->bl,AREA); +} +/// Move Item from or to Personal Tab (CZ_WHATSOEVER) [FE] +/// 0907 <index>.W +/// +/// R 0908 <index>.w <type>.b +/// type: +/// 0 = move item to personal tab +/// 1 = move item to normal tab +void clif_parse_MoveItem(int fd, struct map_session_data *sd) { +#if PACKETVER >= 20111122 + int index; + + /* can't move while dead. */ + if(pc_isdead(sd)) { + return; + } + + index = RFIFOW(fd,2)-2; + + if (index < 0 || index >= MAX_INVENTORY) + return; + + if ( sd->status.inventory[index].favorite && RFIFOB(fd, 4) == 1 ) + sd->status.inventory[index].favorite = 0; + else if( RFIFOB(fd, 4) == 0 ) + sd->status.inventory[index].favorite = 1; + else + return;/* nothing to do. */ + + clif_favorite_item(sd, index); +#endif +} + + +/// Items that are in favorite tab of inventory (ZC_ITEM_FAVORITE). +/// 0900 <index>.W <favorite>.B +void clif_favorite_item(struct map_session_data* sd, unsigned short index) { + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x908)); + WFIFOW(fd,0) = 0x908; + WFIFOW(fd,2) = index+2; + WFIFOL(fd,4) = (sd->status.inventory[index].favorite == 1) ? 0 : 1; + WFIFOSET(fd,packet_len(0x908)); +} + +void clif_snap( struct block_list *bl, short x, short y ) { + unsigned char buf[10]; + + WBUFW(buf,0) = 0x8d2; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = x; + WBUFW(buf,8) = y; + + clif_send(buf,packet_len(0x8d2),bl,AREA); +} + +void clif_monster_hp_bar( struct mob_data* md, int fd ) { +#if PACKETVER >= 20120404 + WFIFOHEAD(fd,packet_len(0x977)); + + WFIFOW(fd,0) = 0x977; + WFIFOL(fd,2) = md->bl.id; + WFIFOL(fd,6) = md->status.hp; + WFIFOL(fd,10) = md->status.max_hp; + + WFIFOSET(fd,packet_len(0x977)); +#endif +} + +/*========================================== + * Main client packet processing function + *------------------------------------------*/ +static int clif_parse(int fd) +{ + int cmd, packet_ver, packet_len, err; + TBL_PC* sd; + int pnum; + + //TODO apply delays or disconnect based on packet throughput [FlavioJS] + // Note: "click masters" can do 80+ clicks in 10 seconds + + for( pnum = 0; pnum < 3; ++pnum )// Limit max packets per cycle to 3 (delay packet spammers) [FlavioJS] -- This actually aids packet spammers, but stuff like /str+ gets slow without it [Ai4rei] + { // begin main client packet processing loop + + sd = (TBL_PC *)session[fd]->session_data; + if (session[fd]->flag.eof) { + if (sd) { + if (sd->state.autotrade) { + //Disassociate character from the socket connection. + session[fd]->session_data = NULL; + sd->fd = 0; + ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off (using @autotrade).\n", sd->status.name); + } else + if (sd->state.active) { + // Player logout display [Valaris] + ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off.\n", sd->status.name); + clif_quitsave(fd, sd); + } else { + //Unusual logout (during log on/off/map-changer procedure) + ShowInfo("Player AID:%d/CID:%d logged off.\n", sd->status.account_id, sd->status.char_id); + map_quit(sd); + } + } else { + ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip2str(session[fd]->client_addr, NULL)); + } + do_close(fd); + return 0; + } + + if (RFIFOREST(fd) < 2) + return 0; + + cmd = RFIFOW(fd,0); + + // identify client's packet version + if (sd) { + packet_ver = sd->packet_ver; + } else { + // check authentification packet to know packet version + packet_ver = clif_guess_PacketVer(fd, 0, &err); + if( err ) {// failed to identify packet version + ShowInfo("clif_parse: Disconnecting session #%d with unknown packet version%s (p:0x%04x,l:%d).\n", fd, ( + err == 1 ? "" : + err == 2 ? ", possibly for having an invalid account_id" : + err == 3 ? ", possibly for having an invalid char_id." : + /* Uncomment when checks are added in clif_guess_PacketVer. [FlavioJS] + err == 4 ? ", possibly for having an invalid login_id1." : + err == 5 ? ", possibly for having an invalid client_tick." : + */ + err == 6 ? ", possibly for having an invalid sex." : + ". ERROR invalid error code"), cmd, RFIFOREST(fd)); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // Rejected from Server + WFIFOSET(fd,packet_len(0x6a)); + +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + + RFIFOSKIP(fd, RFIFOREST(fd)); + set_eof(fd); + return 0; + } + } + + // filter out invalid / unsupported packets + if (cmd > MAX_PACKET_DB || packet_db[packet_ver][cmd].len == 0) { + ShowWarning("clif_parse: Received unsupported packet (packet 0x%04x, %d bytes received), disconnecting session #%d.\n", cmd, RFIFOREST(fd), fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + set_eof(fd); + return 0; + } + + // determine real packet length + packet_len = packet_db[packet_ver][cmd].len; + if (packet_len == -1) { // variable-length packet + if (RFIFOREST(fd) < 4) + return 0; + + packet_len = RFIFOW(fd,2); + if (packet_len < 4 || packet_len > 32768) { + ShowWarning("clif_parse: Received packet 0x%04x specifies invalid packet_len (%d), disconnecting session #%d.\n", cmd, packet_len, fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + set_eof(fd); + return 0; + } + } + if ((int)RFIFOREST(fd) < packet_len) + return 0; // not enough data received to form the packet + + if( packet_db[packet_ver][cmd].func == clif_parse_debug ) + packet_db[packet_ver][cmd].func(fd, sd); + else if( packet_db[packet_ver][cmd].func != NULL ) { + if( !sd && packet_db[packet_ver][cmd].func != clif_parse_WantToConnection ) + ; //Only valid packet when there is no session + else + if( sd && sd->bl.prev == NULL && packet_db[packet_ver][cmd].func != clif_parse_LoadEndAck ) + ; //Only valid packet when player is not on a map + else + if( sd && session[sd->fd]->flag.eof ) + ; //No more packets accepted + else + packet_db[packet_ver][cmd].func(fd, sd); + } +#ifdef DUMP_UNKNOWN_PACKET + else { + const char* packet_txt = "save/packet.txt"; + FILE* fp; + + if( ( fp = fopen( packet_txt , "a" ) ) != NULL ) { + if( sd ) { + fprintf(fp, "Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } else { + fprintf(fp, "Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + WriteDump(fp, RFIFOP(fd,0), packet_len); + fprintf(fp, "\n"); + fclose(fp); + } else { + ShowError("Failed to write '%s'.\n", packet_txt); + + // Dump on console instead + if( sd ) { + ShowDebug("Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } else { + ShowDebug("Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + ShowDump(RFIFOP(fd,0), packet_len); + } + } +#endif + + RFIFOSKIP(fd, packet_len); + + }; // main loop end + + return 0; +} + +/*========================================== + * Reads packet_db.txt and setups its array reference + *------------------------------------------*/ +static int packetdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int cmd,i,j,packet_ver; + int max_cmd=-1; + int skip_ver = 0; + int warned = 0; + char *str[64],*p,*str2[64],*p2,w1[64],w2[64]; + int packet_len_table[MAX_PACKET_DB] = { + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0040 + 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, +#if PACKETVER <= 20081217 + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,110, 3, 2, +#else + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,114, 3, 2, +#endif +#if PACKETVER < 2 + 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, +#elif PACKETVER < 20071106 // 78-7b Lv99 effect for later Kameshima + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#elif PACKETVER <= 20081217 // change in 0x78 and 0x7c + 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 42, 2, 6, 6, +#else + 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 44, 2, 6, 6, +#endif + //#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 2, -1, -1, -1, 0, // 0x8b changed to 2 (was 23) + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, +#if PACKETVER <= 20100622 + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, +#else + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 9, 4, 7, 0, -1, 6, // 0xaa changed to 9 (was 7) +#endif + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, + //#0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 3, 2, 27, // 0xcd change to 3 (was 6) + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, + //#0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, + //#0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, + //#0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, +#if PACKETVER < 1 + 90, 86, 24, 6, 30,102, 8, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#else // 196 comodo icon status display for later + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#endif +#if PACKETVER < 20081126 + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, +#else // 0x1a2 changed (35->37) + 3, 3, 37, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, +#endif + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, + //#0x01C0, Set 0x1d5=-1 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 3, 9, 9, 30, 6, 28, + 8, 14, 10, 35, 6, -1, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, + //#0x0200 + 26, -1, 26, 10, 18, 26, 11, 34, 14, 36, 10, 0, 0, -1, 32, 10, // 0x20c change to 0 (was 19) + 22, 0, 26, 26, 42, 6, 6, 2, 2,282,282, 10, 10, -1, -1, 66, +#if PACKETVER < 20071106 + 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 64, 5, 71, 5, +#else // 0x22c changed + 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 65, 5, 71, 5, +#endif + 12, 26, 9, 11, -1, -1, 10, 2,282, 11, 4, 36, 6, -1, 4, 2, + //#0x0240 + -1, -1, -1, -1, -1, 3, 4, 8, -1, 3, 70, 4, 8, 12, 4, 10, + 3, 32, -1, 3, 3, 5, 5, 8, 2, 3, -1, 6, 4, 6, 4, 6, + 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0280 +#if PACKETVER < 20070711 + 0, 0, 0, 6, 14, 0, 0, -1, 6, 8, 18, 0, 0, 0, 0, 0, +#else + 0, 0, 0, 6, 14, 0, 0, -1, 10, 12, 18, 0, 0, 0, 0, 0, // 0x288, 0x289 increase by 4 (kafra points) +#endif + 0, 4, 0, 70, 10, 0, 0, 0, 8, 6, 27, 80, 0, -1, 0, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, -1, -1,107, 6, -1, 7, 7, 22,191, 0, 8, 0, 0, 0, 0, + //#0x02C0 + 0, -1, 0, 0, 0, 30, 30, 0, 0, 3, 0, 65, 4, 71, 10, 0, + -1, -1, -1, 0, 29, 0, 6, -1, 10, 10, 3, 0, -1, 32, 6, 36, + 34, 33, 0, 0, 0, 0, 0, 0, -1, -1, -1, 13, 67, 59, 60, 8, + 10, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0300 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0340 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0380 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x03C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0400 + 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, 8, 0, 25, + //#0x0440 + 10, 4, -1, 0, 0, 0, 14, 0, 0, 0, 6, 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, + //#0x0480 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x04C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0500 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0540 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0580 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x05C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0600 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0640 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0680 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x06C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0700 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0740 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0780 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x07C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#if PACKETVER < 20090617 + 6, 2, -1, 4, 4, 4, 4, 8, 8,254, 6, 8, 6, 54, 30, 54, +#else // 0x7d9 changed + 6, 2, -1, 4, 4, 4, 4, 8, 8,268, 6, 8, 6, 54, 30, 54, +#endif + 0, 15, 8, 6, -1, 8, 8, 32, -1, 5, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 14, -1, -1, -1, 8, 25, 0, 0, 26, 0, + //#0x0800 +#if PACKETVER < 20091229 + -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 20, +#else // for Party booking ( PACKETVER >= 20091229 ) + -1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20, +#endif + 3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0, + 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -1, -1, 3, 2, 66, 5, 2, 12, 6, 0, 0, + //#0x0840 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -1, -1, -1, -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, + //#0x0880 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x08C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 0, 10, 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, + //#0x0900 + 0, 0, 0, 0, 0, 0, 0, 0, 5, 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, + //#0x0940 + 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, 14, 0, 0, 0, 0, 0, 0, 0, 0, + + }; + struct { + void (*func)(int, struct map_session_data *); + char *name; + } clif_parse_func[]={ + {clif_parse_WantToConnection,"wanttoconnection"}, + {clif_parse_LoadEndAck,"loadendack"}, + {clif_parse_TickSend,"ticksend"}, + {clif_parse_WalkToXY,"walktoxy"}, + {clif_parse_QuitGame,"quitgame"}, + {clif_parse_GetCharNameRequest,"getcharnamerequest"}, + {clif_parse_GlobalMessage,"globalmessage"}, + {clif_parse_MapMove,"mapmove"}, + {clif_parse_ChangeDir,"changedir"}, + {clif_parse_Emotion,"emotion"}, + {clif_parse_HowManyConnections,"howmanyconnections"}, + {clif_parse_ActionRequest,"actionrequest"}, + {clif_parse_Restart,"restart"}, + {clif_parse_WisMessage,"wis"}, + {clif_parse_Broadcast,"broadcast"}, + {clif_parse_TakeItem,"takeitem"}, + {clif_parse_DropItem,"dropitem"}, + {clif_parse_UseItem,"useitem"}, + {clif_parse_EquipItem,"equipitem"}, + {clif_parse_UnequipItem,"unequipitem"}, + {clif_parse_NpcClicked,"npcclicked"}, + {clif_parse_NpcBuySellSelected,"npcbuysellselected"}, + {clif_parse_NpcBuyListSend,"npcbuylistsend"}, + {clif_parse_NpcSellListSend,"npcselllistsend"}, + {clif_parse_CreateChatRoom,"createchatroom"}, + {clif_parse_ChatAddMember,"chataddmember"}, + {clif_parse_ChatRoomStatusChange,"chatroomstatuschange"}, + {clif_parse_ChangeChatOwner,"changechatowner"}, + {clif_parse_KickFromChat,"kickfromchat"}, + {clif_parse_ChatLeave,"chatleave"}, + {clif_parse_TradeRequest,"traderequest"}, + {clif_parse_TradeAck,"tradeack"}, + {clif_parse_TradeAddItem,"tradeadditem"}, + {clif_parse_TradeOk,"tradeok"}, + {clif_parse_TradeCancel,"tradecancel"}, + {clif_parse_TradeCommit,"tradecommit"}, + {clif_parse_StopAttack,"stopattack"}, + {clif_parse_PutItemToCart,"putitemtocart"}, + {clif_parse_GetItemFromCart,"getitemfromcart"}, + {clif_parse_RemoveOption,"removeoption"}, + {clif_parse_ChangeCart,"changecart"}, + {clif_parse_StatusUp,"statusup"}, + {clif_parse_SkillUp,"skillup"}, + {clif_parse_UseSkillToId,"useskilltoid"}, + {clif_parse_UseSkillToPos,"useskilltopos"}, + {clif_parse_UseSkillToPosMoreInfo,"useskilltoposinfo"}, + {clif_parse_UseSkillMap,"useskillmap"}, + {clif_parse_RequestMemo,"requestmemo"}, + {clif_parse_ProduceMix,"producemix"}, + {clif_parse_Cooking,"cooking"}, + {clif_parse_NpcSelectMenu,"npcselectmenu"}, + {clif_parse_NpcNextClicked,"npcnextclicked"}, + {clif_parse_NpcAmountInput,"npcamountinput"}, + {clif_parse_NpcStringInput,"npcstringinput"}, + {clif_parse_NpcCloseClicked,"npccloseclicked"}, + {clif_parse_ItemIdentify,"itemidentify"}, + {clif_parse_SelectArrow,"selectarrow"}, + {clif_parse_AutoSpell,"autospell"}, + {clif_parse_UseCard,"usecard"}, + {clif_parse_InsertCard,"insertcard"}, + {clif_parse_RepairItem,"repairitem"}, + {clif_parse_WeaponRefine,"weaponrefine"}, + {clif_parse_SolveCharName,"solvecharname"}, + {clif_parse_ResetChar,"resetchar"}, + {clif_parse_LocalBroadcast,"localbroadcast"}, + {clif_parse_MoveToKafra,"movetokafra"}, + {clif_parse_MoveFromKafra,"movefromkafra"}, + {clif_parse_MoveToKafraFromCart,"movetokafrafromcart"}, + {clif_parse_MoveFromKafraToCart,"movefromkafratocart"}, + {clif_parse_CloseKafra,"closekafra"}, + {clif_parse_CreateParty,"createparty"}, + {clif_parse_CreateParty2,"createparty2"}, + {clif_parse_PartyInvite,"partyinvite"}, + {clif_parse_PartyInvite2,"partyinvite2"}, + {clif_parse_ReplyPartyInvite,"replypartyinvite"}, + {clif_parse_ReplyPartyInvite2,"replypartyinvite2"}, + {clif_parse_LeaveParty,"leaveparty"}, + {clif_parse_RemovePartyMember,"removepartymember"}, + {clif_parse_PartyChangeOption,"partychangeoption"}, + {clif_parse_PartyMessage,"partymessage"}, + {clif_parse_PartyChangeLeader,"partychangeleader"}, + {clif_parse_CloseVending,"closevending"}, + {clif_parse_VendingListReq,"vendinglistreq"}, + {clif_parse_PurchaseReq,"purchasereq"}, + {clif_parse_PurchaseReq2,"purchasereq2"}, + {clif_parse_OpenVending,"openvending"}, + {clif_parse_CreateGuild,"createguild"}, + {clif_parse_GuildCheckMaster,"guildcheckmaster"}, + {clif_parse_GuildRequestInfo,"guildrequestinfo"}, + {clif_parse_GuildChangePositionInfo,"guildchangepositioninfo"}, + {clif_parse_GuildChangeMemberPosition,"guildchangememberposition"}, + {clif_parse_GuildRequestEmblem,"guildrequestemblem"}, + {clif_parse_GuildChangeEmblem,"guildchangeemblem"}, + {clif_parse_GuildChangeNotice,"guildchangenotice"}, + {clif_parse_GuildInvite,"guildinvite"}, + {clif_parse_GuildReplyInvite,"guildreplyinvite"}, + {clif_parse_GuildLeave,"guildleave"}, + {clif_parse_GuildExpulsion,"guildexpulsion"}, + {clif_parse_GuildMessage,"guildmessage"}, + {clif_parse_GuildRequestAlliance,"guildrequestalliance"}, + {clif_parse_GuildReplyAlliance,"guildreplyalliance"}, + {clif_parse_GuildDelAlliance,"guilddelalliance"}, + {clif_parse_GuildOpposition,"guildopposition"}, + {clif_parse_GuildBreak,"guildbreak"}, + {clif_parse_PetMenu,"petmenu"}, + {clif_parse_CatchPet,"catchpet"}, + {clif_parse_SelectEgg,"selectegg"}, + {clif_parse_SendEmotion,"sendemotion"}, + {clif_parse_ChangePetName,"changepetname"}, + + {clif_parse_GMKick,"gmkick"}, + {clif_parse_GMHide,"gmhide"}, + {clif_parse_GMReqNoChat,"gmreqnochat"}, + {clif_parse_GMReqAccountName,"gmreqaccname"}, + {clif_parse_GMKickAll,"killall"}, + {clif_parse_GMRecall,"recall"}, + {clif_parse_GMRecall,"summon"}, + {clif_parse_GM_Monster_Item,"itemmonster"}, + {clif_parse_GMShift,"remove"}, + {clif_parse_GMShift,"shift"}, + {clif_parse_GMChangeMapType,"changemaptype"}, + {clif_parse_GMRc,"rc"}, + {clif_parse_GMRecall2,"recall2"}, + {clif_parse_GMRemove2,"remove2"}, + + {clif_parse_NoviceDoriDori,"sndoridori"}, + {clif_parse_NoviceExplosionSpirits,"snexplosionspirits"}, + {clif_parse_PMIgnore,"wisexin"}, + {clif_parse_PMIgnoreList,"wisexlist"}, + {clif_parse_PMIgnoreAll,"wisall"}, + {clif_parse_FriendsListAdd,"friendslistadd"}, + {clif_parse_FriendsListRemove,"friendslistremove"}, + {clif_parse_FriendsListReply,"friendslistreply"}, + {clif_parse_Blacksmith,"blacksmith"}, + {clif_parse_Alchemist,"alchemist"}, + {clif_parse_Taekwon,"taekwon"}, + {clif_parse_RankingPk,"rankingpk"}, + {clif_parse_FeelSaveOk,"feelsaveok"}, + {clif_parse_debug,"debug"}, + {clif_parse_ChangeHomunculusName,"changehomunculusname"}, + {clif_parse_HomMoveToMaster,"hommovetomaster"}, + {clif_parse_HomMoveTo,"hommoveto"}, + {clif_parse_HomAttack,"homattack"}, + {clif_parse_HomMenu,"hommenu"}, + {clif_parse_StoragePassword,"storagepassword"}, + {clif_parse_Hotkey,"hotkey"}, + {clif_parse_AutoRevive,"autorevive"}, + {clif_parse_Check,"check"}, + {clif_parse_Adopt_request,"adoptrequest"}, + {clif_parse_Adopt_reply,"adoptreply"}, + // MAIL SYSTEM + {clif_parse_Mail_refreshinbox,"mailrefresh"}, + {clif_parse_Mail_read,"mailread"}, + {clif_parse_Mail_getattach,"mailgetattach"}, + {clif_parse_Mail_delete,"maildelete"}, + {clif_parse_Mail_return,"mailreturn"}, + {clif_parse_Mail_setattach,"mailsetattach"}, + {clif_parse_Mail_winopen,"mailwinopen"}, + {clif_parse_Mail_send,"mailsend"}, + // AUCTION SYSTEM + {clif_parse_Auction_search,"auctionsearch"}, + {clif_parse_Auction_buysell,"auctionbuysell"}, + {clif_parse_Auction_setitem,"auctionsetitem"}, + {clif_parse_Auction_cancelreg,"auctioncancelreg"}, + {clif_parse_Auction_register,"auctionregister"}, + {clif_parse_Auction_cancel,"auctioncancel"}, + {clif_parse_Auction_close,"auctionclose"}, + {clif_parse_Auction_bid,"auctionbid"}, + // Quest Log System + {clif_parse_questStateAck,"queststate"}, + {clif_parse_cashshop_buy,"cashshopbuy"}, + {clif_parse_ViewPlayerEquip,"viewplayerequip"}, + {clif_parse_EquipTick,"equiptickbox"}, + {clif_parse_BattleChat,"battlechat"}, + {clif_parse_mercenary_action,"mermenu"}, + {clif_parse_progressbar,"progressbar"}, + {clif_parse_SkillSelectMenu,"skillselectmenu"}, + {clif_parse_ItemListWindowSelected,"itemlistwindowselected"}, +#if PACKETVER >= 20091229 + {clif_parse_PartyBookingRegisterReq,"bookingregreq"}, + {clif_parse_PartyBookingSearchReq,"bookingsearchreq"}, + {clif_parse_PartyBookingUpdateReq,"bookingupdatereq"}, + {clif_parse_PartyBookingDeleteReq,"bookingdelreq"}, +#endif + {clif_parse_PVPInfo,"pvpinfo"}, + {clif_parse_LessEffect,"lesseffect"}, + // Buying Store + {clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"}, + {clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"}, + {clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"}, + {clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"}, + // Store Search + {clif_parse_SearchStoreInfo,"searchstoreinfo"}, + {clif_parse_SearchStoreInfoNextPage,"searchstoreinfonextpage"}, + {clif_parse_CloseSearchStoreInfo,"closesearchstoreinfo"}, + {clif_parse_SearchStoreInfoListItemClick,"searchstoreinfolistitemclick"}, + /* */ + { clif_parse_MoveItem , "moveitem" }, + {NULL,NULL} + }; + + // initialize packet_db[SERVER] from hardcoded packet_len_table[] values + memset(packet_db,0,sizeof(packet_db)); + for( i = 0; i < ARRAYLENGTH(packet_len_table); ++i ) + packet_len(i) = packet_len_table[i]; + + sprintf(line, "%s/packet_db.txt", db_path); + if( (fp=fopen(line,"r"))==NULL ){ + ShowFatalError("can't read %s\n", line); + exit(EXIT_FAILURE); + } + + clif_config.packet_db_ver = MAX_PACKET_VER; + packet_ver = MAX_PACKET_VER; // read into packet_db's version by default + while( fgets(line, sizeof(line), fp) ) + { + ln++; + if(line[0]=='/' && line[1]=='/') + continue; + if (sscanf(line,"%256[^:]: %256[^\r\n]",w1,w2) == 2) + { + if(strcmpi(w1,"packet_ver")==0) { + int prev_ver = packet_ver; + skip_ver = 0; + packet_ver = atoi(w2); + if ( packet_ver > MAX_PACKET_VER ) + { //Check to avoid overflowing. [Skotlex] + if( (warned&1) == 0 ) + ShowWarning("The packet_db table only has support up to version %d.\n", MAX_PACKET_VER); + warned &= 1; + skip_ver = 1; + } + else if( packet_ver < 0 ) + { + if( (warned&2) == 0 ) + ShowWarning("Negative packet versions are not supported.\n"); + warned &= 2; + skip_ver = 1; + } + else if( packet_ver == SERVER ) + { + if( (warned&4) == 0 ) + ShowWarning("Packet version %d is reserved for server use only.\n", SERVER); + warned &= 4; + skip_ver = 1; + } + + if( skip_ver ) + { + ShowWarning("Skipping packet version %d.\n", packet_ver); + packet_ver = prev_ver; + continue; + } + // copy from previous version into new version and continue + // - indicating all following packets should be read into the newer version + memcpy(&packet_db[packet_ver], &packet_db[prev_ver], sizeof(packet_db[0])); + continue; + } else if(strcmpi(w1,"packet_db_ver")==0) { + if(strcmpi(w2,"default")==0) //This is the preferred version. + clif_config.packet_db_ver = MAX_PACKET_VER; + else // to manually set the packet DB version + clif_config.packet_db_ver = cap_value(atoi(w2), 0, MAX_PACKET_VER); + + continue; + } + } + + if( skip_ver != 0 ) + continue; // Skipping current packet version + + memset(str,0,sizeof(str)); + for(j=0,p=line;j<4 && p; ++j) + { + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(str[0]==NULL) + continue; + cmd=strtol(str[0],(char **)NULL,0); + if(max_cmd < cmd) + max_cmd = cmd; + if(cmd <= 0 || cmd > MAX_PACKET_DB) + continue; + if(str[1]==NULL){ + ShowError("packet_db: packet len error\n"); + continue; + } + + packet_db[packet_ver][cmd].len = (short)atoi(str[1]); + + if(str[2]==NULL){ + packet_db[packet_ver][cmd].func = NULL; + ln++; + continue; + } + + // look up processing function by name + ARR_FIND( 0, ARRAYLENGTH(clif_parse_func), j, clif_parse_func[j].name != NULL && strcmp(str[2],clif_parse_func[j].name)==0 ); + if( j < ARRAYLENGTH(clif_parse_func) ) + packet_db[packet_ver][cmd].func = clif_parse_func[j].func; + + // set the identifying cmd for the packet_db version + if (strcmp(str[2],"wanttoconnection")==0) + clif_config.connect_cmd[packet_ver] = cmd; + + if(str[3]==NULL){ + ShowError("packet_db: packet error\n"); + exit(EXIT_FAILURE); + } + for(j=0,p2=str[3];p2;j++){ + short k; + str2[j]=p2; + p2=strchr(p2,':'); + if(p2) *p2++=0; + k = atoi(str2[j]); + // if (packet_db[packet_ver][cmd].pos[j] != k && clif_config.prefer_packet_db) // not used for now + + if( j >= MAX_PACKET_POS ) + { + ShowError("Too many positions found for packet 0x%04x (max=%d).\n", cmd, MAX_PACKET_POS); + break; + } + + packet_db[packet_ver][cmd].pos[j] = k; + } + } + fclose(fp); + if(max_cmd > MAX_PACKET_DB) + { + ShowWarning("Found packets up to 0x%X, ignored 0x%X and above.\n", max_cmd, MAX_PACKET_DB); + ShowWarning("Please increase MAX_PACKET_DB and recompile.\n"); + } + if (!clif_config.connect_cmd[clif_config.packet_db_ver]) + { //Locate the nearest version that we still support. [Skotlex] + for(j = clif_config.packet_db_ver; j >= 0 && !clif_config.connect_cmd[j]; j--); + + clif_config.packet_db_ver = j?j:MAX_PACKET_VER; + } + ShowStatus("Done reading packet database from '"CL_WHITE"%s"CL_RESET"'. Using default packet version: "CL_WHITE"%d"CL_RESET".\n", "packet_db.txt", clif_config.packet_db_ver); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int do_init_clif(void) { + const char* colors[COLOR_MAX] = { "0xFF0000" }; + int i; + /** + * Setup Color Table (saves unnecessary load of strtoul on every call) + **/ + for(i = 0; i < COLOR_MAX; i++) { + color_table[i] = strtoul(colors[i],NULL,0); + color_table[i] = (color_table[i] & 0x0000FF) << 16 | (color_table[i] & 0x00FF00) | (color_table[i] & 0xFF0000) >> 16;//RGB to BGR + } + + clif_config.packet_db_ver = -1; // the main packet version of the DB + memset(clif_config.connect_cmd, 0, sizeof(clif_config.connect_cmd)); //The default connect command will be determined after reading the packet_db [Skotlex] + + memset(packet_db,0,sizeof(packet_db)); + //Using the packet_db file is the only way to set up packets now [Skotlex] + packetdb_readdb(); + + set_defaultparse(clif_parse); + if( make_listen_bind(bind_ip,map_port) == -1 ) { + ShowFatalError("can't bind game port\n"); + exit(EXIT_FAILURE); + } + + add_timer_func_list(clif_clearunit_delayed_sub, "clif_clearunit_delayed_sub"); + add_timer_func_list(clif_delayquit, "clif_delayquit"); + + delay_clearunit_ers = ers_new(sizeof(struct block_list),"clif.c::delay_clearunit_ers",ERS_OPT_CLEAR); + + return 0; +} + +void do_final_clif(void) { + ers_destroy(delay_clearunit_ers); +} diff --git a/src/map/clif.h b/src/map/clif.h new file mode 100644 index 000000000..cd7fbdb35 --- /dev/null +++ b/src/map/clif.h @@ -0,0 +1,772 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CLIF_H_ +#define _CLIF_H_ + +#include "../common/cbasetypes.h" +//#include "../common/mmo.h" +struct item; +struct storage_data; +struct guild_storage; +//#include "map.h" +struct block_list; +struct unit_data; +struct map_session_data; +struct homun_data; +struct pet_data; +struct mob_data; +struct npc_data; +struct chat_data; +struct flooritem_data; +struct skill_unit; +struct s_vending; +struct party; +struct party_data; +struct guild; +struct battleground_data; +struct quest; +struct party_booking_ad_info; +#include <stdarg.h> + +enum +{// packet DB + MAX_PACKET_DB = 0xA00, + MAX_PACKET_VER = 30, + MAX_PACKET_POS = 20, +}; + +struct s_packet_db { + short len; + void (*func)(int, struct map_session_data *); + short pos[MAX_PACKET_POS]; +}; + +// packet_db[SERVER] is reserved for server use +#define SERVER 0 +#define packet_len(cmd) packet_db[SERVER][cmd].len +extern struct s_packet_db packet_db[MAX_PACKET_VER+1][MAX_PACKET_DB+1]; + +// local define +typedef enum send_target { + ALL_CLIENT, + ALL_SAMEMAP, + AREA, // area + AREA_WOS, // area, without self + AREA_WOC, // area, without chatrooms + AREA_WOSC, // area, without own chatroom + AREA_CHAT_WOC, // hearable area, without chatrooms + CHAT, // current chatroom + CHAT_WOS, // current chatroom, without self + PARTY, + PARTY_WOS, + PARTY_SAMEMAP, + PARTY_SAMEMAP_WOS, + PARTY_AREA, + PARTY_AREA_WOS, + GUILD, + GUILD_WOS, + GUILD_SAMEMAP, + GUILD_SAMEMAP_WOS, + GUILD_AREA, + GUILD_AREA_WOS, + GUILD_NOBG, + DUEL, + DUEL_WOS, + CHAT_MAINCHAT, // everyone on main chat + SELF, + BG, // BattleGround System + BG_WOS, + BG_SAMEMAP, + BG_SAMEMAP_WOS, + BG_AREA, + BG_AREA_WOS, +} send_target; + +typedef enum emotion_type +{ + E_GASP = 0, // /! + E_WHAT, // /? + E_HO, + E_LV, + E_SWT, + E_IC, + E_AN, + E_AG, + E_CASH, // /$ + E_DOTS, // /... + E_SCISSORS, // /gawi --- 10 + E_ROCK, // /bawi + E_PAPER, // /bo + E_KOREA, + E_LV2, + E_THX, + E_WAH, + E_SRY, + E_HEH, + E_SWT2, + E_HMM, // --- 20 + E_NO1, + E_NO, // /?? + E_OMG, + E_OH, + E_X, + E_HLP, + E_GO, + E_SOB, + E_GG, + E_KIS, // --- 30 + E_KIS2, + E_PIF, + E_OK, + E_MUTE, // red /... used for muted characters + E_INDONESIA, + E_BZZ, // /bzz, /stare + E_RICE, + E_AWSM, // /awsm, /cool + E_MEH, + E_SHY, // --- 40 + E_PAT, // /pat, /goodboy + E_MP, // /mp, /sptime + E_SLUR, + E_COM, // /com, /comeon + E_YAWN, // /yawn, /sleepy + E_GRAT, // /grat, /congrats + E_HP, // /hp, /hptime + E_PHILIPPINES, + E_MALAYSIA, + E_SINGAPORE, // --- 50 + E_BRAZIL, + E_FLASH, // /fsh + E_SPIN, // /spin + E_SIGH, + E_DUM, // /dum + E_LOUD, // /crwd + E_OTL, // /otl, /desp + E_DICE1, + E_DICE2, + E_DICE3, // --- 60 + E_DICE4, + E_DICE5, + E_DICE6, + E_INDIA, + E_LUV, // /love + E_RUSSIA, + E_VIRGIN, + E_MOBILE, + E_MAIL, + E_CHINESE, // --- 70 + E_ANTENNA1, + E_ANTENNA2, + E_ANTENNA3, + E_HUM, + E_ABS, + E_OOPS, + E_SPIT, + E_ENE, + E_PANIC, + E_WHISP, // --- 80 + E_YUT1, + E_YUT2, + E_YUT3, + E_YUT4, + E_YUT5, + E_YUT6, + E_YUT7, + // + E_MAX +} emotion_type; + +typedef enum clr_type +{ + CLR_OUTSIGHT = 0, + CLR_DEAD, + CLR_RESPAWN, + CLR_TELEPORT, + CLR_TRICKDEAD, +} clr_type; + +enum map_property +{// clif_map_property + MAPPROPERTY_NOTHING = 0, + MAPPROPERTY_FREEPVPZONE = 1, + MAPPROPERTY_EVENTPVPZONE = 2, + MAPPROPERTY_AGITZONE = 3, + MAPPROPERTY_PKSERVERZONE = 4, // message "You are in a PK area. Please beware of sudden attacks." in color 0x9B9BFF (light red) + MAPPROPERTY_PVPSERVERZONE = 5, + MAPPROPERTY_DENYSKILLZONE = 6, +}; + +enum map_type +{// clif_map_type + MAPTYPE_VILLAGE = 0, + MAPTYPE_VILLAGE_IN = 1, + MAPTYPE_FIELD = 2, + MAPTYPE_DUNGEON = 3, + MAPTYPE_ARENA = 4, + MAPTYPE_PENALTY_FREEPKZONE = 5, + MAPTYPE_NOPENALTY_FREEPKZONE = 6, + MAPTYPE_EVENT_GUILDWAR = 7, + MAPTYPE_AGIT = 8, + MAPTYPE_DUNGEON2 = 9, + MAPTYPE_DUNGEON3 = 10, + MAPTYPE_PKSERVER = 11, + MAPTYPE_PVPSERVER = 12, + MAPTYPE_DENYSKILL = 13, + MAPTYPE_TURBOTRACK = 14, + MAPTYPE_JAIL = 15, + MAPTYPE_MONSTERTRACK = 16, + MAPTYPE_PORINGBATTLE = 17, + MAPTYPE_AGIT_SIEGEV15 = 18, + MAPTYPE_BATTLEFIELD = 19, + MAPTYPE_PVP_TOURNAMENT = 20, + //Map types 21 - 24 not used. + MAPTYPE_SIEGE_LOWLEVEL = 25, + //Map types 26 - 28 remains opens for future types. + MAPTYPE_UNUSED = 29, +}; + +enum useskill_fail_cause +{// clif_skill_fail + USESKILL_FAIL_LEVEL = 0, + USESKILL_FAIL_SP_INSUFFICIENT = 1, + USESKILL_FAIL_HP_INSUFFICIENT = 2, + USESKILL_FAIL_STUFF_INSUFFICIENT = 3, + USESKILL_FAIL_SKILLINTERVAL = 4, + USESKILL_FAIL_MONEY = 5, + USESKILL_FAIL_THIS_WEAPON = 6, + USESKILL_FAIL_REDJAMSTONE = 7, + USESKILL_FAIL_BLUEJAMSTONE = 8, + USESKILL_FAIL_WEIGHTOVER = 9, + USESKILL_FAIL = 10, + USESKILL_FAIL_TOTARGET = 11, + USESKILL_FAIL_ANCILLA_NUMOVER = 12, + USESKILL_FAIL_HOLYWATER = 13, + USESKILL_FAIL_ANCILLA = 14, + USESKILL_FAIL_DUPLICATE_RANGEIN = 15, + USESKILL_FAIL_NEED_OTHER_SKILL = 16, + USESKILL_FAIL_NEED_HELPER = 17, + USESKILL_FAIL_INVALID_DIR = 18, + USESKILL_FAIL_SUMMON = 19, + USESKILL_FAIL_SUMMON_NONE = 20, + USESKILL_FAIL_IMITATION_SKILL_NONE = 21, + USESKILL_FAIL_DUPLICATE = 22, + USESKILL_FAIL_CONDITION = 23, + USESKILL_FAIL_PAINTBRUSH = 24, + USESKILL_FAIL_DRAGON = 25, + USESKILL_FAIL_POS = 26, + USESKILL_FAIL_HELPER_SP_INSUFFICIENT = 27, + USESKILL_FAIL_NEER_WALL = 28, + USESKILL_FAIL_NEED_EXP_1PERCENT = 29, + USESKILL_FAIL_CHORUS_SP_INSUFFICIENT = 30, + USESKILL_FAIL_GC_WEAPONBLOCKING = 31, + USESKILL_FAIL_GC_POISONINGWEAPON = 32, + USESKILL_FAIL_MADOGEAR = 33, + USESKILL_FAIL_NEED_EQUIPMENT_KUNAI = 34, + USESKILL_FAIL_TOTARGET_PLAYER = 35, + USESKILL_FAIL_SIZE = 36, + USESKILL_FAIL_CANONBALL = 37, + //XXX_USESKILL_FAIL_II_MADOGEAR_ACCELERATION = 38, + //XXX_USESKILL_FAIL_II_MADOGEAR_HOVERING_BOOSTER = 39, + USESKILL_FAIL_MADOGEAR_HOVERING = 40, + //XXX_USESKILL_FAIL_II_MADOGEAR_SELFDESTRUCTION_DEVICE = 41, + //XXX_USESKILL_FAIL_II_MADOGEAR_SHAPESHIFTER = 42, + USESKILL_FAIL_GUILLONTINE_POISON = 43, + //XXX_USESKILL_FAIL_II_MADOGEAR_COOLING_DEVICE = 44, + //XXX_USESKILL_FAIL_II_MADOGEAR_MAGNETICFIELD_GENERATOR = 45, + //XXX_USESKILL_FAIL_II_MADOGEAR_BARRIER_GENERATOR = 46, + //XXX_USESKILL_FAIL_II_MADOGEAR_OPTICALCAMOUFLAGE_GENERATOR = 47, + //XXX_USESKILL_FAIL_II_MADOGEAR_REPAIRKIT = 48, + //XXX_USESKILL_FAIL_II_MONKEY_SPANNER = 49, + USESKILL_FAIL_MADOGEAR_RIDE = 50, + USESKILL_FAIL_SPELLBOOK = 51, + USESKILL_FAIL_SPELLBOOK_DIFFICULT_SLEEP = 52, + USESKILL_FAIL_SPELLBOOK_PRESERVATION_POINT = 53, + USESKILL_FAIL_SPELLBOOK_READING = 54, + //XXX_USESKILL_FAIL_II_FACE_PAINTS = 55, + //XXX_USESKILL_FAIL_II_MAKEUP_BRUSH = 56, + USESKILL_FAIL_CART = 57, + //XXX_USESKILL_FAIL_II_THORNS_SEED = 58, + //XXX_USESKILL_FAIL_II_BLOOD_SUCKER_SEED = 59, + USESKILL_FAIL_NO_MORE_SPELL = 60, + //XXX_USESKILL_FAIL_II_BOMB_MUSHROOM_SPORE = 61, + //XXX_USESKILL_FAIL_II_GASOLINE_BOOMB = 62, + //XXX_USESKILL_FAIL_II_OIL_BOTTLE = 63, + //XXX_USESKILL_FAIL_II_EXPLOSION_POWDER = 64, + //XXX_USESKILL_FAIL_II_SMOKE_POWDER = 65, + //XXX_USESKILL_FAIL_II_TEAR_GAS = 66, + //XXX_USESKILL_FAIL_II_HYDROCHLORIC_ACID_BOTTLE = 67, + //XXX_USESKILL_FAIL_II_HELLS_PLANT_BOTTLE = 68, + //XXX_USESKILL_FAIL_II_MANDRAGORA_FLOWERPOT = 69, + USESKILL_FAIL_MANUAL_NOTIFY = 70, + USESKILL_FAIL_NEED_ITEM = 71, + USESKILL_FAIL_NEED_EQUIPMENT = 72, + USESKILL_FAIL_COMBOSKILL = 73, + USESKILL_FAIL_SPIRITS = 74, + USESKILL_FAIL_EXPLOSIONSPIRITS = 75, + USESKILL_FAIL_HP_TOOMANY = 76, + USESKILL_FAIL_NEED_ROYAL_GUARD_BANDING = 77, + USESKILL_FAIL_NEED_EQUIPPED_WEAPON_CLASS = 78, + USESKILL_FAIL_EL_SUMMON = 79, + USESKILL_FAIL_RELATIONGRADE = 80, + USESKILL_FAIL_STYLE_CHANGE_FIGHTER = 81, + USESKILL_FAIL_STYLE_CHANGE_GRAPPLER = 82, + USESKILL_FAIL_THERE_ARE_NPC_AROUND = 83, +}; + +int clif_setip(const char* ip); +void clif_setbindip(const char* ip); +void clif_setport(uint16 port); + +uint32 clif_getip(void); +uint32 clif_refresh_ip(void); +uint16 clif_getport(void); + +void clif_authok(struct map_session_data *sd); +void clif_authrefuse(int fd, uint8 error_code); +void clif_authfail_fd(int fd, int type); +void clif_charselectok(int id, uint8 ok); +void clif_dropflooritem(struct flooritem_data* fitem); +void clif_clearflooritem(struct flooritem_data *fitem, int fd); + +void clif_clearunit_single(int id, clr_type type, int fd); +void clif_clearunit_area(struct block_list* bl, clr_type type); +void clif_clearunit_delayed(struct block_list* bl, clr_type type, unsigned int tick); +int clif_spawn(struct block_list *bl); //area +void clif_walkok(struct map_session_data *sd); // self +void clif_move(struct unit_data *ud); //area +void clif_changemap(struct map_session_data *sd, short map, int x, int y); //self +void clif_changemapserver(struct map_session_data* sd, unsigned short map_index, int x, int y, uint32 ip, uint16 port); //self +void clif_blown(struct block_list *bl); // area +void clif_slide(struct block_list *bl, int x, int y); // area +void clif_fixpos(struct block_list *bl); // area +void clif_npcbuysell(struct map_session_data* sd, int id); //self +void clif_buylist(struct map_session_data *sd, struct npc_data *nd); //self +void clif_selllist(struct map_session_data *sd); //self +void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes); //self +void clif_scriptnext(struct map_session_data *sd,int npcid); //self +void clif_scriptclose(struct map_session_data *sd, int npcid); //self +void clif_scriptmenu(struct map_session_data* sd, int npcid, const char* mes); //self +void clif_scriptinput(struct map_session_data *sd, int npcid); //self +void clif_scriptinputstr(struct map_session_data *sd, int npcid); // self +void clif_cutin(struct map_session_data* sd, const char* image, int type); //self +void clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color); //self +void clif_additem(struct map_session_data *sd, int n, int amount, int fail); // self +void clif_dropitem(struct map_session_data *sd,int n,int amount); //self +void clif_delitem(struct map_session_data *sd,int n,int amount, short reason); //self +void clif_updatestatus(struct map_session_data *sd,int type); //self +void clif_changestatus(struct map_session_data* sd,int type,int val); //area +int clif_damage(struct block_list* src, struct block_list* dst, unsigned int tick, int sdelay, int ddelay, int damage, int div, int type, int damage2); // area +void clif_takeitem(struct block_list* src, struct block_list* dst); +void clif_sitting(struct block_list* bl); +void clif_standing(struct block_list* bl); +void clif_changelook(struct block_list *bl,int type,int val); // area +void clif_changetraplook(struct block_list *bl,int val); // area +void clif_refreshlook(struct block_list *bl,int id,int type,int val,enum send_target target); //area specified in 'target' +void clif_arrowequip(struct map_session_data *sd,int val); //self +void clif_arrow_fail(struct map_session_data *sd,int type); //self +void clif_arrow_create_list(struct map_session_data *sd); //self +void clif_statusupack(struct map_session_data *sd,int type,int ok,int val); // self +void clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok); // self +void clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok); // self +void clif_misceffect(struct block_list* bl,int type); // area +void clif_changeoption(struct block_list* bl); // area +void clif_changeoption2(struct block_list* bl); // area +void clif_useitemack(struct map_session_data *sd,int index,int amount,bool ok); // self +void clif_GlobalMessage(struct block_list* bl, const char* message); +void clif_createchat(struct map_session_data* sd, int flag); // self +void clif_dispchat(struct chat_data* cd, int fd); // area or fd +void clif_joinchatfail(struct map_session_data *sd,int flag); // self +void clif_joinchatok(struct map_session_data *sd,struct chat_data* cd); // self +void clif_addchat(struct chat_data* cd,struct map_session_data *sd); // chat +void clif_changechatowner(struct chat_data* cd, struct map_session_data* sd); // chat +void clif_clearchat(struct chat_data *cd,int fd); // area or fd +void clif_leavechat(struct chat_data* cd, struct map_session_data* sd, bool flag); // chat +void clif_changechatstatus(struct chat_data* cd); // chat +void clif_refresh(struct map_session_data *sd); // self + +void clif_fame_blacksmith(struct map_session_data *sd, int points); +void clif_fame_alchemist(struct map_session_data *sd, int points); +void clif_fame_taekwon(struct map_session_data *sd, int points); + +void clif_emotion(struct block_list *bl,int type); +void clif_talkiebox(struct block_list* bl, const char* talkie); +void clif_wedding_effect(struct block_list *bl); +void clif_divorced(struct map_session_data* sd, const char* name); +void clif_callpartner(struct map_session_data *sd); +void clif_playBGM(struct map_session_data* sd, const char* name); +void clif_soundeffect(struct map_session_data* sd, struct block_list* bl, const char* name, int type); +void clif_soundeffectall(struct block_list* bl, const char* name, int type, enum send_target coverage); +void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, int target_id, unsigned int tick); +void clif_parse_LoadEndAck(int fd,struct map_session_data *sd); +void clif_hotkeys_send(struct map_session_data *sd); + +// trade +void clif_traderequest(struct map_session_data* sd, const char* name); +void clif_tradestart(struct map_session_data* sd, uint8 type); +void clif_tradeadditem(struct map_session_data* sd, struct map_session_data* tsd, int index, int amount); +void clif_tradeitemok(struct map_session_data* sd, int index, int fail); +void clif_tradedeal_lock(struct map_session_data* sd, int fail); +void clif_tradecancelled(struct map_session_data* sd); +void clif_tradecompleted(struct map_session_data* sd, int fail); +void clif_tradeundo(struct map_session_data* sd); + +// storage +void clif_storagelist(struct map_session_data* sd, struct item* items, int items_length); +void clif_updatestorageamount(struct map_session_data* sd, int amount, int max_amount); +void clif_storageitemadded(struct map_session_data* sd, struct item* i, int index, int amount); +void clif_storageitemremoved(struct map_session_data* sd, int index, int amount); +void clif_storageclose(struct map_session_data* sd); + +int clif_insight(struct block_list *bl,va_list ap); // map_forallinmovearea callback +int clif_outsight(struct block_list *bl,va_list ap); // map_forallinmovearea callback + +void clif_class_change(struct block_list *bl,int class_,int type); +#define clif_mob_class_change(md, class_) clif_class_change(&md->bl, class_, 1) + +void clif_skillinfoblock(struct map_session_data *sd); +void clif_skillup(struct map_session_data *sd,uint16 skill_id); +void clif_skillinfo(struct map_session_data *sd,int skill, int inf); +void clif_addskill(struct map_session_data *sd, int id); +void clif_deleteskill(struct map_session_data *sd, int id); + +void clif_skillcasting(struct block_list* bl, int src_id, int dst_id, int dst_x, int dst_y, uint16 skill_id, int property, int casttime); +void clif_skillcastcancel(struct block_list* bl); +void clif_skill_fail(struct map_session_data *sd,uint16 skill_id,enum useskill_fail_cause cause,int btype); +void clif_skill_cooldown(struct map_session_data *sd, uint16 skill_id, unsigned int tick); +int clif_skill_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type); +//int clif_skill_damage2(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type); +int clif_skill_nodamage(struct block_list *src,struct block_list *dst,uint16 skill_id,int heal,int fail); +void clif_skill_poseffect(struct block_list *src,uint16 skill_id,int val,int x,int y,int tick); +void clif_skill_estimation(struct map_session_data *sd,struct block_list *dst); +void clif_skill_warppoint(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv, unsigned short map1, unsigned short map2, unsigned short map3, unsigned short map4); +void clif_skill_memomessage(struct map_session_data* sd, int type); +void clif_skill_teleportmessage(struct map_session_data *sd, int type); +void clif_skill_produce_mix_list(struct map_session_data *sd, int skill_id, int trigger); +void clif_cooking_list(struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type); + +void clif_produceeffect(struct map_session_data* sd,int flag,int nameid); + +void clif_skill_setunit(struct skill_unit *unit); +void clif_skill_delunit(struct skill_unit *unit); + +void clif_skillunit_update(struct block_list* bl); + +void clif_autospell(struct map_session_data *sd,uint16 skill_lv); +void clif_devotion(struct block_list *src, struct map_session_data *tsd); +void clif_spiritball(struct block_list *bl); +void clif_combo_delay(struct block_list *bl,int wait); +void clif_bladestop(struct block_list *src, int dst_id, int active); +void clif_changemapcell(int fd, int16 m, int x, int y, int type, enum send_target target); + +#define clif_status_load(bl, type, flag) clif_status_change((bl), (type), (flag), 0, 0, 0, 0) +void clif_status_change(struct block_list *bl,int type,int flag,int tick,int val1, int val2, int val3); + +void clif_wis_message(int fd, const char* nick, const char* mes, int mes_len); +void clif_wis_end(int fd, int flag); + +void clif_solved_charname(int fd, int charid, const char* name); +void clif_charnameack(int fd, struct block_list *bl); +void clif_charnameupdate(struct map_session_data *ssd); + +void clif_use_card(struct map_session_data *sd,int idx); +void clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag); + +void clif_inventorylist(struct map_session_data *sd); +void clif_equiplist(struct map_session_data *sd); + +void clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail); +void clif_cart_delitem(struct map_session_data *sd,int n,int amount); +void clif_cartlist(struct map_session_data *sd); +void clif_clearcart(int fd); + +void clif_item_identify_list(struct map_session_data *sd); +void clif_item_identified(struct map_session_data *sd,int idx,int flag); +void clif_item_repair_list(struct map_session_data *sd, struct map_session_data *dstsd, int lv); +void clif_item_repaireffect(struct map_session_data *sd, int idx, int flag); +void clif_item_damaged(struct map_session_data* sd, unsigned short position); +void clif_item_refine_list(struct map_session_data *sd); + +void clif_item_skill(struct map_session_data *sd,uint16 skill_id,uint16 skill_lv); + +void clif_mvp_effect(struct map_session_data *sd); +void clif_mvp_item(struct map_session_data *sd,int nameid); +void clif_mvp_exp(struct map_session_data *sd, unsigned int exp); +void clif_mvp_noitem(struct map_session_data* sd); +void clif_changed_dir(struct block_list *bl, enum send_target target); + +// vending +void clif_openvendingreq(struct map_session_data* sd, int num); +void clif_showvendingboard(struct block_list* bl, const char* message, int fd); +void clif_closevendingboard(struct block_list* bl, int fd); +void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* vending); +void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail); +void clif_openvending(struct map_session_data* sd, int id, struct s_vending* vending); +void clif_vendingreport(struct map_session_data* sd, int index, int amount); + +void clif_movetoattack(struct map_session_data *sd,struct block_list *bl); + +// party +void clif_party_created(struct map_session_data *sd,int result); +void clif_party_member_info(struct party_data *p, struct map_session_data *sd); +void clif_party_info(struct party_data* p, struct map_session_data *sd); +void clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd); +void clif_party_inviteack(struct map_session_data* sd, const char* nick, int result); +void clif_party_option(struct party_data *p,struct map_session_data *sd,int flag); +void clif_party_withdraw(struct party_data* p, struct map_session_data* sd, int account_id, const char* name, int flag); +void clif_party_message(struct party_data* p, int account_id, const char* mes, int len); +void clif_party_xy(struct map_session_data *sd); +void clif_party_xy_single(int fd, struct map_session_data *sd); +void clif_party_hp(struct map_session_data *sd); +void clif_hpmeter_single(int fd, int id, unsigned int hp, unsigned int maxhp); + +// guild +void clif_guild_created(struct map_session_data *sd,int flag); +void clif_guild_belonginfo(struct map_session_data *sd, struct guild *g); +void clif_guild_masterormember(struct map_session_data *sd); +void clif_guild_basicinfo(struct map_session_data *sd); +void clif_guild_allianceinfo(struct map_session_data *sd); +void clif_guild_memberlist(struct map_session_data *sd); +void clif_guild_skillinfo(struct map_session_data* sd); +void clif_guild_send_onlineinfo(struct map_session_data *sd); //[LuzZza] +void clif_guild_memberlogin_notice(struct guild *g,int idx,int flag); +void clif_guild_invite(struct map_session_data *sd,struct guild *g); +void clif_guild_inviteack(struct map_session_data *sd,int flag); +void clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes); +void clif_guild_expulsion(struct map_session_data* sd, const char* name, const char* mes, int account_id); +void clif_guild_positionchanged(struct guild *g,int idx); +void clif_guild_memberpositionchanged(struct guild *g,int idx); +void clif_guild_emblem(struct map_session_data *sd,struct guild *g); +void clif_guild_emblem_area(struct block_list* bl); +void clif_guild_notice(struct map_session_data* sd, struct guild* g); +void clif_guild_message(struct guild *g,int account_id,const char *mes,int len); +int clif_guild_skillup(struct map_session_data *sd,uint16 skill_id,int lv); +void clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name); +void clif_guild_allianceack(struct map_session_data *sd,int flag); +void clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +void clif_guild_oppositionack(struct map_session_data *sd,int flag); +void clif_guild_broken(struct map_session_data *sd,int flag); +void clif_guild_xy(struct map_session_data *sd); +void clif_guild_xy_single(int fd, struct map_session_data *sd); +void clif_guild_xy_remove(struct map_session_data *sd); + +// Battleground +void clif_bg_hp(struct map_session_data *sd); +void clif_bg_xy(struct map_session_data *sd); +void clif_bg_xy_remove(struct map_session_data *sd); +void clif_bg_message(struct battleground_data *bg, int src_id, const char *name, const char *mes, int len); +void clif_bg_updatescore(int16 m); +void clif_bg_updatescore_single(struct map_session_data *sd); +void clif_sendbgemblem_area(struct map_session_data *sd); +void clif_sendbgemblem_single(int fd, struct map_session_data *sd); + +// Instancing +int clif_instance(int instance_id, int type, int flag); +void clif_instance_join(int fd, int instance_id); +void clif_instance_leave(int fd); + +// Custom Fonts +void clif_font(struct map_session_data *sd); + +// atcommand +void clif_displaymessage(const int fd, const char* mes); +void clif_disp_onlyself(struct map_session_data *sd, const char *mes, int len); +void clif_disp_message(struct block_list* src, const char* mes, int len, enum send_target target); +void clif_broadcast(struct block_list* bl, const char* mes, int len, int type, enum send_target target); +void clif_MainChatMessage(const char* message); +void clif_broadcast2(struct block_list* bl, const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, enum send_target target); +void clif_heal(int fd,int type,int val); +void clif_resurrection(struct block_list *bl,int type); +void clif_map_property(struct map_session_data* sd, enum map_property property); +void clif_pvpset(struct map_session_data *sd, int pvprank, int pvpnum,int type); +void clif_map_property_mapall(int map, enum map_property property); +void clif_refine(int fd, int fail, int index, int val); +void clif_upgrademessage(int fd, int result, int item_id); + +//petsystem +void clif_catch_process(struct map_session_data *sd); +void clif_pet_roulette(struct map_session_data *sd,int data); +void clif_sendegg(struct map_session_data *sd); +void clif_send_petstatus(struct map_session_data *sd); +void clif_send_petdata(struct map_session_data* sd, struct pet_data* pd, int type, int param); +#define clif_pet_equip(sd, pd) clif_send_petdata(sd, pd, 3, (pd)->vd.head_bottom) +#define clif_pet_equip_area(pd) clif_send_petdata(NULL, pd, 3, (pd)->vd.head_bottom) +#define clif_pet_performance(pd, param) clif_send_petdata(NULL, pd, 4, param) +void clif_pet_emotion(struct pet_data *pd,int param); +void clif_pet_food(struct map_session_data *sd,int foodid,int fail); + +//friends list +int clif_friendslist_toggle_sub(struct map_session_data *sd,va_list ap); +void clif_friendslist_send(struct map_session_data *sd); +void clif_friendslist_reqack(struct map_session_data *sd, struct map_session_data *f_sd, int type); + +void clif_weather(int16 m); // [Valaris] +void clif_specialeffect(struct block_list* bl, int type, enum send_target target); // special effects [Valaris] +void clif_specialeffect_single(struct block_list* bl, int type, int fd); +void clif_messagecolor(struct block_list* bl, unsigned long color, const char* msg); // Mob/Npc color talk [SnakeDrak] +void clif_message(struct block_list* bl, const char* msg); +void clif_specialeffect_value(struct block_list* bl, int effect_id, int num, send_target target); + +void clif_GM_kickack(struct map_session_data *sd, int id); +void clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd); +void clif_manner_message(struct map_session_data* sd, uint32 type); +void clif_GM_silence(struct map_session_data* sd, struct map_session_data* tsd, uint8 type); + +void clif_disp_overhead(struct map_session_data *sd, const char* mes); + +void clif_get_weapon_view(struct map_session_data* sd, unsigned short *rhand, unsigned short *lhand); + +void clif_party_xy_remove(struct map_session_data *sd); //Fix for minimap [Kevin] +void clif_gospel_info(struct map_session_data *sd, int type); +void clif_feel_req(int fd, struct map_session_data *sd, uint16 skill_lv); +void clif_starskill(struct map_session_data* sd, const char* mapname, int monster_id, unsigned char star, unsigned char result); +void clif_feel_info(struct map_session_data* sd, unsigned char feel_level, unsigned char type); +void clif_hate_info(struct map_session_data *sd, unsigned char hate_level,int class_, unsigned char type); +void clif_mission_info(struct map_session_data *sd, int mob_id, unsigned char progress); +void clif_feel_hate_reset(struct map_session_data *sd); + +// [blackhole89] +void clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag); +int clif_homskillinfoblock(struct map_session_data *sd); +void clif_homskillup(struct map_session_data *sd, uint16 skill_id); //[orn] +int clif_hom_food(struct map_session_data *sd,int foodid,int fail); //[orn] +void clif_send_homdata(struct map_session_data *sd, int state, int param); //[orn] + +void clif_equiptickack(struct map_session_data* sd, int flag); +void clif_viewequip_ack(struct map_session_data* sd, struct map_session_data* tsd); +void clif_viewequip_fail(struct map_session_data* sd); +void clif_equipcheckbox(struct map_session_data* sd); + +void clif_msg(struct map_session_data* sd, unsigned short id); +void clif_msg_value(struct map_session_data* sd, unsigned short id, int value); +void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id); + +//quest system [Kevin] [Inkfish] +void clif_quest_send_list(struct map_session_data * sd); +void clif_quest_send_mission(struct map_session_data * sd); +void clif_quest_add(struct map_session_data * sd, struct quest * qd, int index); +void clif_quest_delete(struct map_session_data * sd, int quest_id); +void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active); +void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int index); +void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color); +void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest); + +int clif_send(const uint8* buf, int len, struct block_list* bl, enum send_target type); +int do_init_clif(void); +void do_final_clif(void); + +// MAIL SYSTEM +void clif_Mail_window(int fd, int flag); +void clif_Mail_read(struct map_session_data *sd, int mail_id); +void clif_Mail_delete(int fd, int mail_id, short fail); +void clif_Mail_return(int fd, int mail_id, short fail); +void clif_Mail_send(int fd, bool fail); +void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title); +void clif_Mail_refreshinbox(struct map_session_data *sd); +void clif_Mail_getattachment(int fd, uint8 flag); +// AUCTION SYSTEM +void clif_Auction_openwindow(struct map_session_data *sd); +void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf); +void clif_Auction_message(int fd, unsigned char flag); +void clif_Auction_close(int fd, unsigned char flag); +void clif_parse_Auction_cancelreg(int fd, struct map_session_data *sd); + +void clif_bossmapinfo(int fd, struct mob_data *md, short flag); +void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd); + +// ADOPTION +void clif_Adopt_reply(struct map_session_data *sd, int type); + +// MERCENARIES +void clif_mercenary_info(struct map_session_data *sd); +void clif_mercenary_skillblock(struct map_session_data *sd); +void clif_mercenary_message(struct map_session_data* sd, int message); +void clif_mercenary_updatestatus(struct map_session_data *sd, int type); + +// RENTAL SYSTEM +void clif_rental_time(int fd, int nameid, int seconds); +void clif_rental_expired(int fd, int index, int nameid); + +// BOOK READING +void clif_readbook(int fd, int book_id, int page); + +// Show Picker +void clif_party_show_picker(struct map_session_data * sd, struct item * item_data); + +// Progress Bar [Inkfish] +void clif_progressbar(struct map_session_data * sd, unsigned long color, unsigned int second); +void clif_progressbar_abort(struct map_session_data * sd); + +void clif_PartyBookingRegisterAck(struct map_session_data *sd, int flag); +void clif_PartyBookingDeleteAck(struct map_session_data* sd, int flag); +void clif_PartyBookingSearchAck(int fd, struct party_booking_ad_info** results, int count, bool more_result); +void clif_PartyBookingUpdateNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad); +void clif_PartyBookingDeleteNotify(struct map_session_data* sd, int index); +void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad); + +void clif_showdigit(struct map_session_data* sd, unsigned char type, int value); + +/// Buying Store System +void clif_buyingstore_open(struct map_session_data* sd); +void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight); +void clif_buyingstore_myitemlist(struct map_session_data* sd); +void clif_buyingstore_entry(struct map_session_data* sd); +void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_disappear_entry(struct map_session_data* sd); +void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result); +void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount); +void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price); +void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid); + +/// Search Store System +void clif_search_store_info_ack(struct map_session_data* sd); +void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason); +void clif_open_search_store_info(struct map_session_data* sd); +void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y); +/** + * 3CeAM + **/ +void clif_msgtable(int fd, int line); +void clif_msgtable_num(int fd, int line, int num); + +int clif_elementalconverter_list(struct map_session_data *sd); + +void clif_millenniumshield(struct map_session_data *sd, short shields ); + +int clif_spellbook_list(struct map_session_data *sd); + +int clif_magicdecoy_list(struct map_session_data *sd, uint16 skill_lv, short x, short y); + +int clif_poison_list(struct map_session_data *sd, uint16 skill_lv); + +int clif_autoshadowspell_list(struct map_session_data *sd); + +int clif_status_load_notick(struct block_list *bl,int type,int flag,int val1, int val2, int val3); +int clif_status_load_single(int fd, int id,int type,int flag,int val1, int val2, int val3); + + +int clif_skill_itemlistwindow( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv ); +void clif_elemental_info(struct map_session_data *sd); +void clif_elemental_updatestatus(struct map_session_data *sd, int type); + +void clif_talisman(struct map_session_data *sd, short type); + +void clif_snap( struct block_list *bl, short x, short y ); +void clif_monster_hp_bar( struct mob_data* md, int fd ); + +/** + * Color Table + **/ +enum clif_colors { + COLOR_RED, + + COLOR_MAX +}; +unsigned long color_table[COLOR_MAX]; +int clif_colormes(struct map_session_data * sd, enum clif_colors color, const char* msg); + +#define clif_menuskill_clear(sd) (sd)->menuskill_id = (sd)->menuskill_val = (sd)->menuskill_val2 = 0; + +#endif /* _CLIF_H_ */ diff --git a/src/map/date.c b/src/map/date.c new file mode 100644 index 000000000..9f2bc4bee --- /dev/null +++ b/src/map/date.c @@ -0,0 +1,71 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "date.h" +#include <time.h> + +int date_get_year(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_year+1900; +} +int date_get_month(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_mon+1; +} +int date_get_day(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_mday; +} +int date_get_hour(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_hour; +} + +int date_get_min(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_min; +} + +int date_get_sec(void) +{ + time_t t; + struct tm * lt; + t = time(NULL); + lt = localtime(&t); + return lt->tm_sec; +} + +int is_day_of_sun(void) +{ + return date_get_day()%2 == 0; +} + +int is_day_of_moon(void) +{ + return date_get_day()%2 == 1; +} + +int is_day_of_star(void) +{ + return date_get_day()%5 == 0; +} diff --git a/src/map/date.h b/src/map/date.h new file mode 100644 index 000000000..cc19d88d1 --- /dev/null +++ b/src/map/date.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _DATE_H_ +#define _DATE_H_ + +int date_get_year(void); +int date_get_month(void); +int date_get_day(void); +int date_get_hour(void); +int date_get_min(void); +int date_get_sec(void); + +int is_day_of_sun(void); +int is_day_of_moon(void); +int is_day_of_star(void); + +#endif /* _DATE_H_ */ diff --git a/src/map/duel.c b/src/map/duel.c new file mode 100644 index 000000000..c13d004b0 --- /dev/null +++ b/src/map/duel.c @@ -0,0 +1,182 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" + +#include "atcommand.h" // msg_txt +#include "clif.h" +#include "duel.h" +#include "pc.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +struct duel duel_list[MAX_DUEL]; +int duel_count = 0; + +/*========================================== + * Duel organizing functions [LuzZza] + *------------------------------------------*/ +void duel_savetime(struct map_session_data* sd) +{ + time_t timer; + struct tm *t; + + time(&timer); + t = localtime(&timer); + + pc_setglobalreg(sd, "PC_LAST_DUEL_TIME", t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min); +} + +int duel_checktime(struct map_session_data* sd) +{ + int diff; + time_t timer; + struct tm *t; + + time(&timer); + t = localtime(&timer); + + diff = t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min - pc_readglobalreg(sd, "PC_LAST_DUEL_TIME"); + + return !(diff >= 0 && diff < battle_config.duel_time_interval); +} +static int duel_showinfo_sub(struct map_session_data* sd, va_list va) +{ + struct map_session_data *ssd = va_arg(va, struct map_session_data*); + int *p = va_arg(va, int*); + char output[256]; + + if (sd->duel_group != ssd->duel_group) return 0; + + sprintf(output, " %d. %s", ++(*p), sd->status.name); + clif_disp_onlyself(ssd, output, strlen(output)); + return 1; +} + +void duel_showinfo(const unsigned int did, struct map_session_data* sd) +{ + int p=0; + char output[256]; + + if(duel_list[did].max_players_limit > 0) + sprintf(output, msg_txt(370), //" -- Duels: %d/%d, Members: %d/%d, Max players: %d --" + did, duel_count, + duel_list[did].members_count, + duel_list[did].members_count + duel_list[did].invites_count, + duel_list[did].max_players_limit); + else + sprintf(output, msg_txt(371), //" -- Duels: %d/%d, Members: %d/%d --" + did, duel_count, + duel_list[did].members_count, + duel_list[did].members_count + duel_list[did].invites_count); + + clif_disp_onlyself(sd, output, strlen(output)); + map_foreachpc(duel_showinfo_sub, sd, &p); +} + +int duel_create(struct map_session_data* sd, const unsigned int maxpl) +{ + int i=1; + char output[256]; + + while(duel_list[i].members_count > 0 && i < MAX_DUEL) i++; + if(i == MAX_DUEL) return 0; + + duel_count++; + sd->duel_group = i; + duel_list[i].members_count++; + duel_list[i].invites_count = 0; + duel_list[i].max_players_limit = maxpl; + + strcpy(output, msg_txt(372)); // " -- Duel has been created (@invite/@leave) --" + clif_disp_onlyself(sd, output, strlen(output)); + + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + //clif_misceffect2(&sd->bl, 159); + return i; +} + +void duel_invite(const unsigned int did, struct map_session_data* sd, struct map_session_data* target_sd) +{ + char output[256]; + + // " -- Player %s invites %s to duel --" + sprintf(output, msg_txt(373), sd->status.name, target_sd->status.name); + clif_disp_message(&sd->bl, output, strlen(output), DUEL_WOS); + + target_sd->duel_invite = did; + duel_list[did].invites_count++; + + // "Blue -- Player %s invites you to PVP duel (@accept/@reject) --" + sprintf(output, msg_txt(374), sd->status.name); + clif_broadcast((struct block_list *)target_sd, output, strlen(output)+1, 0x10, SELF); +} + +static int duel_leave_sub(struct map_session_data* sd, va_list va) +{ + int did = va_arg(va, int); + if (sd->duel_invite == did) + sd->duel_invite = 0; + return 0; +} + +void duel_leave(const unsigned int did, struct map_session_data* sd) +{ + char output[256]; + + // " <- Player %s has left duel --" + sprintf(output, msg_txt(375), sd->status.name); + clif_disp_message(&sd->bl, output, strlen(output), DUEL_WOS); + + duel_list[did].members_count--; + + if(duel_list[did].members_count == 0) { + map_foreachpc(duel_leave_sub, did); + duel_count--; + } + + sd->duel_group = 0; + duel_savetime(sd); + clif_map_property(sd, MAPPROPERTY_NOTHING); +} + +void duel_accept(const unsigned int did, struct map_session_data* sd) +{ + char output[256]; + + duel_list[did].members_count++; + sd->duel_group = sd->duel_invite; + duel_list[did].invites_count--; + sd->duel_invite = 0; + + // " -> Player %s has accepted duel --" + sprintf(output, msg_txt(376), sd->status.name); + clif_disp_message(&sd->bl, output, strlen(output), DUEL_WOS); + + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + //clif_misceffect2(&sd->bl, 159); +} + +void duel_reject(const unsigned int did, struct map_session_data* sd) +{ + char output[256]; + + // " -- Player %s has rejected duel --" + sprintf(output, msg_txt(377), sd->status.name); + clif_disp_message(&sd->bl, output, strlen(output), DUEL_WOS); + + duel_list[did].invites_count--; + sd->duel_invite = 0; +} + +void do_final_duel(void) +{ +} + +void do_init_duel(void) +{ + memset(&duel_list[0], 0, sizeof(duel_list)); +} diff --git a/src/map/duel.h b/src/map/duel.h new file mode 100644 index 000000000..04d8e4e84 --- /dev/null +++ b/src/map/duel.h @@ -0,0 +1,29 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _DUEL_H_ +#define _DUEL_H_ + +struct duel { + int members_count; + int invites_count; + int max_players_limit; +}; + +#define MAX_DUEL 1024 +extern struct duel duel_list[MAX_DUEL]; +extern int duel_count; + +//Duel functions // [LuzZza] +int duel_create(struct map_session_data* sd, const unsigned int maxpl); +void duel_invite(const unsigned int did, struct map_session_data* sd, struct map_session_data* target_sd); +void duel_accept(const unsigned int did, struct map_session_data* sd); +void duel_reject(const unsigned int did, struct map_session_data* sd); +void duel_leave(const unsigned int did, struct map_session_data* sd); +void duel_showinfo(const unsigned int did, struct map_session_data* sd); +int duel_checktime(struct map_session_data* sd); + +void do_init_duel(void); +void do_final_duel(void); + +#endif /* _DUEL_H_ */ diff --git a/src/map/elemental.c b/src/map/elemental.c new file mode 100644 index 000000000..90b90c1e3 --- /dev/null +++ b/src/map/elemental.c @@ -0,0 +1,943 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "../common/random.h" + +#include "log.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" +#include "unit.h" +#include "elemental.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +struct s_elemental_db elemental_db[MAX_ELEMENTAL_CLASS]; // Elemental Database + +int elemental_search_index(int class_) { + int i; + ARR_FIND(0, MAX_ELEMENTAL_CLASS, i, elemental_db[i].class_ == class_); + return (i == MAX_ELEMENTAL_CLASS)?-1:i; +} + +bool elemental_class(int class_) { + return (bool)(elemental_search_index(class_) > -1); +} + +struct view_data * elemental_get_viewdata(int class_) { + int i = elemental_search_index(class_); + if( i < 0 ) + return 0; + + return &elemental_db[i].vd; +} + +int elemental_create(struct map_session_data *sd, int class_, unsigned int lifetime) { + struct s_elemental ele; + struct s_elemental_db *db; + int i; + + nullpo_retr(1,sd); + + if( (i = elemental_search_index(class_)) < 0 ) + return 0; + + db = &elemental_db[i]; + memset(&ele,0,sizeof(struct s_elemental)); + + ele.char_id = sd->status.char_id; + ele.class_ = class_; + ele.mode = EL_MODE_PASSIVE; // Initial mode + i = db->status.size+1; // summon level + + //[(Caster痴 Max HP/ 3 ) + (Caster痴 INT x 10 )+ (Caster痴 Job Level x 20 )] x [(Elemental Summon Level + 2) / 3] + ele.hp = ele.max_hp = (sd->battle_status.max_hp/3 + sd->battle_status.int_*10 + sd->status.job_level) * ((i + 2) / 3); + //Caster痴 Max SP /4 + ele.sp = ele.max_sp = sd->battle_status.max_sp/4; + //Caster痴 [ Max SP / (18 / Elemental Summon Skill Level) 1- 100 ] + ele.atk = (sd->battle_status.max_sp / (18 / i) * 1 - 100); + //Caster痴 [ Max SP / (18 / Elemental Summon Skill Level) ] + ele.atk2 = sd->battle_status.max_sp / 18; + //Caster痴 HIT + (Caster痴 Base Level ) + ele.hit = sd->battle_status.hit + sd->status.base_level; + //[Elemental Summon Skill Level x (Caster痴 INT / 2 + Caster痴 DEX / 4)] + ele.matk = i * (sd->battle_status.int_ / 2 + sd->battle_status.dex / 4); + //150 + [Caster痴 DEX / 10] + [Elemental Summon Skill Level x 3 ] + ele.amotion = 150 + sd->battle_status.dex / 10 + i * 3; + //Caster痴 DEF + (Caster痴 Base Level / (5 Elemental Summon Skill Level) + ele.def = sd->battle_status.def + sd->status.base_level / (5-i); + //Caster痴 MDEF + (Caster痴 INT / (5 - Elemental Summon Skill Level) + ele.mdef = sd->battle_status.mdef + sd->battle_status.int_ / (5-i); + //Caster痴 FLEE + (Caster痴 Base Level / (5 Elemental Summon Skill Level) + ele.flee = sd->status.base_level / (5-i); + //Caster痴 HIT + (Caster痴 Base Level ) + ele.hit = sd->battle_status.hit + sd->status.base_level; + + //per individual bonuses + switch(db->class_){ + case 2114: case 2115: + case 2116: //ATK + (Summon Agni Skill Level x 20) / HIT + (Summon Agni Skill Level x 10) + ele.atk += i * 20; + ele.atk2 += i * 20; + ele.hit += i * 10; + break; + case 2117: case 2118: + case 2119: //MDEF + (Summon Aqua Skill Level x 10) / MATK + (Summon Aqua Skill Level x 20) + ele.mdef += i * 10; + ele.matk += i * 20; + break; + case 2120: case 2121: + case 2122: //FLEE + (Summon Ventus Skill Level x 20) / MATK + (Summon Ventus Skill Level x 10) + ele.flee += i * 20; + ele.matk += i * 10; + break; + case 2123: case 2124: + case 2125: //DEF + (Summon Tera Skill Level x 25) / ATK + (Summon Tera Skill Level x 5) + ele.def += i * 25; + ele.atk += i * 5; + ele.atk2 += i * 5; + break; + } + + if( (i=pc_checkskill(sd,SO_EL_SYMPATHY)) > 0 ){ + ele.hp = ele.max_hp = ele.max_hp * 5 * i / 100; + ele.sp = ele.max_sp = ele.max_sp * 5 * i / 100; + ele.atk += 25 * i; + ele.atk2 += 25 * i; + ele.matk += 25 * i; + } + + ele.life_time = lifetime; + + // Request Char Server to create this elemental + intif_elemental_create(&ele); + + return 1; +} + +int elemental_get_lifetime(struct elemental_data *ed) { + const struct TimerData * td; + if( ed == NULL || ed->summon_timer == INVALID_TIMER ) + return 0; + + td = get_timer(ed->summon_timer); + return (td != NULL) ? DIFF_TICK(td->tick, gettick()) : 0; +} + +int elemental_save(struct elemental_data *ed) { + ed->elemental.mode = ed->battle_status.mode; + ed->elemental.hp = ed->battle_status.hp; + ed->elemental.sp = ed->battle_status.sp; + ed->elemental.max_hp = ed->battle_status.max_hp; + ed->elemental.max_sp = ed->battle_status.max_sp; + ed->elemental.atk = ed->battle_status.rhw.atk; + ed->elemental.atk2 = ed->battle_status.rhw.atk2; + ed->elemental.matk = ed->battle_status.matk_min; + ed->elemental.def = ed->battle_status.def; + ed->elemental.mdef = ed->battle_status.mdef; + ed->elemental.flee = ed->battle_status.flee; + ed->elemental.hit = ed->battle_status.hit; + ed->elemental.life_time = elemental_get_lifetime(ed); + intif_elemental_save(&ed->elemental); + return 1; +} + +static int elemental_summon_end(int tid, unsigned int tick, int id, intptr_t data) { + struct map_session_data *sd; + struct elemental_data *ed; + + if( (sd = map_id2sd(id)) == NULL ) + return 1; + if( (ed = sd->ed) == NULL ) + return 1; + + if( ed->summon_timer != tid ) { + ShowError("elemental_summon_end %d != %d.\n", ed->summon_timer, tid); + return 0; + } + + ed->summon_timer = INVALID_TIMER; + elemental_delete(ed, 0); // Elemental's summon time is over. + + return 0; +} + +void elemental_summon_stop(struct elemental_data *ed) { + nullpo_retv(ed); + if( ed->summon_timer != INVALID_TIMER ) + delete_timer(ed->summon_timer, elemental_summon_end); + ed->summon_timer = INVALID_TIMER; +} + +int elemental_delete(struct elemental_data *ed, int reply) { + struct map_session_data *sd; + + nullpo_ret(ed); + + sd = ed->master; + ed->elemental.life_time = 0; + + elemental_clean_effect(ed); + elemental_summon_stop(ed); + + if( !sd ) + return unit_free(&ed->bl, 0); + + sd->ed = NULL; + sd->status.ele_id = 0; + + return unit_remove_map(&ed->bl, 0); +} + +void elemental_summon_init(struct elemental_data *ed) { + if( ed->summon_timer == INVALID_TIMER ) + ed->summon_timer = add_timer(gettick() + ed->elemental.life_time, elemental_summon_end, ed->master->bl.id, 0); + + ed->regen.state.block = 0; +} + +int elemental_data_received(struct s_elemental *ele, bool flag) { + struct map_session_data *sd; + struct elemental_data *ed; + struct s_elemental_db *db; + int i = elemental_search_index(ele->class_); + + if( (sd = map_charid2sd(ele->char_id)) == NULL ) + return 0; + + if( !flag || i < 0 ) { // Not created - loaded - DB info + sd->status.ele_id = 0; + return 0; + } + + db = &elemental_db[i]; + if( !sd->ed ) { // Initialize it after first summon. + sd->ed = ed = (struct elemental_data*)aCalloc(1,sizeof(struct elemental_data)); + ed->bl.type = BL_ELEM; + ed->bl.id = npc_get_new_npc_id(); + ed->master = sd; + ed->db = db; + memcpy(&ed->elemental, ele, sizeof(struct s_elemental)); + status_set_viewdata(&ed->bl, ed->elemental.class_); + ed->vd->head_mid = 10; // Why? + status_change_init(&ed->bl); + unit_dataset(&ed->bl); + ed->ud.dir = sd->ud.dir; + + ed->bl.m = sd->bl.m; + ed->bl.x = sd->bl.x; + ed->bl.y = sd->bl.y; + unit_calc_pos(&ed->bl, sd->bl.x, sd->bl.y, sd->ud.dir); + ed->bl.x = ed->ud.to_x; + ed->bl.y = ed->ud.to_y; + + map_addiddb(&ed->bl); + status_calc_elemental(ed,1); + ed->last_spdrain_time = ed->last_thinktime = gettick(); + ed->summon_timer = INVALID_TIMER; + elemental_summon_init(ed); + } else { + memcpy(&sd->ed->elemental, ele, sizeof(struct s_elemental)); + ed = sd->ed; + } + + sd->status.ele_id = ele->elemental_id; + + if( ed->bl.prev == NULL && sd->bl.prev != NULL ) { + map_addblock(&ed->bl); + clif_spawn(&ed->bl); + clif_elemental_info(sd); + clif_elemental_updatestatus(sd,SP_HP); + clif_hpmeter_single(sd->fd,ed->bl.id,ed->battle_status.hp,ed->battle_status.max_hp); + clif_elemental_updatestatus(sd,SP_SP); + } + + return 1; +} + +int elemental_clean_single_effect(struct elemental_data *ed, uint16 skill_id) { + struct block_list *bl; + sc_type type = status_skill2sc(skill_id); + + nullpo_ret(ed); + + bl = battle_get_master(&ed->bl); + + if( type ) { + switch( type ) { + // Just remove status change. + case SC_PYROTECHNIC_OPTION: + case SC_HEATER_OPTION: + case SC_TROPIC_OPTION: + case SC_FIRE_CLOAK_OPTION: + case SC_AQUAPLAY_OPTION: + case SC_WATER_SCREEN_OPTION: + case SC_COOLER_OPTION: + case SC_CHILLY_AIR_OPTION: + case SC_GUST_OPTION: + case SC_WIND_STEP_OPTION: + case SC_BLAST_OPTION: + case SC_WATER_DROP_OPTION: + case SC_WIND_CURTAIN_OPTION: + case SC_WILD_STORM_OPTION: + case SC_PETROLOGY_OPTION: + case SC_SOLID_SKIN_OPTION: + case SC_CURSED_SOIL_OPTION: + case SC_STONE_SHIELD_OPTION: + case SC_UPHEAVAL_OPTION: + case SC_CIRCLE_OF_FIRE_OPTION: + case SC_TIDAL_WEAPON_OPTION: + if( bl ) status_change_end(bl,type,INVALID_TIMER); // Master + status_change_end(&ed->bl,type-1,INVALID_TIMER); // Elemental Spirit + break; + case SC_ZEPHYR: + if( bl ) status_change_end(bl,type,INVALID_TIMER); + break; + default: + ShowWarning("Invalid SC=%d in elemental_clean_single_effect\n",type); + break; + } + } + + return 1; +} + +int elemental_clean_effect(struct elemental_data *ed) { + struct map_session_data *sd; + + nullpo_ret(ed); + + // Elemental side + status_change_end(&ed->bl, SC_TROPIC, INVALID_TIMER); + status_change_end(&ed->bl, SC_HEATER, INVALID_TIMER); + status_change_end(&ed->bl, SC_AQUAPLAY, INVALID_TIMER); + status_change_end(&ed->bl, SC_COOLER, INVALID_TIMER); + status_change_end(&ed->bl, SC_CHILLY_AIR, INVALID_TIMER); + status_change_end(&ed->bl, SC_PYROTECHNIC, INVALID_TIMER); + status_change_end(&ed->bl, SC_FIRE_CLOAK, INVALID_TIMER); + status_change_end(&ed->bl, SC_WATER_DROP, INVALID_TIMER); + status_change_end(&ed->bl, SC_WATER_SCREEN, INVALID_TIMER); + status_change_end(&ed->bl, SC_GUST, INVALID_TIMER); + status_change_end(&ed->bl, SC_WIND_STEP, INVALID_TIMER); + status_change_end(&ed->bl, SC_BLAST, INVALID_TIMER); + status_change_end(&ed->bl, SC_WIND_CURTAIN, INVALID_TIMER); + status_change_end(&ed->bl, SC_WILD_STORM, INVALID_TIMER); + status_change_end(&ed->bl, SC_PETROLOGY, INVALID_TIMER); + status_change_end(&ed->bl, SC_SOLID_SKIN, INVALID_TIMER); + status_change_end(&ed->bl, SC_CURSED_SOIL, INVALID_TIMER); + status_change_end(&ed->bl, SC_STONE_SHIELD, INVALID_TIMER); + status_change_end(&ed->bl, SC_UPHEAVAL, INVALID_TIMER); + status_change_end(&ed->bl, SC_CIRCLE_OF_FIRE, INVALID_TIMER); + status_change_end(&ed->bl, SC_TIDAL_WEAPON, INVALID_TIMER); + + if( (sd = ed->master) == NULL ) + return 0; + + // Master side + status_change_end(&sd->bl, SC_TROPIC_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_HEATER_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_AQUAPLAY_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_COOLER_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_CHILLY_AIR_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_PYROTECHNIC_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_FIRE_CLOAK_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WATER_DROP_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WATER_SCREEN_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_GUST_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WIND_STEP_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_BLAST_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WATER_DROP_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WIND_CURTAIN_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_WILD_STORM_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_ZEPHYR, INVALID_TIMER); + status_change_end(&sd->bl, SC_WIND_STEP_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_PETROLOGY_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_SOLID_SKIN_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_CURSED_SOIL_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_STONE_SHIELD_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_UPHEAVAL_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_CIRCLE_OF_FIRE_OPTION, INVALID_TIMER); + status_change_end(&sd->bl, SC_TIDAL_WEAPON_OPTION, INVALID_TIMER); + + return 1; +} + +int elemental_action(struct elemental_data *ed, struct block_list *bl, unsigned int tick) { + struct skill_condition req; + uint16 skill_id, skill_lv; + int i; + + nullpo_ret(ed); + nullpo_ret(bl); + + if( !ed->master ) + return 0; + + if( ed->target_id ) + elemental_unlocktarget(ed); // Remove previous target. + + ARR_FIND(0, MAX_ELESKILLTREE, i, ed->db->skill[i].id && (ed->db->skill[i].mode&EL_SKILLMODE_AGGRESSIVE)); + if( i == MAX_ELESKILLTREE ) + return 0; + + skill_id = ed->db->skill[i].id; + skill_lv = ed->db->skill[i].lv; + + if( elemental_skillnotok(skill_id, ed) ) + return 0; + + if( ed->ud.skilltimer != INVALID_TIMER ) + return 0; + else if( DIFF_TICK(tick, ed->ud.canact_tick) < 0 ) + return 0; + + ed->target_id = ed->ud.skilltarget = bl->id; // Set new target + ed->last_thinktime = tick; + + // Not in skill range. + if( !battle_check_range(&ed->bl,bl,skill_get_range(skill_id,skill_lv)) ) { + // Try to walk to the target. + if( !unit_walktobl(&ed->bl, bl, skill_get_range(skill_id,skill_lv), 2) ) + elemental_unlocktarget(ed); + else { + // Walking, waiting to be in range. Client don't handle it, then we must handle it here. + int walk_dist = distance_bl(&ed->bl,bl) - skill_get_range(skill_id,skill_lv); + ed->ud.skill_id = skill_id; + ed->ud.skill_lv = skill_lv; + + if( skill_get_inf(skill_id) & INF_GROUND_SKILL ) + ed->ud.skilltimer = add_timer( tick+status_get_speed(&ed->bl)*walk_dist, skill_castend_pos, ed->bl.id, 0 ); + else + ed->ud.skilltimer = add_timer( tick+status_get_speed(&ed->bl)*walk_dist, skill_castend_id, ed->bl.id, 0 ); + } + return 1; + + } + + req = elemental_skill_get_requirements(skill_id, skill_lv); + + if(req.hp || req.sp){ + struct map_session_data *sd = BL_CAST(BL_PC, battle_get_master(&ed->bl)); + if( sd ){ + if( sd->skill_id_old != SO_EL_ACTION && //regardless of remaining HP/SP it can be cast + (status_get_hp(&ed->bl) < req.hp || status_get_sp(&ed->bl) < req.sp) ) + return 1; + else + status_zap(&ed->bl, req.hp, req.sp); + } + } + + //Otherwise, just cast the skill. + if( skill_get_inf(skill_id) & INF_GROUND_SKILL ) + unit_skilluse_pos(&ed->bl, bl->x, bl->y, skill_id, skill_lv); + else + unit_skilluse_id(&ed->bl, bl->id, skill_id, skill_lv); + + // Reset target. + ed->target_id = 0; + + return 1; +} + +/*=============================================================== + * Action that elemental perform after changing mode. + * Activates one of the skills of the new mode. + *-------------------------------------------------------------*/ +int elemental_change_mode_ack(struct elemental_data *ed, int mode) { + struct block_list *bl = &ed->master->bl; + uint16 skill_id, skill_lv; + int i; + + nullpo_ret(ed); + + if( !bl ) + return 0; + + // Select a skill. + ARR_FIND(0, MAX_ELESKILLTREE, i, ed->db->skill[i].id && (ed->db->skill[i].mode&mode)); + if( i == MAX_ELESKILLTREE ) + return 0; + + skill_id = ed->db->skill[i].id; + skill_lv = ed->db->skill[i].lv; + + if( elemental_skillnotok(skill_id, ed) ) + return 0; + + if( ed->ud.skilltimer != INVALID_TIMER ) + return 0; + else if( DIFF_TICK(gettick(), ed->ud.canact_tick) < 0 ) + return 0; + + ed->target_id = bl->id; // Set new target + ed->last_thinktime = gettick(); + + if( skill_get_inf(skill_id) & INF_GROUND_SKILL ) + unit_skilluse_pos(&ed->bl, bl->x, bl->y, skill_id, skill_lv); + else + unit_skilluse_id(&ed->bl,bl->id,skill_id,skill_lv); + + ed->target_id = 0; // Reset target after casting the skill to avoid continious attack. + + return 1; +} + +/*=============================================================== + * Change elemental mode. + *-------------------------------------------------------------*/ +int elemental_change_mode(struct elemental_data *ed, int mode) { + nullpo_ret(ed); + + // Remove target + elemental_unlocktarget(ed); + + // Removes the effects of the previous mode. + if(ed->elemental.mode != mode ) elemental_clean_effect(ed); + + ed->battle_status.mode = ed->elemental.mode = mode; + + // Normalize elemental mode to elemental skill mode. + if( mode == EL_MODE_AGGRESSIVE ) mode = EL_SKILLMODE_AGGRESSIVE; // Aggressive spirit mode -> Aggressive spirit skill. + else if( mode == EL_MODE_ASSIST ) mode = EL_SKILLMODE_ASSIST; // Assist spirit mode -> Assist spirit skill. + else mode = EL_SKILLMODE_PASIVE; // Passive spirit mode -> Passive spirit skill. + + // Use a skill inmediately after every change mode. + if( mode != EL_SKILLMODE_AGGRESSIVE ) + elemental_change_mode_ack(ed,mode); + return 1; +} + +void elemental_heal(struct elemental_data *ed, int hp, int sp) { + if( hp ) + clif_elemental_updatestatus(ed->master, SP_HP); + if( sp ) + clif_elemental_updatestatus(ed->master, SP_SP); +} + +int elemental_dead(struct elemental_data *ed) { + elemental_delete(ed, 1); + return 0; +} + +int elemental_unlocktarget(struct elemental_data *ed) { + nullpo_ret(ed); + + ed->target_id = 0; + elemental_stop_attack(ed); + elemental_stop_walking(ed,1); + return 0; +} + +int elemental_skillnotok(uint16 skill_id, struct elemental_data *ed) { + int idx = skill_get_index(skill_id); + nullpo_retr(1,ed); + + if (idx == 0) + return 1; // invalid skill id + + return skillnotok(skill_id, ed->master); +} + +struct skill_condition elemental_skill_get_requirements(uint16 skill_id, uint16 skill_lv){ + struct skill_condition req; + int idx = skill_get_index(skill_id); + + memset(&req,0,sizeof(req)); + + if( idx == 0 ) // invalid skill id + return req; + + if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) + return req; + + req.hp = skill_db[idx].hp[skill_lv-1]; + req.sp = skill_db[idx].sp[skill_lv-1]; + + return req; +} + +int elemental_set_target( struct map_session_data *sd, struct block_list *bl ) { + struct elemental_data *ed = sd->ed; + + nullpo_ret(ed); + nullpo_ret(bl); + + if( ed->bl.m != bl->m || !check_distance_bl(&ed->bl, bl, ed->db->range2) ) + return 0; + + if( !status_check_skilluse(&ed->bl, bl, 0, 0) ) + return 0; + + if( ed->target_id == 0 ) + ed->target_id = bl->id; + + return 1; +} + +static int elemental_ai_sub_timer_activesearch(struct block_list *bl, va_list ap) { + struct elemental_data *ed; + struct block_list **target; + int dist; + + nullpo_ret(bl); + + ed = va_arg(ap,struct elemental_data *); + target = va_arg(ap,struct block_list**); + + //If can't seek yet, not an enemy, or you can't attack it, skip. + if( (*target) == bl || !status_check_skilluse(&ed->bl, bl, 0, 0) ) + return 0; + + if( battle_check_target(&ed->bl,bl,BCT_ENEMY) <= 0 ) + return 0; + + switch( bl->type ) { + case BL_PC: + if( !map_flag_vs(ed->bl.m) ) + return 0; + default: + dist = distance_bl(&ed->bl, bl); + if( ((*target) == NULL || !check_distance_bl(&ed->bl, *target, dist)) && battle_check_range(&ed->bl,bl,ed->db->range2) ) { //Pick closest target? + (*target) = bl; + ed->target_id = bl->id; + ed->min_chase = dist + ed->db->range3; + if( ed->min_chase > AREA_SIZE ) + ed->min_chase = AREA_SIZE; + return 1; + } + break; + } + return 0; +} + +static int elemental_ai_sub_timer(struct elemental_data *ed, struct map_session_data *sd, unsigned int tick) { + struct block_list *target = NULL; + int master_dist, view_range, mode; + + nullpo_ret(ed); + nullpo_ret(sd); + + if( ed->bl.prev == NULL || sd == NULL || sd->bl.prev == NULL ) + return 0; + + // Check if caster can sustain the summoned elemental + if( DIFF_TICK(tick,ed->last_spdrain_time) >= 10000 ){// Drain SP every 10 seconds + int sp = 5; + + switch(ed->vd->class_){ + case 2115: case 2118: + case 2121: case 2124: + sp = 8; + break; + case 2116: case 2119: + case 2122: case 2125: + sp = 11; + break; + } + + if( status_get_sp(&sd->bl) < sp ){ // Can't sustain delete it. + elemental_delete(sd->ed,0); + return 0; + } + + status_zap(&sd->bl,0,sp); + ed->last_spdrain_time = tick; + } + + if( DIFF_TICK(tick,ed->last_thinktime) < MIN_ELETHINKTIME ) + return 0; + + ed->last_thinktime = tick; + + if( ed->ud.skilltimer != INVALID_TIMER ) + return 0; + + if( ed->ud.walktimer != INVALID_TIMER && ed->ud.walkpath.path_pos <= 2 ) + return 0; //No thinking when you just started to walk. + + if(ed->ud.walkpath.path_pos < ed->ud.walkpath.path_len && ed->ud.target == sd->bl.id) + return 0; //No thinking until be near the master. + + if( ed->sc.count && ed->sc.data[SC_BLIND] ) + view_range = 3; + else + view_range = ed->db->range2; + + mode = status_get_mode(&ed->bl); + + master_dist = distance_bl(&sd->bl, &ed->bl); + if( master_dist > AREA_SIZE ) { // Master out of vision range. + elemental_unlocktarget(ed); + unit_warp(&ed->bl,sd->bl.m,sd->bl.x,sd->bl.y,CLR_TELEPORT); + clif_elemental_updatestatus(sd,SP_HP); + clif_elemental_updatestatus(sd,SP_SP); + return 0; + } else if( master_dist > MAX_ELEDISTANCE ) { // Master too far, chase. + short x = sd->bl.x, y = sd->bl.y; + if( ed->target_id ) + elemental_unlocktarget(ed); + if( ed->ud.walktimer != INVALID_TIMER && ed->ud.target == sd->bl.id ) + return 0; //Already walking to him + if( DIFF_TICK(tick, ed->ud.canmove_tick) < 0 ) + return 0; //Can't move yet. + if( map_search_freecell(&ed->bl, sd->bl.m, &x, &y, MIN_ELEDISTANCE, MIN_ELEDISTANCE, 1) + && unit_walktoxy(&ed->bl, x, y, 0) ) + return 0; + } + + if( mode == EL_MODE_AGGRESSIVE ) { + target = map_id2bl(ed->ud.target); + + if( !target ) + map_foreachinrange(elemental_ai_sub_timer_activesearch, &ed->bl, view_range, BL_CHAR, ed, &target, status_get_mode(&ed->bl)); + + if( !target ) { //No targets available. + elemental_unlocktarget(ed); + return 1; + } + + if( battle_check_range(&ed->bl,target,view_range) && rnd()%100 < 2 ) { // 2% chance to cast attack skill. + if( elemental_action(ed,target,tick) ) + return 1; + } + + //Attempt to attack. + //At this point we know the target is attackable, we just gotta check if the range matches. + if( ed->ud.target == target->id && ed->ud.attacktimer != INVALID_TIMER ) //Already locked. + return 1; + + if( battle_check_range(&ed->bl, target, ed->base_status.rhw.range) ) {//Target within range, engage + unit_attack(&ed->bl,target->id,1); + return 1; + } + + //Follow up if possible. + if( !unit_walktobl(&ed->bl, target, ed->base_status.rhw.range, 2) ) + elemental_unlocktarget(ed); + } + + return 0; +} + +static int elemental_ai_sub_foreachclient(struct map_session_data *sd, va_list ap) { + unsigned int tick = va_arg(ap,unsigned int); + if(sd->status.ele_id && sd->ed) + elemental_ai_sub_timer(sd->ed,sd,tick); + + return 0; +} + +static int elemental_ai_timer(int tid, unsigned int tick, int id, intptr_t data) { + map_foreachpc(elemental_ai_sub_foreachclient,tick); + return 0; +} + +int read_elementaldb(void) { + FILE *fp; + char line[1024], *p; + char *str[26]; + int i, j = 0, k = 0, ele; + struct s_elemental_db *db; + struct status_data *status; + + sprintf(line, "%s/%s", db_path, "elemental_db.txt"); + memset(elemental_db,0,sizeof(elemental_db)); + + fp = fopen(line, "r"); + if( !fp ) { + ShowError("read_elementaldb : can't read elemental_db.txt\n"); + return -1; + } + + while( fgets(line, sizeof(line), fp) && j < MAX_ELEMENTAL_CLASS ) { + k++; + if( line[0] == '/' && line[1] == '/' ) + continue; + + if( line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + + i = 0; + p = strtok(line, ","); + while( p != NULL && i < 26 ) { + str[i++] = p; + p = strtok(NULL, ","); + } + if( i < 26 ) { + ShowError("read_elementaldb : Incorrect number of columns at elemental_db.txt line %d.\n", k); + continue; + } + + db = &elemental_db[j]; + db->class_ = atoi(str[0]); + strncpy(db->sprite, str[1], NAME_LENGTH); + strncpy(db->name, str[2], NAME_LENGTH); + db->lv = atoi(str[3]); + + status = &db->status; + db->vd.class_ = db->class_; + + status->max_hp = atoi(str[4]); + status->max_sp = atoi(str[5]); + status->rhw.range = atoi(str[6]); + status->rhw.atk = atoi(str[7]); + status->rhw.atk2 = atoi(str[8]); + status->def = atoi(str[9]); + status->mdef = atoi(str[10]); + status->str = atoi(str[11]); + status->agi = atoi(str[12]); + status->vit = atoi(str[13]); + status->int_ = atoi(str[14]); + status->dex = atoi(str[15]); + status->luk = atoi(str[16]); + db->range2 = atoi(str[17]); + db->range3 = atoi(str[18]); + status->size = atoi(str[19]); + status->race = atoi(str[20]); + + ele = atoi(str[21]); + status->def_ele = ele%10; + status->ele_lv = ele/20; + if( status->def_ele >= ELE_MAX ) { + ShowWarning("Elemental %d has invalid element type %d (max element is %d)\n", db->class_, status->def_ele, ELE_MAX - 1); + status->def_ele = ELE_NEUTRAL; + } + if( status->ele_lv < 1 || status->ele_lv > 4 ) { + ShowWarning("Elemental %d has invalid element level %d (max is 4)\n", db->class_, status->ele_lv); + status->ele_lv = 1; + } + + status->aspd_rate = 1000; + status->speed = atoi(str[22]); + status->adelay = atoi(str[23]); + status->amotion = atoi(str[24]); + status->dmotion = atoi(str[25]); + + j++; + } + + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' elementals in '"CL_WHITE"db/elemental_db.txt"CL_RESET"'.\n",j); + + return 0; +} + +int read_elemental_skilldb(void) { + FILE *fp; + char line[1024], *p; + char *str[4]; + struct s_elemental_db *db; + int i, j = 0, k = 0, class_; + uint16 skill_id, skill_lv; + int skillmode; + + sprintf(line, "%s/%s", db_path, "elemental_skill_db.txt"); + fp = fopen(line, "r"); + if( !fp ) { + ShowError("read_elemental_skilldb : can't read elemental_skill_db.txt\n"); + return -1; + } + + while( fgets(line, sizeof(line), fp) ) { + k++; + if( line[0] == '/' && line[1] == '/' ) + continue; + + if( line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + + i = 0; + p = strtok(line, ","); + while( p != NULL && i < 4 ) { + str[i++] = p; + p = strtok(NULL, ","); + } + if( i < 4 ) { + ShowError("read_elemental_skilldb : Incorrect number of columns at elemental_skill_db.txt line %d.\n", k); + continue; + } + + class_ = atoi(str[0]); + ARR_FIND(0, MAX_ELEMENTAL_CLASS, i, class_ == elemental_db[i].class_); + if( i == MAX_ELEMENTAL_CLASS ) { + ShowError("read_elemental_skilldb : Class not found in elemental_db for skill entry, line %d.\n", k); + continue; + } + + skill_id = atoi(str[1]); + if( skill_id < EL_SKILLBASE || skill_id >= EL_SKILLBASE + MAX_ELEMENTALSKILL ) { + ShowError("read_elemental_skilldb : Skill out of range, line %d.\n", k); + continue; + } + + db = &elemental_db[i]; + skill_lv = atoi(str[2]); + + skillmode = atoi(str[3]); + if( skillmode < EL_SKILLMODE_PASIVE || skillmode > EL_SKILLMODE_AGGRESSIVE ) { + ShowError("read_elemental_skilldb : Skillmode out of range, line %d.\n",k); + continue; + } + ARR_FIND( 0, MAX_ELESKILLTREE, i, db->skill[i].id == 0 || db->skill[i].id == skill_id ); + if( i == MAX_ELESKILLTREE ) { + ShowWarning("Unable to load skill %d into Elemental %d's tree. Maximum number of skills per elemental has been reached.\n", skill_id, class_); + continue; + } + db->skill[i].id = skill_id; + db->skill[i].lv = skill_lv; + db->skill[i].mode = skillmode; + j++; + } + + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"db/elemental_skill_db.txt"CL_RESET"'.\n",j); + return 0; +} + +void reload_elementaldb(void) { + read_elementaldb(); + reload_elemental_skilldb(); +} + +void reload_elemental_skilldb(void) { + read_elemental_skilldb(); +} + +int do_init_elemental(void) { + read_elementaldb(); + read_elemental_skilldb(); + + add_timer_func_list(elemental_ai_timer,"elemental_ai_timer"); + add_timer_interval(gettick()+MIN_ELETHINKTIME,elemental_ai_timer,0,0,MIN_ELETHINKTIME); + + return 0; +} + +void do_final_elemental(void) { + return; +} diff --git a/src/map/elemental.h b/src/map/elemental.h new file mode 100644 index 000000000..f941f3dfd --- /dev/null +++ b/src/map/elemental.h @@ -0,0 +1,94 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _ELEMENTAL_H_ +#define _ELEMENTAL_H_ + +#include "status.h" // struct status_data, struct status_change +#include "unit.h" // struct unit_data + +#define MIN_ELETHINKTIME 100 +#define MIN_ELEDISTANCE 2 +#define MAX_ELEDISTANCE 5 + +#define EL_MODE_AGGRESSIVE (MD_CANMOVE|MD_AGGRESSIVE|MD_CANATTACK) +#define EL_MODE_ASSIST (MD_CANMOVE|MD_ASSIST) +#define EL_MODE_PASSIVE MD_CANMOVE + +#define EL_SKILLMODE_PASIVE 0x1 +#define EL_SKILLMODE_ASSIST 0x2 +#define EL_SKILLMODE_AGGRESSIVE 0x4 + +struct elemental_skill { + unsigned short id, lv; + short mode; +}; + +struct s_elemental_db { + int class_; + char sprite[NAME_LENGTH], name[NAME_LENGTH]; + unsigned short lv; + short range2, range3; + struct status_data status; + struct view_data vd; + struct elemental_skill skill[MAX_ELESKILLTREE]; +}; + +extern struct s_elemental_db elemental_db[MAX_ELEMENTAL_CLASS]; + +struct elemental_data { + struct block_list bl; + struct unit_data ud; + struct view_data *vd; + struct status_data base_status, battle_status; + struct status_change sc; + struct regen_data regen; + + struct s_elemental_db *db; + struct s_elemental elemental; + + struct map_session_data *master; + int summon_timer; + int skill_timer; + + unsigned last_thinktime, last_linktime, last_spdrain_time; + short min_chase; + int target_id, attacked_id; +}; + +bool elemental_class(int class_); +struct view_data * elemental_get_viewdata(int class_); + +int elemental_create(struct map_session_data *sd, int class_, unsigned int lifetime); +int elemental_data_received(struct s_elemental *ele, bool flag); +int elemental_save(struct elemental_data *ed); + +int elemental_change_mode_ack(struct elemental_data *ed, int mode); +int elemental_change_mode(struct elemental_data *ed, int mode); + +void elemental_heal(struct elemental_data *ed, int hp, int sp); +int elemental_dead(struct elemental_data *ed); + +int elemental_delete(struct elemental_data *ed, int reply); +void elemental_summon_stop(struct elemental_data *ed); + +int elemental_get_lifetime(struct elemental_data *ed); + +int elemental_unlocktarget(struct elemental_data *ed); +int elemental_skillnotok(uint16 skill_id, struct elemental_data *ed); +int elemental_set_target( struct map_session_data *sd, struct block_list *bl ); +int elemental_clean_single_effect(struct elemental_data *ed, uint16 skill_id); +int elemental_clean_effect(struct elemental_data *ed); +int elemental_action(struct elemental_data *ed, struct block_list *bl, unsigned int tick); +struct skill_condition elemental_skill_get_requirements(uint16 skill_id, uint16 skill_lv); + +#define elemental_stop_walking(ed, type) unit_stop_walking(&(ed)->bl, type) +#define elemental_stop_attack(ed) unit_stop_attack(&(ed)->bl) + +int read_elemental_skilldb(void); +void reload_elementaldb(void); +void reload_elemental_skilldb(void); +int do_init_elemental(void); +void do_final_elemental(void); + +#endif /* _ELEMENTAL_H_ */ diff --git a/src/map/guild.c b/src/map/guild.c new file mode 100644 index 000000000..780154590 --- /dev/null +++ b/src/map/guild.c @@ -0,0 +1,2147 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/mapindex.h" +#include "../common/showmsg.h" +#include "../common/ers.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "map.h" +#include "guild.h" +#include "storage.h" +#include "battle.h" +#include "npc.h" +#include "pc.h" +#include "status.h" +#include "mob.h" +#include "intif.h" +#include "clif.h" +#include "skill.h" +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +static DBMap* guild_db; // int guild_id -> struct guild* +static DBMap* castle_db; // int castle_id -> struct guild_castle* +static DBMap* guild_expcache_db; // int char_id -> struct guild_expcache* +static DBMap* guild_infoevent_db; // int guild_id -> struct eventlist* + +struct eventlist { + char name[EVENT_NAME_LENGTH]; + struct eventlist *next; +}; + +//Constant related to the flash of the Guild EXP cache +#define GUILD_SEND_XY_INVERVAL 5000 // Interval of sending coordinates and HP +#define GUILD_PAYEXP_INVERVAL 10000 //Interval (maximum survival time of the cache, in milliseconds) +#define GUILD_PAYEXP_LIST 8192 //The maximum number of cache + +//Guild EXP cache + +struct guild_expcache { + int guild_id, account_id, char_id; + uint64 exp; +}; +static struct eri *expcache_ers; //For handling of guild exp payment. + +#define MAX_GUILD_SKILL_REQUIRE 5 +struct{ + int id; + int max; + struct{ + short id; + short lv; + }need[MAX_GUILD_SKILL_REQUIRE]; +} guild_skill_tree[MAX_GUILDSKILL]; + +int guild_payexp_timer(int tid, unsigned int tick, int id, intptr_t data); +static int guild_send_xy_timer(int tid, unsigned int tick, int id, intptr_t data); + +/* guild flags cache */ +struct npc_data **guild_flags; +unsigned short guild_flags_count; + +/*========================================== + * Retrieves and validates the sd pointer for this guild member [Skotlex] + *------------------------------------------*/ +static TBL_PC* guild_sd_check(int guild_id, int account_id, int char_id) +{ + TBL_PC* sd = map_id2sd(account_id); + + if (!(sd && sd->status.char_id == char_id)) + return NULL; + + if (sd->status.guild_id != guild_id) + { //If player belongs to a different guild, kick him out. + intif_guild_leave(guild_id,account_id,char_id,0,"** Guild Mismatch **"); + return NULL; + } + + return sd; +} + + // Modified [Komurka] +int guild_skill_get_max (int id) +{ + if (id < GD_SKILLBASE || id >= GD_SKILLBASE+MAX_GUILDSKILL) + return 0; + return guild_skill_tree[id-GD_SKILLBASE].max; +} + +// Retrive skill_lv learned by guild + +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; +} + +/*========================================== + * guild_skill_tree.txt reading - from jA [Komurka] + *------------------------------------------*/ +static bool guild_read_guildskill_tree_db(char* split[], int columns, int current) +{// <skill id>,<max lv>,<req id1>,<req lv1>,<req id2>,<req lv2>,<req id3>,<req lv3>,<req id4>,<req lv4>,<req id5>,<req lv5> + int k, id, skill_id; + + skill_id = atoi(split[0]); + id = skill_id - GD_SKILLBASE; + + if( id < 0 || id >= MAX_GUILDSKILL ) + { + ShowWarning("guild_read_guildskill_tree_db: Invalid skill id %d.\n", skill_id); + return false; + } + + guild_skill_tree[id].id = skill_id; + guild_skill_tree[id].max = atoi(split[1]); + + if( guild_skill_tree[id].id == GD_GLORYGUILD && battle_config.require_glory_guild && guild_skill_tree[id].max == 0 ) + {// enable guild's glory when required for emblems + guild_skill_tree[id].max = 1; + } + + for( k = 0; k < MAX_GUILD_SKILL_REQUIRE; k++ ) + { + guild_skill_tree[id].need[k].id = atoi(split[k*2+2]); + guild_skill_tree[id].need[k].lv = atoi(split[k*2+3]); + } + + return true; +} + +/*========================================== + * Guild skill check - from jA [Komurka] + *------------------------------------------*/ +int guild_check_skill_require(struct guild *g,int id) +{ + int i; + int idx = id-GD_SKILLBASE; + + if(g == NULL) + return 0; + + if (idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + for(i=0;i<MAX_GUILD_SKILL_REQUIRE;i++) + { + if(guild_skill_tree[idx].need[i].id == 0) break; + if(guild_skill_tree[idx].need[i].lv > guild_checkskill(g,guild_skill_tree[idx].need[i].id)) + return 0; + } + return 1; +} + +static bool guild_read_castledb(char* str[], int columns, int current) +{// <castle id>,<map name>,<castle name>,<castle event>[,<reserved/unused switch flag>] + struct guild_castle *gc; + int mapindex = mapindex_name2id(str[1]); + + if (map_mapindex2mapid(mapindex) < 0) // Map not found or on another map-server + return false; + + CREATE(gc, struct guild_castle, 1); + gc->castle_id = atoi(str[0]); + gc->mapindex = mapindex; + safestrncpy(gc->castle_name, str[2], sizeof(gc->castle_name)); + safestrncpy(gc->castle_event, str[3], sizeof(gc->castle_event)); + + idb_put(castle_db,gc->castle_id,gc); + + //intif_guild_castle_info(gc->castle_id); + + return true; +} + +/// lookup: guild id -> guild* +struct guild* guild_search(int guild_id) +{ + return (struct guild*)idb_get(guild_db,guild_id); +} + +/// lookup: guild name -> guild* +struct guild* guild_searchname(char* str) +{ + struct guild* g; + DBIterator *iter = db_iterator(guild_db); + + for( g = dbi_first(iter); dbi_exists(iter); g = dbi_next(iter) ) + { + if( strcmpi(g->name, str) == 0 ) + break; + } + dbi_destroy(iter); + + return g; +} + +/// lookup: castle id -> castle* +struct guild_castle* guild_castle_search(int gcid) +{ + return (struct guild_castle*)idb_get(castle_db,gcid); +} + +/// lookup: map index -> castle* +struct guild_castle* guild_mapindex2gc(short mapindex) +{ + struct guild_castle* gc; + DBIterator *iter = db_iterator(castle_db); + + for( gc = dbi_first(iter); dbi_exists(iter); gc = dbi_next(iter) ) + { + if( gc->mapindex == mapindex ) + break; + } + dbi_destroy(iter); + + return gc; +} + +/// lookup: map name -> castle* +struct guild_castle* guild_mapname2gc(const char* mapname) +{ + return guild_mapindex2gc(mapindex_name2id(mapname)); +} + +struct map_session_data* guild_getavailablesd(struct guild* g) +{ + int i; + + nullpo_retr(NULL, g); + + ARR_FIND( 0, g->max_member, i, g->member[i].sd != NULL ); + return( i < g->max_member ) ? g->member[i].sd : NULL; +} + +/// lookup: player AID/CID -> member index +int guild_getindex(struct guild *g,int account_id,int char_id) +{ + int i; + + if( g == NULL ) + return -1; + + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == account_id && g->member[i].char_id == char_id ); + return( i < g->max_member ) ? i : -1; +} + +/// lookup: player sd -> member position +int guild_getposition(struct guild* g, struct map_session_data* sd) +{ + int i; + + if( g == NULL && (g=guild_search(sd->status.guild_id)) == NULL ) + return -1; + + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == sd->status.account_id && g->member[i].char_id == sd->status.char_id ); + return( i < g->max_member ) ? g->member[i].position : -1; +} + +//Creation of member information +void guild_makemember(struct guild_member *m,struct map_session_data *sd) +{ + nullpo_retv(sd); + + memset(m,0,sizeof(struct guild_member)); + m->account_id =sd->status.account_id; + m->char_id =sd->status.char_id; + m->hair =sd->status.hair; + m->hair_color =sd->status.hair_color; + m->gender =sd->status.sex; + m->class_ =sd->status.class_; + m->lv =sd->status.base_level; +// m->exp =0; +// m->exp_payper =0; + m->online =1; + m->position =MAX_GUILDPOSITION-1; + memcpy(m->name,sd->status.name,NAME_LENGTH); + return; +} + +/** + * Server cache to be flushed to inter the Guild EXP + * @see DBApply + */ +int guild_payexp_timer_sub(DBKey key, DBData *data, va_list ap) { + int i; + struct guild_expcache *c; + struct guild *g; + + c = db_data2ptr(data); + + if ( + (g = guild_search(c->guild_id)) == NULL || + (i = guild_getindex(g, c->account_id, c->char_id)) < 0 + ) { + ers_free(expcache_ers, c); + return 0; + } + + if (g->member[i].exp > UINT64_MAX - c->exp) + g->member[i].exp = UINT64_MAX; + else + g->member[i].exp+= c->exp; + + intif_guild_change_memberinfo(g->guild_id,c->account_id,c->char_id, + GMI_EXP,&g->member[i].exp,sizeof(g->member[i].exp)); + c->exp=0; + + ers_free(expcache_ers, c); + return 0; +} + +int guild_payexp_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + guild_expcache_db->clear(guild_expcache_db,guild_payexp_timer_sub); + return 0; +} + +/** + * Taken from party_send_xy_timer_sub. [Skotlex] + * @see DBApply + */ +int guild_send_xy_timer_sub(DBKey key, DBData *data, va_list ap) +{ + struct guild *g = db_data2ptr(data); + int i; + + nullpo_ret(g); + + if( !g->connect_member ) + {// no members connected to this guild so do not iterate + return 0; + } + + for(i=0;i<g->max_member;i++){ + struct map_session_data* sd = g->member[i].sd; + if( sd != NULL && sd->fd && (sd->guild_x != sd->bl.x || sd->guild_y != sd->bl.y) && !sd->bg_id ) + { + clif_guild_xy(sd); + sd->guild_x = sd->bl.x; + sd->guild_y = sd->bl.y; + } + } + return 0; +} + +//Code from party_send_xy_timer [Skotlex] +static int guild_send_xy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + guild_db->foreach(guild_db,guild_send_xy_timer_sub,tick); + return 0; +} + +int guild_send_dot_remove(struct map_session_data *sd) +{ + if (sd->status.guild_id) + clif_guild_xy_remove(sd); + return 0; +} +//------------------------------------------------------------------------ + +int guild_create(struct map_session_data *sd, const char *name) +{ + char tname[NAME_LENGTH]; + struct guild_member m; + nullpo_ret(sd); + + safestrncpy(tname, name, NAME_LENGTH); + trim(tname); + + if( !tname[0] ) + return 0; // empty name + + if( sd->status.guild_id ) + {// already in a guild + clif_guild_created(sd,1); + return 0; + } + if( battle_config.guild_emperium_check && pc_search_inventory(sd,714) == -1 ) + {// item required + clif_guild_created(sd,3); + return 0; + } + + guild_makemember(&m,sd); + m.position=0; + intif_guild_create(name,&m); + return 1; +} + +//Whether or not to create guild +int guild_created(int account_id,int guild_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + + if(sd==NULL) + return 0; + if(!guild_id) { + clif_guild_created(sd, 2); // Creation failure (presence of the same name Guild) + return 0; + } + //struct guild *g; + sd->status.guild_id=guild_id; + clif_guild_created(sd,0); + if(battle_config.guild_emperium_check) + pc_delitem(sd,pc_search_inventory(sd,ITEMID_EMPERIUM),1,0,0,LOG_TYPE_CONSUME); //emperium consumption + return 0; +} + +//Information request +int guild_request_info(int guild_id) +{ + return intif_guild_request_info(guild_id); +} + +//Information request with event +int guild_npc_request_info(int guild_id,const char *event) +{ + if( guild_search(guild_id) ) + { + if( event && *event ) + npc_event_do(event); + + return 0; + } + + if( event && *event ) + { + struct eventlist *ev; + DBData prev; + ev=(struct eventlist *)aCalloc(sizeof(struct eventlist),1); + memcpy(ev->name,event,strlen(event)); + //The one in the db (if present) becomes the next event from this. + if (guild_infoevent_db->put(guild_infoevent_db, db_i2key(guild_id), db_ptr2data(ev), &prev)) + ev->next = db_data2ptr(&prev); + } + + return guild_request_info(guild_id); +} + +//Confirmation of the character belongs to guild +int guild_check_member(struct guild *g) +{ + int i; + struct map_session_data *sd; + struct s_mapiterator* iter; + + nullpo_ret(g); + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if( sd->status.guild_id != g->guild_id ) + continue; + + i = guild_getindex(g,sd->status.account_id,sd->status.char_id); + if (i < 0) { + sd->status.guild_id=0; + sd->guild_emblem_id=0; + ShowWarning("guild: check_member %d[%s] is not member\n",sd->status.account_id,sd->status.name); + } + } + mapit_free(iter); + + return 0; +} + +//Delete association with guild_id for all characters +int guild_recv_noinfo(int guild_id) +{ + struct map_session_data *sd; + struct s_mapiterator* iter; + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if( sd->status.guild_id == guild_id ) + sd->status.guild_id = 0; // erase guild + } + mapit_free(iter); + + return 0; +} + +//Get and display information for all member +int guild_recv_info(struct guild *sg) +{ + struct guild *g,before; + int i,bm,m; + DBData data; + struct map_session_data *sd; + bool guild_new = false; + + nullpo_ret(sg); + + if((g = (struct guild*)idb_get(guild_db,sg->guild_id))==NULL) + { + guild_new = true; + g=(struct guild *)aCalloc(1,sizeof(struct guild)); + idb_put(guild_db,sg->guild_id,g); + before=*sg; + + //Perform the check on the user because the first load + guild_check_member(sg); + if ((sd = map_nick2sd(sg->master)) != NULL) + { + //If the guild master is online the first time the guild_info is received, + //that means he was the first to join, so apply guild skill blocking here. + if( battle_config.guild_skill_relog_delay ) + guild_block_skill(sd, 300000); + + //Also set the guild master flag. + sd->state.gmaster_flag = g; + clif_charnameupdate(sd); // [LuzZza] + clif_guild_masterormember(sd); + } + }else + before=*g; + memcpy(g,sg,sizeof(struct guild)); + + if(g->max_member > MAX_GUILD) + { + ShowError("guild_recv_info: Received guild with %d members, but MAX_GUILD is only %d. Extra guild-members have been lost!\n", g->max_member, MAX_GUILD); + g->max_member = MAX_GUILD; + } + + for(i=bm=m=0;i<g->max_member;i++){ + if(g->member[i].account_id>0){ + sd = g->member[i].sd = guild_sd_check(g->guild_id, g->member[i].account_id, g->member[i].char_id); + if (sd) clif_charnameupdate(sd); // [LuzZza] + m++; + }else + g->member[i].sd=NULL; + if(before.member[i].account_id>0) + bm++; + } + + for (i = 0; i < g->max_member; i++) { //Transmission of information at all members + sd = g->member[i].sd; + if( sd==NULL ) + continue; + + if (before.guild_lv != g->guild_lv || bm != m || + before.max_member != g->max_member) { + clif_guild_basicinfo(sd); //Submit basic information + clif_guild_emblem(sd, g); //Submit emblem + } + + if (bm != m) { //Send members information + clif_guild_memberlist(g->member[i].sd); + } + + if (before.skill_point != g->skill_point) + clif_guild_skillinfo(sd); //Submit information skills + + if (guild_new) { // Send information and affiliation if unsent + clif_guild_belonginfo(sd, g); + clif_guild_notice(sd, g); + sd->guild_emblem_id = g->emblem_id; + } + } + + //Occurrence of an event + if (guild_infoevent_db->remove(guild_infoevent_db, db_i2key(sg->guild_id), &data)) + { + struct eventlist *ev = db_data2ptr(&data), *ev2; + while(ev){ + npc_event_do(ev->name); + ev2=ev->next; + aFree(ev); + ev=ev2; + } + } + + return 0; +} + +/*============================================= + * Player sd send a guild invatation to player tsd to join his guild + *--------------------------------------------*/ +int guild_invite(struct map_session_data *sd, struct map_session_data *tsd) { + struct guild *g; + int i; + + nullpo_ret(sd); + + g=guild_search(sd->status.guild_id); + + if(tsd==NULL || g==NULL) + return 0; + + if( (i=guild_getposition(g,sd))<0 || !(g->position[i].mode&0x0001) ) + return 0; //Invite permission. + + if(!battle_config.invite_request_check) { + if (tsd->party_invite > 0 || tsd->trade_partner || tsd->adopt_invite) { //checking if there no other invitation pending + clif_guild_inviteack(sd,0); + return 0; + } + } + + if (!tsd->fd) { //You can't invite someone who has already disconnected. + clif_guild_inviteack(sd,1); + return 0; + } + + if(tsd->status.guild_id>0 || + tsd->guild_invite>0 || + ((agit_flag || agit2_flag) && map[tsd->bl.m].flag.gvg_castle)) + { //Can't invite people inside castles. [Skotlex] + clif_guild_inviteack(sd,0); + return 0; + } + + //search an empty spot in guild + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == 0 ); + if(i==g->max_member){ + clif_guild_inviteack(sd,3); + return 0; + } + + tsd->guild_invite=sd->status.guild_id; + tsd->guild_invite_account=sd->status.account_id; + + clif_guild_invite(tsd,g); + return 0; +} + +/// Guild invitation reply. +/// flag: 0:rejected, 1:accepted +int guild_reply_invite(struct map_session_data* sd, int guild_id, int flag) +{ + struct map_session_data* tsd; + + nullpo_ret(sd); + + // subsequent requests may override the value + if( sd->guild_invite != guild_id ) + return 0; // mismatch + + // look up the person who sent the invite + //NOTE: this can be NULL because the person might have logged off in the meantime + tsd = map_id2sd(sd->guild_invite_account); + + if ( sd->status.guild_id > 0 ) // [Paradox924X] + { // Already in another guild. + if ( tsd ) clif_guild_inviteack(tsd,0); + return 0; + } + else if( flag == 0 ) + {// rejected + sd->guild_invite = 0; + sd->guild_invite_account = 0; + if( tsd ) clif_guild_inviteack(tsd,1); + } + else + {// accepted + struct guild_member m; + struct guild* g; + int i; + + if( (g=guild_search(guild_id)) == NULL ) + { + sd->guild_invite = 0; + sd->guild_invite_account = 0; + return 0; + } + + ARR_FIND( 0, g->max_member, i, g->member[i].account_id == 0 ); + if( i == g->max_member ) + { + sd->guild_invite = 0; + sd->guild_invite_account = 0; + if( tsd ) clif_guild_inviteack(tsd,3); + return 0; + } + + guild_makemember(&m,sd); + intif_guild_addmember(guild_id, &m); + //TODO: send a minimap update to this player + } + + return 0; +} + +//Invoked when a player joins. +//- If guild is not in memory, it is requested +//- Otherwise sd pointer is set up. +//- Player must be authed and must belong to a guild before invoking this method +void guild_member_joined(struct map_session_data *sd) +{ + struct guild* g; + int i; + g=guild_search(sd->status.guild_id); + if (!g) { + guild_request_info(sd->status.guild_id); + return; + } + if (strcmp(sd->status.name,g->master) == 0) + { // set the Guild Master flag + sd->state.gmaster_flag = g; + // prevent Guild Skills from being used directly after relog + if( battle_config.guild_skill_relog_delay ) + guild_block_skill(sd, 300000); + } + i = guild_getindex(g, sd->status.account_id, sd->status.char_id); + if (i == -1) + sd->status.guild_id = 0; + else + g->member[i].sd = sd; +} + +/*========================================== + * Add a player to a given guild_id + *----------------------------------------*/ +int guild_member_added(int guild_id,int account_id,int char_id,int flag) +{ + struct map_session_data *sd= map_id2sd(account_id),*sd2; + struct guild *g; + + if( (g=guild_search(guild_id))==NULL ) + return 0; + + if(sd==NULL || sd->guild_invite==0){ + // cancel if player not present or invalide guild_id invitation + if (flag == 0) { + ShowError("guild: member added error %d is not online\n",account_id); + intif_guild_leave(guild_id,account_id,char_id,0,"** Data Error **"); + } + return 0; + } + sd2 = map_id2sd(sd->guild_invite_account); + sd->guild_invite = 0; + sd->guild_invite_account = 0; + + if (flag == 1) { //failure + if( sd2!=NULL ) + clif_guild_inviteack(sd2,3); + return 0; + } + + //if all ok add player to guild + sd->status.guild_id = g->guild_id; + sd->guild_emblem_id = g->emblem_id; + //Packets which were sent in the previous 'guild_sent' implementation. + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + + //TODO: send new emblem info to others + + if( sd2!=NULL ) + clif_guild_inviteack(sd2,2); + + //Next line commented because it do nothing, look at guild_recv_info [LuzZza] + //clif_charnameupdate(sd); //Update display name [Skotlex] + + return 0; +} + +/*========================================== + * Player request leaving a given guild_id + *----------------------------------------*/ +int guild_leave(struct map_session_data* sd, int guild_id, int account_id, int char_id, const char* mes) +{ + struct guild *g; + + nullpo_ret(sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if(sd->status.account_id!=account_id || + sd->status.char_id!=char_id || sd->status.guild_id!=guild_id || + ((agit_flag || agit2_flag) && map[sd->bl.m].flag.gvg_castle)) + return 0; + + intif_guild_leave(sd->status.guild_id, sd->status.account_id, sd->status.char_id,0,mes); + return 0; +} + +/*========================================== + * Request remove a player to a given guild_id + *----------------------------------------*/ +int guild_expulsion(struct map_session_data* sd, int guild_id, int account_id, int char_id, const char* mes) +{ + struct map_session_data *tsd; + struct guild *g; + int i,ps; + + nullpo_ret(sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if(sd->status.guild_id!=guild_id) + return 0; + + if( (ps=guild_getposition(g,sd))<0 || !(g->position[ps].mode&0x0010) ) + return 0; //Expulsion permission + + //Can't leave inside guild castles. + if ((tsd = map_id2sd(account_id)) && + tsd->status.char_id == char_id && + ((agit_flag || agit2_flag) && map[tsd->bl.m].flag.gvg_castle)) + return 0; + + // find the member and perform expulsion + i = guild_getindex(g, account_id, char_id); + if( i != -1 && strcmp(g->member[i].name,g->master) != 0 ) //Can't expel the GL! + intif_guild_leave(g->guild_id,account_id,char_id,1,mes); + + return 0; +} + +int guild_member_withdraw(int guild_id, int account_id, int char_id, int flag, const char* name, const char* mes) +{ + int i; + struct guild* g = guild_search(guild_id); + struct map_session_data* sd = map_charid2sd(char_id); + struct map_session_data* online_member_sd; + + if(g == NULL) + return 0; // no such guild (error!) + + i = guild_getindex(g, account_id, char_id); + if( i == -1 ) + return 0; // not a member (inconsistency!) + + online_member_sd = guild_getavailablesd(g); + if(online_member_sd == NULL) + return 0; // noone online to inform + + if(!flag) + clif_guild_leave(online_member_sd, name, mes); + else + clif_guild_expulsion(online_member_sd, name, mes, account_id); + + // remove member from guild + memset(&g->member[i],0,sizeof(struct guild_member)); + clif_guild_memberlist(online_member_sd); + + // update char, if online + if(sd != NULL && sd->status.guild_id == guild_id) + { + // do stuff that needs the guild_id first, BEFORE we wipe it + if (sd->state.storage_flag == 2) //Close the guild storage. + storage_guild_storageclose(sd); + guild_send_dot_remove(sd); + + sd->status.guild_id = 0; + sd->guild_emblem_id = 0; + + clif_charnameupdate(sd); //Update display name [Skotlex] + //TODO: send emblem update to self and people around + } + return 0; +} + +int guild_send_memberinfoshort(struct map_session_data *sd,int online) +{ // cleaned up [LuzZza] + struct guild *g; + + nullpo_ret(sd); + + if(sd->status.guild_id <= 0) + return 0; + + if(!(g = guild_search(sd->status.guild_id))) + return 0; + + intif_guild_memberinfoshort(g->guild_id, + sd->status.account_id,sd->status.char_id,online,sd->status.base_level,sd->status.class_); + + if(!online){ + int i=guild_getindex(g,sd->status.account_id,sd->status.char_id); + if(i>=0) + g->member[i].sd=NULL; + else + ShowError("guild_send_memberinfoshort: Failed to locate member %d:%d in guild %d!\n", sd->status.account_id, sd->status.char_id, g->guild_id); + return 0; + } + + if(sd->state.connect_new) + { //Note that this works because it is invoked in parse_LoadEndAck before connect_new is cleared. + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + sd->guild_emblem_id = g->emblem_id; + } + return 0; +} + +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class_) +{ // cleaned up [LuzZza] + + int i,alv,c,idx=-1,om=0,oldonline=-1; + struct guild *g = guild_search(guild_id); + + if(g == NULL) + return 0; + + for(i=0,alv=0,c=0,om=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(!m->account_id) continue; + if(m->account_id==account_id && m->char_id==char_id ){ + oldonline=m->online; + m->online=online; + m->lv=lv; + m->class_=class_; + idx=i; + } + alv+=m->lv; + c++; + if(m->online) + om++; + } + + if(idx == -1 || c == 0) { + //Treat char_id who doesn't match guild_id (not found as member) + struct map_session_data *sd = map_id2sd(account_id); + if(sd && sd->status.char_id == char_id) { + sd->status.guild_id=0; + sd->guild_emblem_id=0; + } + ShowWarning("guild: not found member %d,%d on %d[%s]\n", account_id,char_id,guild_id,g->name); + return 0; + } + + g->average_lv=alv/c; + g->connect_member=om; + + //Ensure validity of pointer (ie: player logs in/out, changes map-server) + g->member[idx].sd = guild_sd_check(guild_id, account_id, char_id); + + if(oldonline!=online) + clif_guild_memberlogin_notice(g, idx, online); + + if(!g->member[idx].sd) + return 0; + + //Send XY dot updates. [Skotlex] + //Moved from guild_send_memberinfoshort [LuzZza] + for(i=0; i < g->max_member; i++) { + + if(!g->member[i].sd || i == idx || + g->member[i].sd->bl.m != g->member[idx].sd->bl.m) + continue; + + clif_guild_xy_single(g->member[idx].sd->fd, g->member[i].sd); + clif_guild_xy_single(g->member[i].sd->fd, g->member[idx].sd); + } + + return 0; +} + +/*==================================================== + * Send a message to whole guild + *---------------------------------------------------*/ +int guild_send_message(struct map_session_data *sd,const char *mes,int len) +{ + nullpo_ret(sd); + + if(sd->status.guild_id==0) + return 0; + intif_guild_message(sd->status.guild_id,sd->status.account_id,mes,len); + guild_recv_message(sd->status.guild_id,sd->status.account_id,mes,len); + + // Chat logging type 'G' / Guild Chat + log_chat(LOG_CHAT_GUILD, sd->status.guild_id, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, mes); + + return 0; +} + +/*==================================================== + * Guild receive a message, will be displayed to whole member + *---------------------------------------------------*/ +int guild_recv_message(int guild_id,int account_id,const char *mes,int len) +{ + struct guild *g; + if( (g=guild_search(guild_id))==NULL) + return 0; + clif_guild_message(g,account_id,mes,len); + return 0; +} + +/*==================================================== + * Member changing position in guild + *---------------------------------------------------*/ +int guild_change_memberposition(int guild_id,int account_id,int char_id,short idx) +{ + return intif_guild_change_memberinfo(guild_id,account_id,char_id,GMI_POSITION,&idx,sizeof(idx)); +} + +/*==================================================== + * Notification of new position for member + *---------------------------------------------------*/ +int guild_memberposition_changed(struct guild *g,int idx,int pos) +{ + nullpo_ret(g); + + g->member[idx].position=pos; + clif_guild_memberpositionchanged(g,idx); + + // Update char position in client [LuzZza] + if(g->member[idx].sd != NULL) + clif_charnameupdate(g->member[idx].sd); + return 0; +} + +/*==================================================== + * Change guild title or member + *---------------------------------------------------*/ +int guild_change_position(int guild_id,int idx, + int mode,int exp_mode,const char *name) +{ + struct guild_position p; + + exp_mode = cap_value(exp_mode, 0, battle_config.guild_exp_limit); + //Mode 0x01 <- Invite + //Mode 0x10 <- Expel. + p.mode=mode&0x11; + p.exp_mode=exp_mode; + safestrncpy(p.name,name,NAME_LENGTH); + return intif_guild_position(guild_id,idx,&p); +} + +/*==================================================== + * Notification of member has changed his guild title + *---------------------------------------------------*/ +int guild_position_changed(int guild_id,int idx,struct guild_position *p) +{ + struct guild *g=guild_search(guild_id); + int i; + if(g==NULL) + return 0; + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + clif_guild_positionchanged(g,idx); + + // Update char name in client [LuzZza] + for(i=0;i<g->max_member;i++) + if(g->member[i].position == idx && g->member[i].sd != NULL) + clif_charnameupdate(g->member[i].sd); + return 0; +} + +/*==================================================== + * Change guild notice + *---------------------------------------------------*/ +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2) +{ + nullpo_ret(sd); + + if(guild_id!=sd->status.guild_id) + return 0; + return intif_guild_notice(guild_id,mes1,mes2); +} + +/*==================================================== + * Notification of guild has changed his notice + *---------------------------------------------------*/ +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,MAX_GUILDMES1); + memcpy(g->mes2,mes2,MAX_GUILDMES2); + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL) + clif_guild_notice(sd,g); + } + return 0; +} + +/*==================================================== + * Change guild emblem + *---------------------------------------------------*/ +int guild_change_emblem(struct map_session_data *sd,int len,const char *data) +{ + struct guild *g; + nullpo_ret(sd); + + if (battle_config.require_glory_guild && + !((g = guild_search(sd->status.guild_id)) && guild_checkskill(g, GD_GLORYGUILD)>0)) { + clif_skill_fail(sd,GD_GLORYGUILD,USESKILL_FAIL_LEVEL,0); + return 0; + } + + return intif_guild_emblem(sd->status.guild_id,len,data); +} + +/*==================================================== + * Notification of guild emblem changed + *---------------------------------------------------*/ +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id=emblem_id; + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL){ + sd->guild_emblem_id=emblem_id; + clif_guild_belonginfo(sd,g); + clif_guild_emblem(sd,g); + clif_guild_emblem_area(&sd->bl); + } + } + {// update guardians (mobs) + DBIterator* iter = db_iterator(castle_db); + struct guild_castle* gc; + for( gc = (struct guild_castle*)dbi_first(iter) ; dbi_exists(iter); gc = (struct guild_castle*)dbi_next(iter) ) + { + if( gc->guild_id != guild_id ) + continue; + // update permanent guardians + for( i = 0; i < ARRAYLENGTH(gc->guardian); ++i ) + { + TBL_MOB* md = (gc->guardian[i].id ? map_id2md(gc->guardian[i].id) : NULL); + if( md == NULL || md->guardian_data == NULL ) + continue; + md->guardian_data->emblem_id = emblem_id; + clif_guild_emblem_area(&md->bl); + } + // update temporary guardians + for( i = 0; i < gc->temp_guardians_max; ++i ) + { + TBL_MOB* md = (gc->temp_guardians[i] ? map_id2md(gc->temp_guardians[i]) : NULL); + if( md == NULL || md->guardian_data == NULL ) + continue; + md->guardian_data->emblem_id = emblem_id; + clif_guild_emblem_area(&md->bl); + } + } + dbi_destroy(iter); + } + {// update npcs (flags or other npcs that used flagemblem to attach to this guild) + for( i = 0; i < guild_flags_count; i++ ) { + if( guild_flags[i] && guild_flags[i]->u.scr.guild_id == guild_id ) { + clif_guild_emblem_area(&guild_flags[i]->bl); + } + } + } + return 0; +} + +/** + * @see DBCreateData + */ +static DBData create_expcache(DBKey key, va_list args) +{ + struct guild_expcache *c; + struct map_session_data *sd = va_arg(args, struct map_session_data*); + + c = ers_alloc(expcache_ers, struct guild_expcache); + c->guild_id = sd->status.guild_id; + c->account_id = sd->status.account_id; + c->char_id = sd->status.char_id; + c->exp = 0; + return db_ptr2data(c); +} + +/*==================================================== + * Return taxed experience from player sd to guild + *---------------------------------------------------*/ +unsigned int guild_payexp(struct map_session_data *sd,unsigned int exp) +{ + struct guild *g; + struct guild_expcache *c; + int per; + + nullpo_ret(sd); + + if (!exp) return 0; + + if (sd->status.guild_id == 0 || + (g = guild_search(sd->status.guild_id)) == NULL || + (per = guild_getposition(g,sd)) < 0 || + (per = g->position[per].exp_mode) < 1) + return 0; + + + if (per < 100) + exp = exp * per / 100; + //Otherwise tax everything. + + c = db_data2ptr(guild_expcache_db->ensure(guild_expcache_db, db_i2key(sd->status.char_id), create_expcache, sd)); + + if (c->exp > UINT64_MAX - exp) + c->exp = UINT64_MAX; + else + c->exp += exp; + + return exp; +} + +/*==================================================== + * Player sd pay a tribute experience to his guild + * Add this experience to guild exp + * [Celest] + *---------------------------------------------------*/ +int guild_getexp(struct map_session_data *sd,int exp) +{ + struct guild_expcache *c; + nullpo_ret(sd); + + if (sd->status.guild_id == 0 || guild_search(sd->status.guild_id) == NULL) + return 0; + + c = db_data2ptr(guild_expcache_db->ensure(guild_expcache_db, db_i2key(sd->status.char_id), create_expcache, sd)); + if (c->exp > UINT64_MAX - exp) + c->exp = UINT64_MAX; + else + c->exp += exp; + return exp; +} + +/*==================================================== + * Ask to increase guildskill skill_id + *---------------------------------------------------*/ +int guild_skillup(TBL_PC* sd, uint16 skill_id) +{ + struct guild* g; + int idx = skill_id - GD_SKILLBASE; + int max = guild_skill_get_max(skill_id); + + nullpo_ret(sd); + + if( idx < 0 || idx >= MAX_GUILDSKILL || // not a guild skill + sd->status.guild_id == 0 || (g=guild_search(sd->status.guild_id)) == NULL || // no guild + strcmp(sd->status.name, g->master) ) // not the guild master + return 0; + + if( g->skill_point > 0 && + g->skill[idx].id != 0 && + g->skill[idx].lv < max ) + intif_guild_skillup(g->guild_id, skill_id, sd->status.account_id, max); + + return 0; +} + +/*==================================================== + * Notification of guildskill skill_id increase request + *---------------------------------------------------*/ +int guild_skillupack(int guild_id,uint16 skill_id,int account_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct guild *g=guild_search(guild_id); + int i; + if(g==NULL) + return 0; + if( sd != NULL ) { + clif_guild_skillup(sd,skill_id,g->skill[skill_id-GD_SKILLBASE].lv); + + /* Guild Aura handling */ + switch( skill_id ) { + case GD_LEADERSHIP: + case GD_GLORYWOUNDS: + case GD_SOULCOLD: + case GD_HAWKEYES: + guild_guildaura_refresh(sd,skill_id,g->skill[skill_id-GD_SKILLBASE].lv); + break; + } + } + + // Inform all members + for(i=0;i<g->max_member;i++) + if((sd=g->member[i].sd)!=NULL) + clif_guild_skillinfo(sd); + + return 0; +} + +void guild_guildaura_refresh(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv) { + struct skill_unit_group* group = NULL; + int type = status_skill2sc(skill_id); + if( !(battle_config.guild_aura&((agit_flag || agit2_flag)?2:1)) && + !(battle_config.guild_aura&(map_flag_gvg2(sd->bl.m)?8:4)) ) + return; + if( !skill_lv ) + return; + if( sd->sc.data[type] && (group = skill_id2group(sd->sc.data[type]->val4)) ) { + skill_delunitgroup(group); + status_change_end(&sd->bl,type,INVALID_TIMER); + } + group = skill_unitsetting(&sd->bl,skill_id,skill_lv,sd->bl.x,sd->bl.y,0); + if( group ) { + sc_start4(&sd->bl,type,100,(battle_config.guild_aura&16)?0:skill_lv,0,0,group->group_id,600000);//duration doesn't matter these status never end with val4 + } + return; +} + +/*==================================================== + * Count number of relations the guild has. + * Flag: + * 0 = allied + * 1 = enemy + *---------------------------------------------------*/ +int guild_get_alliance_count(struct guild *g,int flag) +{ + int i,c; + + nullpo_ret(g); + + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id>0 && + g->alliance[i].opposition==flag ) + c++; + } + return c; +} + +// Blocks all guild skills which have a common delay time. +void guild_block_skill(struct map_session_data *sd, int time) +{ + uint16 skill_id[] = { GD_BATTLEORDER, GD_REGENERATION, GD_RESTORE, GD_EMERGENCYCALL }; + int i; + for (i = 0; i < 4; i++) + skill_blockpc_start_(sd, skill_id[i], time , true); +} + +/*==================================================== + * Check relation between guild_id1 and guild_id2. + * Flag: + * 0 = allied + * 1 = enemy + * Returns true if yes. + *---------------------------------------------------*/ +int guild_check_alliance(int guild_id1, int guild_id2, int flag) +{ + struct guild *g; + int i; + + g = guild_search(guild_id1); + if (g == NULL) + return 0; + + ARR_FIND( 0, MAX_GUILDALLIANCE, i, g->alliance[i].guild_id == guild_id2 && g->alliance[i].opposition == flag ); + return( i < MAX_GUILDALLIANCE ) ? 1 : 0; +} + +/*==================================================== + * Player sd, asking player tsd an alliance between their 2 guilds + *---------------------------------------------------*/ +int guild_reqalliance(struct map_session_data *sd,struct map_session_data *tsd) +{ + struct guild *g[2]; + int i; + + if(agit_flag || agit2_flag) { // Disable alliance creation during woe [Valaris] + clif_displaymessage(sd->fd,msg_txt(676)); //"Alliances cannot be made during Guild Wars!" + return 0; + } // end addition [Valaris] + + + nullpo_ret(sd); + + if(tsd==NULL || tsd->status.guild_id<=0) + return 0; + + g[0]=guild_search(sd->status.guild_id); + g[1]=guild_search(tsd->status.guild_id); + + if(g[0]==NULL || g[1]==NULL) + return 0; + + // Prevent creation alliance with same guilds [LuzZza] + if(sd->status.guild_id == tsd->status.guild_id) + return 0; + + if( guild_get_alliance_count(g[0],0) >= battle_config.max_guild_alliance ) { + clif_guild_allianceack(sd,4); + return 0; + } + if( guild_get_alliance_count(g[1],0) >= battle_config.max_guild_alliance ) { + clif_guild_allianceack(sd,3); + return 0; + } + + if( tsd->guild_alliance>0 ){ + clif_guild_allianceack(sd,1); + return 0; + } + + for (i = 0; i < MAX_GUILDALLIANCE; i++) { // check if already allied + if( g[0]->alliance[i].guild_id==tsd->status.guild_id && + g[0]->alliance[i].opposition==0){ + clif_guild_allianceack(sd,0); + return 0; + } + } + + tsd->guild_alliance=sd->status.guild_id; + tsd->guild_alliance_account=sd->status.account_id; + + clif_guild_reqalliance(tsd,sd->status.account_id,g[0]->name); + return 0; +} + +/*==================================================== + * Player sd, answer to player tsd (account_id) for an alliance request + *---------------------------------------------------*/ +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag) +{ + struct map_session_data *tsd; + + nullpo_ret(sd); + tsd= map_id2sd( account_id ); + if (!tsd) { //Character left? Cancel alliance. + clif_guild_allianceack(sd,3); + return 0; + } + + if (sd->guild_alliance != tsd->status.guild_id) // proposed guild_id alliance doesn't match tsd guildid + return 0; + + if (flag == 1) { // consent + int i; + + struct guild *g, *tg; // Reconfirm the number of alliance + g=guild_search(sd->status.guild_id); + tg=guild_search(tsd->status.guild_id); + + if(g==NULL || guild_get_alliance_count(g,0) >= battle_config.max_guild_alliance){ + clif_guild_allianceack(sd,4); + clif_guild_allianceack(tsd,3); + return 0; + } + if(tg==NULL || guild_get_alliance_count(tg,0) >= battle_config.max_guild_alliance){ + clif_guild_allianceack(sd,3); + clif_guild_allianceack(tsd,4); + return 0; + } + + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if(g->alliance[i].guild_id==tsd->status.guild_id && + g->alliance[i].opposition==1) + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,9 ); + } + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if(tg->alliance[i].guild_id==sd->status.guild_id && + tg->alliance[i].opposition==1) + intif_guild_alliance( tsd->status.guild_id,sd->status.guild_id, + tsd->status.account_id,sd->status.account_id,9 ); + } + + // inform other servers + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,0 ); + return 0; + } else { // deny + sd->guild_alliance=0; + sd->guild_alliance_account=0; + if(tsd!=NULL) + clif_guild_allianceack(tsd,3); + } + return 0; +} + +/*==================================================== + * Player sd asking to break alliance with guild guild_id + *---------------------------------------------------*/ +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + nullpo_ret(sd); + + if(agit_flag || agit2_flag) { // Disable alliance breaking during woe [Valaris] + clif_displaymessage(sd->fd,msg_txt(677)); //"Alliances cannot be broken during Guild Wars!" + return 0; + } // end addition [Valaris] + + intif_guild_alliance( sd->status.guild_id,guild_id,sd->status.account_id,0,flag|8 ); + return 0; +} + +/*==================================================== + * Player sd, asking player tsd a formal enemy relation between their 2 guilds + *---------------------------------------------------*/ +int guild_opposition(struct map_session_data *sd,struct map_session_data *tsd) +{ + struct guild *g; + int i; + + nullpo_ret(sd); + + g=guild_search(sd->status.guild_id); + if(g==NULL || tsd==NULL) + return 0; + + // Prevent creation opposition with same guilds [LuzZza] + if(sd->status.guild_id == tsd->status.guild_id) + return 0; + + if( guild_get_alliance_count(g,1) >= battle_config.max_guild_alliance ) { + clif_guild_oppositionack(sd,1); + return 0; + } + + for (i = 0; i < MAX_GUILDALLIANCE; i++) { // checking relations + if(g->alliance[i].guild_id==tsd->status.guild_id){ + if (g->alliance[i].opposition == 1) { // check if not already hostile + clif_guild_oppositionack(sd,2); + return 0; + } + if(agit_flag || agit2_flag) // Prevent the changing of alliances to oppositions during WoE. + return 0; + //Change alliance to opposition. + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,8 ); + } + } + + // inform other serv + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,1 ); + return 0; +} + +/*==================================================== + * Notification of a relationship between 2 guilds + *---------------------------------------------------*/ +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2,int flag,const char *name1,const char *name2) +{ + struct guild *g[2]; + int guild_id[2]; + const char *guild_name[2]; + struct map_session_data *sd[2]; + int j,i; + + guild_id[0] = guild_id1; + guild_id[1] = guild_id2; + guild_name[0] = name1; + guild_name[1] = name2; + sd[0] = map_id2sd(account_id1); + sd[1] = map_id2sd(account_id2); + + g[0]=guild_search(guild_id1); + g[1]=guild_search(guild_id2); + + if(sd[0]!=NULL && (flag&0x0f)==0){ + sd[0]->guild_alliance=0; + sd[0]->guild_alliance_account=0; + } + + if (flag & 0x70) { // failure + for(i=0;i<2-(flag&1);i++) + if( sd[i]!=NULL ) + clif_guild_allianceack(sd[i],((flag>>4)==i+1)?3:4); + return 0; + } + + if (!(flag & 0x08)) { // new relationship + for(i=0;i<2-(flag&1);i++) + { + if(g[i]!=NULL) + { + ARR_FIND( 0, MAX_GUILDALLIANCE, j, g[i]->alliance[j].guild_id == 0 ); + if( j < MAX_GUILDALLIANCE ) + { + g[i]->alliance[j].guild_id=guild_id[1-i]; + memcpy(g[i]->alliance[j].name,guild_name[1-i],NAME_LENGTH); + g[i]->alliance[j].opposition=flag&1; + } + } + } + } else { // remove relationship + for(i=0;i<2-(flag&1);i++) + { + if(g[i]!=NULL) + { + ARR_FIND( 0, MAX_GUILDALLIANCE, j, g[i]->alliance[j].guild_id == guild_id[1-i] && g[i]->alliance[j].opposition == (flag&1) ); + if( j < MAX_GUILDALLIANCE ) + g[i]->alliance[j].guild_id = 0; + } + if (sd[i] != NULL) // notify players + clif_guild_delalliance(sd[i],guild_id[1-i],(flag&1)); + } + } + + if ((flag & 0x0f) == 0) { // alliance notification + if( sd[1]!=NULL ) + clif_guild_allianceack(sd[1],2); + } else if ((flag & 0x0f) == 1) { // enemy notification + if( sd[0]!=NULL ) + clif_guild_oppositionack(sd[0],0); + } + + + for (i = 0; i < 2 - (flag & 1); i++) { // Retransmission of the relationship list to all members + struct map_session_data *sd; + if(g[i]!=NULL) + for(j=0;j<g[i]->max_member;j++) + if((sd=g[i]->member[j].sd)!=NULL) + clif_guild_allianceinfo(sd); + } + return 0; +} + +/** + * Notification for the guild disbanded + * @see DBApply + */ +int guild_broken_sub(DBKey key, DBData *data, va_list ap) +{ + struct guild *g = db_data2ptr(data); + int guild_id=va_arg(ap,int); + int i,j; + struct map_session_data *sd=NULL; + + nullpo_ret(g); + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // Destroy all relationships + if(g->alliance[i].guild_id==guild_id){ + for(j=0;j<g->max_member;j++) + if( (sd=g->member[j].sd)!=NULL ) + clif_guild_delalliance(sd,guild_id,g->alliance[i].opposition); + intif_guild_alliance(g->guild_id, guild_id,0,0,g->alliance[i].opposition|8); + g->alliance[i].guild_id=0; + } + } + return 0; +} + +/** + * Invoked on Castles when a guild is broken. [Skotlex] + * @see DBApply + */ +int castle_guild_broken_sub(DBKey key, DBData *data, va_list ap) +{ + char name[EVENT_NAME_LENGTH]; + struct guild_castle *gc = db_data2ptr(data); + int guild_id = va_arg(ap, int); + + nullpo_ret(gc); + + if (gc->guild_id == guild_id) { + // We call castle_event::OnGuildBreak of all castles of the guild + // You can set all castle_events in the 'db/castle_db.txt' + safestrncpy(name, gc->castle_event, sizeof(name)); + npc_event_do(strcat(name, "::OnGuildBreak")); + + //Save the new 'owner', this should invoke guardian clean up and other such things. + guild_castledatasave(gc->castle_id, 1, 0); + } + return 0; +} + +//Invoked on /breakguild "Guild name" +int guild_broken(int guild_id,int flag) +{ + struct guild *g = guild_search(guild_id); + struct map_session_data *sd = NULL; + int i; + + if(flag!=0 || g==NULL) + return 0; + + for(i=0;i<g->max_member;i++){ // Destroy all relationships + if((sd=g->member[i].sd)!=NULL){ + if(sd->state.storage_flag == 2) + storage_guild_storage_quit(sd,1); + sd->status.guild_id=0; + clif_guild_broken(g->member[i].sd,0); + clif_charnameupdate(sd); // [LuzZza] + } + } + + guild_db->foreach(guild_db,guild_broken_sub,guild_id); + castle_db->foreach(castle_db,castle_guild_broken_sub,guild_id); + guild_storage_delete(guild_id); + idb_remove(guild_db,guild_id); + return 0; +} + +//Changes the Guild Master to the specified player. [Skotlex] +int guild_gm_change(int guild_id, struct map_session_data *sd) +{ + struct guild *g; + nullpo_ret(sd); + + if (sd->status.guild_id != guild_id) + return 0; + + g=guild_search(guild_id); + + nullpo_ret(g); + + if (strcmp(g->master, sd->status.name) == 0) //Nothing to change. + return 0; + + //Notify servers that master has changed. + intif_guild_change_gm(guild_id, sd->status.name, strlen(sd->status.name)+1); + return 1; +} + +//Notification from Char server that a guild's master has changed. [Skotlex] +int guild_gm_changed(int guild_id, int account_id, int char_id) +{ + struct guild *g; + struct guild_member gm; + int pos, i; + + g=guild_search(guild_id); + + if (!g) + return 0; + + for(pos=0; pos<g->max_member && !( + g->member[pos].account_id==account_id && + g->member[pos].char_id==char_id); + pos++); + + if (pos == 0 || pos == g->max_member) return 0; + + 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. + strcpy(g->master, g->member[0].name); + + if (g->member[pos].sd && g->member[pos].sd->fd) + { + clif_displaymessage(g->member[pos].sd->fd, msg_txt(678)); //"You no longer are the Guild Master." + g->member[pos].sd->state.gmaster_flag = 0; + } + + if (g->member[0].sd && g->member[0].sd->fd) + { + clif_displaymessage(g->member[0].sd->fd, msg_txt(679)); //"You have become the Guild Master!" + g->member[0].sd->state.gmaster_flag = g; + //Block his skills for 5 minutes to prevent abuse. + guild_block_skill(g->member[0].sd, 300000); + } + + // announce the change to all guild members + for( i = 0; i < g->max_member; i++ ) + { + if( g->member[i].sd && g->member[i].sd->fd ) + { + clif_guild_basicinfo(g->member[i].sd); + clif_guild_memberlist(g->member[i].sd); + } + } + + return 1; +} + +/*==================================================== + * Guild disbanded + *---------------------------------------------------*/ +int guild_break(struct map_session_data *sd,char *name) +{ + struct guild *g; + int i; + + nullpo_ret(sd); + + if( (g=guild_search(sd->status.guild_id))==NULL ) + return 0; + if(strcmp(g->name,name)!=0) + return 0; + if(!sd->state.gmaster_flag) + return 0; + for(i=0;i<g->max_member;i++){ + if( g->member[i].account_id>0 && ( + g->member[i].account_id!=sd->status.account_id || + g->member[i].char_id!=sd->status.char_id )) + break; + } + if(i<g->max_member){ + clif_guild_broken(sd,2); + return 0; + } + + intif_guild_break(g->guild_id); + return 1; +} + +/** + * Creates a list of guild castle IDs to be requested + * from char-server. + */ +void guild_castle_map_init(void) +{ + DBIterator* iter = NULL; + int num = db_size(castle_db); + + if (num > 0) { + struct guild_castle* gc = NULL; + int *castle_ids, *cursor; + + CREATE(castle_ids, int, num); + cursor = castle_ids; + iter = db_iterator(castle_db); + for (gc = dbi_first(iter); dbi_exists(iter); gc = dbi_next(iter)) { + *(cursor++) = gc->castle_id; + } + dbi_destroy(iter); + if (intif_guild_castle_dataload(num, castle_ids)) + ShowStatus("Requested '"CL_WHITE"%d"CL_RESET"' guild castles from char-server...\n", num); + aFree(castle_ids); + } +} + +/** + * Setter function for members of guild_castle struct. + * Handles all side-effects, like updating guardians. + * Sends updated info to char-server for saving. + * @param castle_id Castle ID + * @param index Type of data to change + * @param value New value + */ +int guild_castledatasave(int castle_id, int index, int value) +{ + struct guild_castle *gc = guild_castle_search(castle_id); + + if (gc == NULL) { + ShowWarning("guild_castledatasave: guild castle '%d' not found\n", castle_id); + return 0; + } + + switch (index) { + case 1: // The castle's owner has changed? Update or remove Guardians too. [Skotlex] + { + int i; + struct mob_data *gd; + gc->guild_id = value; + for (i = 0; i < MAX_GUARDIANS; i++) + if (gc->guardian[i].visible && (gd = map_id2md(gc->guardian[i].id)) != NULL) + mob_guardian_guildchange(gd); + break; + } + case 2: + gc->economy = value; break; + case 3: // defense invest change -> recalculate guardian hp + { + int i; + struct mob_data *gd; + gc->defense = value; + for (i = 0; i < MAX_GUARDIANS; i++) + if (gc->guardian[i].visible && (gd = map_id2md(gc->guardian[i].id)) != NULL) + status_calc_mob(gd, 0); + 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; + default: + if (index > 9 && index <= 9+MAX_GUARDIANS) { + gc->guardian[index-10].visible = value; + break; + } + ShowWarning("guild_castledatasave: index = '%d' is out of allowed range\n", index); + return 0; + } + + if (!intif_guild_castle_datasave(castle_id, index, value)) { + guild_castle_reconnect(castle_id, index, value); + } + return 0; +} + +void guild_castle_reconnect_sub(void *key, void *data, va_list ap) +{ + int castle_id = GetWord((int)__64BPRTSIZE(key), 0); + int index = GetWord((int)__64BPRTSIZE(key), 1); + intif_guild_castle_datasave(castle_id, index, *(int *)data); + aFree(data); +} + +/** + * Saves pending guild castle data changes when char-server is + * disconnected. + * On reconnect pushes all changes to char-server for saving. + */ +void guild_castle_reconnect(int castle_id, int index, int value) +{ + static struct linkdb_node *gc_save_pending = NULL; + + if (castle_id < 0) { // char-server reconnected + linkdb_foreach(&gc_save_pending, guild_castle_reconnect_sub); + linkdb_final(&gc_save_pending); + } else { + int *data; + CREATE(data, int, 1); + *data = value; + linkdb_replace(&gc_save_pending, (void*)__64BPRTSIZE((MakeDWord(castle_id, index))), data); + } +} + +// Load castle data then invoke OnAgitInit* on last +int guild_castledataloadack(int len, struct guild_castle *gc) +{ + int i; + int n = (len-4) / sizeof(struct guild_castle); + int ev; + + nullpo_ret(gc); + + //Last owned castle in the list invokes ::OnAgitInit + for( i = n-1; i >= 0 && !(gc[i].guild_id); --i ); + ev = i; // offset of castle or -1 + + if( ev < 0 ) { //No castles owned, invoke OnAgitInit as it is. + npc_event_doall("OnAgitInit"); + npc_event_doall("OnAgitInit2"); + } + else // load received castles into memory, one by one + for( i = 0; i < n; i++, gc++ ) + { + struct guild_castle *c = guild_castle_search(gc->castle_id); + if (!c) { + ShowError("guild_castledataloadack: castle id=%d not found.\n", gc->castle_id); + continue; + } + + // update map-server castle data with new info + memcpy(&c->guild_id, &gc->guild_id, sizeof(struct guild_castle) - offsetof(struct guild_castle, guild_id)); + + if( c->guild_id ) + { + if( i != ev ) + guild_request_info(c->guild_id); + else { // last owned one + guild_npc_request_info(c->guild_id, "::OnAgitInit"); + guild_npc_request_info(c->guild_id, "::OnAgitInit2"); + } + } + } + ShowStatus("Received '"CL_WHITE"%d"CL_RESET"' guild castles from char-server.\n", n); + return 0; +} + +/*==================================================== + * Start normal woe and triggers all npc OnAgitStart + *---------------------------------------------------*/ +void guild_agit_start(void) +{ // Run All NPC_Event[OnAgitStart] + int c = npc_event_doall("OnAgitStart"); + ShowStatus("NPC_Event:[OnAgitStart] Run (%d) Events by @AgitStart.\n",c); +} + +/*==================================================== + * End normal woe and triggers all npc OnAgitEnd + *---------------------------------------------------*/ +void guild_agit_end(void) +{ // Run All NPC_Event[OnAgitEnd] + int c = npc_event_doall("OnAgitEnd"); + ShowStatus("NPC_Event:[OnAgitEnd] Run (%d) Events by @AgitEnd.\n",c); +} + +/*==================================================== + * Start woe2 and triggers all npc OnAgitStart2 + *---------------------------------------------------*/ +void guild_agit2_start(void) +{ // Run All NPC_Event[OnAgitStart2] + int c = npc_event_doall("OnAgitStart2"); + ShowStatus("NPC_Event:[OnAgitStart2] Run (%d) Events by @AgitStart2.\n",c); +} + +/*==================================================== + * End woe2 and triggers all npc OnAgitEnd2 + *---------------------------------------------------*/ +void guild_agit2_end(void) +{ // Run All NPC_Event[OnAgitEnd2] + int c = npc_event_doall("OnAgitEnd2"); + ShowStatus("NPC_Event:[OnAgitEnd2] Run (%d) Events by @AgitEnd2.\n",c); +} + +// How many castles does this guild have? +int guild_checkcastles(struct guild *g) +{ + int nb_cas = 0; + struct guild_castle* gc = NULL; + DBIterator *iter = db_iterator(castle_db); + + for (gc = dbi_first(iter); dbi_exists(iter); gc = dbi_next(iter)) { + if (gc->guild_id == g->guild_id) { + nb_cas++; + } + } + dbi_destroy(iter); + return nb_cas; +} + +// Are these two guilds allied? +bool guild_isallied(int guild_id, int guild_id2) +{ + int i; + struct guild* g = guild_search(guild_id); + nullpo_ret(g); + + ARR_FIND( 0, MAX_GUILDALLIANCE, i, g->alliance[i].guild_id == guild_id2 ); + return( i < MAX_GUILDALLIANCE && g->alliance[i].opposition == 0 ); +} + +void guild_flag_add(struct npc_data *nd) { + int i; + + /* check */ + for( i = 0; i < guild_flags_count; i++ ) { + if( guild_flags[i] && guild_flags[i]->bl.id == nd->bl.id ) { + return;/* exists, most likely updated the id. */ + } + } + + i = guild_flags_count;/* save the current slot */ + /* add */ + RECREATE(guild_flags,struct npc_data*,++guild_flags_count); + /* save */ + guild_flags[i] = nd; +} + +void guild_flag_remove(struct npc_data *nd) { + int i, cursor; + if( guild_flags_count == 0 ) + return; + /* find it */ + for( i = 0; i < guild_flags_count; i++ ) { + if( guild_flags[i] && guild_flags[i]->bl.id == nd->bl.id ) {/* found */ + guild_flags[i] = NULL; + break; + } + } + + /* compact list */ + for( i = 0, cursor = 0; i < guild_flags_count; i++ ) { + if( guild_flags[i] == NULL ) + continue; + + if( cursor != i ) { + memmove(&guild_flags[cursor], &guild_flags[i], sizeof(struct npc_data*)); + } + + cursor++; + } + +} + +/** + * @see DBApply + */ +static int eventlist_db_final(DBKey key, DBData *data, va_list ap) { + struct eventlist *next = NULL; + struct eventlist *current = db_data2ptr(data); + while (current != NULL) { + next = current->next; + aFree(current); + current = next; + } + return 0; +} + +/** + * @see DBApply + */ +static int guild_expcache_db_final(DBKey key, DBData *data, va_list ap) { + ers_free(expcache_ers, db_data2ptr(data)); + return 0; +} + +/** + * @see DBApply + */ +static int guild_castle_db_final(DBKey key, DBData *data, va_list ap) { + struct guild_castle* gc = db_data2ptr(data); + if( gc->temp_guardians ) + aFree(gc->temp_guardians); + aFree(gc); + return 0; +} + +/* called when scripts are reloaded/unloaded */ +void guild_flags_clear(void) { + int i; + for( i = 0; i < guild_flags_count; i++ ) { + if( guild_flags[i] ) + guild_flags[i] = NULL; + } + + guild_flags_count = 0; +} + +void do_init_guild(void) { + guild_db = idb_alloc(DB_OPT_RELEASE_DATA); + castle_db = idb_alloc(DB_OPT_BASE); + guild_expcache_db = idb_alloc(DB_OPT_BASE); + guild_infoevent_db = idb_alloc(DB_OPT_BASE); + expcache_ers = ers_new(sizeof(struct guild_expcache),"guild.c::expcache_ers",ERS_OPT_NONE); + + guild_flags_count = 0; + + sv_readdb(db_path, "castle_db.txt", ',', 4, 5, -1, &guild_read_castledb); + + memset(guild_skill_tree,0,sizeof(guild_skill_tree)); + sv_readdb(db_path, "guild_skill_tree.txt", ',', 2+MAX_GUILD_SKILL_REQUIRE*2, 2+MAX_GUILD_SKILL_REQUIRE*2, -1, &guild_read_guildskill_tree_db); //guild skill tree [Komurka] + + add_timer_func_list(guild_payexp_timer,"guild_payexp_timer"); + add_timer_func_list(guild_send_xy_timer, "guild_send_xy_timer"); + add_timer_interval(gettick()+GUILD_PAYEXP_INVERVAL,guild_payexp_timer,0,0,GUILD_PAYEXP_INVERVAL); + add_timer_interval(gettick()+GUILD_SEND_XY_INVERVAL,guild_send_xy_timer,0,0,GUILD_SEND_XY_INVERVAL); +} + +void do_final_guild(void) { + + db_destroy(guild_db); + castle_db->destroy(castle_db,guild_castle_db_final); + guild_expcache_db->destroy(guild_expcache_db,guild_expcache_db_final); + guild_infoevent_db->destroy(guild_infoevent_db,eventlist_db_final); + ers_destroy(expcache_ers); + + aFree(guild_flags);/* never empty; created on boot */ +} diff --git a/src/map/guild.h b/src/map/guild.h new file mode 100644 index 000000000..0df93d138 --- /dev/null +++ b/src/map/guild.h @@ -0,0 +1,112 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GUILD_H_ +#define _GUILD_H_ + +//#include "../common/mmo.h" +struct guild; +struct guild_member; +struct guild_position; +struct guild_castle; +#include "map.h" // NAME_LENGTH +struct map_session_data; +struct mob_data; + +//For quick linking to a guardian's info. [Skotlex] +struct guardian_data { + int number; //0-MAX_GUARDIANS-1 = Guardians. MAX_GUARDIANS = Emperium. + int guild_id; + int emblem_id; + int guardup_lv; //Level of GD_GUARDUP skill. + char guild_name[NAME_LENGTH]; + struct guild_castle* castle; +}; + +int guild_skill_get_max(int id); + +int guild_checkskill(struct guild *g,int id); +int guild_check_skill_require(struct guild *g,int id); // [Komurka] +int guild_checkcastles(struct guild *g); // [MouseJstr] +bool guild_isallied(int guild_id, int guild_id2); //Checks alliance based on guild Ids. [Skotlex] + +void do_init_guild(void); +struct guild *guild_search(int guild_id); +struct guild *guild_searchname(char *str); +struct guild_castle *guild_castle_search(int gcid); + +struct guild_castle* guild_mapname2gc(const char* mapname); +struct guild_castle* guild_mapindex2gc(short mapindex); + +struct map_session_data *guild_getavailablesd(struct guild *g); +int guild_getindex(struct guild *g,int account_id,int char_id); +int guild_getposition(struct guild *g, struct map_session_data *sd); +unsigned int guild_payexp(struct map_session_data *sd,unsigned int exp); +int guild_getexp(struct map_session_data *sd,int exp); // [Celest] + +int guild_create(struct map_session_data *sd, const char *name); +int guild_created(int account_id,int guild_id); +int guild_request_info(int guild_id); +int guild_recv_noinfo(int guild_id); +int guild_recv_info(struct guild *sg); +int guild_npc_request_info(int guild_id,const char *ev); +int guild_invite(struct map_session_data *sd,struct map_session_data *tsd); +int guild_reply_invite(struct map_session_data *sd,int guild_id,int flag); +void guild_member_joined(struct map_session_data *sd); +int guild_member_added(int guild_id,int account_id,int char_id,int flag); +int guild_leave(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_member_withdraw(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes); +int guild_expulsion(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_skillup(struct map_session_data* sd, uint16 skill_id); +void guild_block_skill(struct map_session_data *sd, int time); +int guild_reqalliance(struct map_session_data *sd,struct map_session_data *tsd); +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag); +int guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2); +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2); +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +int guild_opposition(struct map_session_data *sd,struct map_session_data *tsd); +int guild_check_alliance(int guild_id1, int guild_id2, int flag); + +int guild_send_memberinfoshort(struct map_session_data *sd,int online); +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class_); +int guild_change_memberposition(int guild_id,int account_id,int char_id,short idx); +int guild_memberposition_changed(struct guild *g,int idx,int pos); +int guild_change_position(int guild_id,int idx,int mode,int exp_mode,const char *name); +int guild_position_changed(int guild_id,int idx,struct guild_position *p); +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2); +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2); +int guild_change_emblem(struct map_session_data *sd,int len,const char *data); +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data); +int guild_send_message(struct map_session_data *sd,const char *mes,int len); +int guild_recv_message(int guild_id,int account_id,const char *mes,int len); +int guild_send_dot_remove(struct map_session_data *sd); +int guild_skillupack(int guild_id,uint16 skill_id,int account_id); +int guild_break(struct map_session_data *sd,char *name); +int guild_broken(int guild_id,int flag); +int guild_gm_change(int guild_id, struct map_session_data *sd); +int guild_gm_changed(int guild_id, int account_id, int char_id); + +void guild_castle_map_init(void); +int guild_castledatasave(int castle_id,int index,int value); +int guild_castledataloadack(int len, struct guild_castle *gc); +void guild_castle_reconnect(int castle_id, int index, int value); + +void guild_agit_start(void); +void guild_agit_end(void); + +void guild_agit2_start(void); +void guild_agit2_end(void); +/* guild flag cachin */ +void guild_flag_add(struct npc_data *nd); +void guild_flag_remove(struct npc_data *nd); +void guild_flags_clear(void); + +void guild_guildaura_refresh(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); + +void do_final_guild(void); + +#endif /* _GUILD_H_ */ diff --git a/src/map/homunculus.c b/src/map/homunculus.c new file mode 100644 index 000000000..081287d8a --- /dev/null +++ b/src/map/homunculus.c @@ -0,0 +1,1284 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/mmo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "log.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" +#include "unit.h" + +#include "homunculus.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +struct s_homunculus_db homunculus_db[MAX_HOMUNCULUS_CLASS]; //[orn] +struct homun_skill_tree_entry hskill_tree[MAX_HOMUNCULUS_CLASS][MAX_SKILL_TREE]; + +static int merc_hom_hungry(int tid, unsigned int tick, int id, intptr_t data); + +static unsigned int hexptbl[MAX_LEVEL]; + +//For holding the view data of npc classes. [Skotlex] +static struct view_data hom_viewdb[MAX_HOMUNCULUS_CLASS]; + +struct view_data* merc_get_hom_viewdata(int class_) +{ //Returns the viewdata for homunculus + if (homdb_checkid(class_)) + return &hom_viewdb[class_-HM_CLASS_BASE]; + return NULL; +} + +int hom_class2mapid(int hom_class) +{ + switch(hom_class) + { + // Normal Homunculus + case 6001: case 6005: return MAPID_LIF; + case 6002: case 6006: return MAPID_AMISTR; + case 6003: case 6007: return MAPID_FILIR; + case 6004: case 6008: return MAPID_VANILMIRTH; + // Evolved Homunculus + case 6009: case 6013: return MAPID_LIF_E; + case 6010: case 6014: return MAPID_AMISTR_E; + case 6011: case 6015: return MAPID_FILIR_E; + case 6012: case 6016: return MAPID_VANILMIRTH_E; + // Homunculus S + case 6048: return MAPID_EIRA; + case 6049: return MAPID_BAYERI; + case 6050: return MAPID_SERA; + case 6051: return MAPID_DIETER; + case 6052: return MAPID_ELANOR; + + default: return -1; + } +} + +int hom_addspiritball(TBL_HOM *hd, int max) { + nullpo_ret(hd); + + if (max > MAX_SKILL_LEVEL) + max = MAX_SKILL_LEVEL; + if (hd->homunculus.spiritball < 0) + hd->homunculus.spiritball = 0; + + if (hd->homunculus.spiritball && hd->homunculus.spiritball >= max) { + hd->homunculus.spiritball = max; + } + else + hd->homunculus.spiritball++; + + clif_spiritball(&hd->bl); + + return 0; +} + +int hom_delspiritball(TBL_HOM *hd, int count, int type) { + nullpo_ret(hd); + + if (hd->homunculus.spiritball <= 0) { + hd->homunculus.spiritball = 0; + return 0; + } + if (count <= 0) + return 0; + if (count > MAX_SKILL_LEVEL) + count = MAX_SKILL_LEVEL; + if (count > hd->homunculus.spiritball) + count = hd->homunculus.spiritball; + + hd->homunculus.spiritball -= count; + if (!type) + clif_spiritball(&hd->bl); + + return 0; +} + +void merc_damage(struct homun_data *hd) { + clif_hominfo(hd->master,hd,0); +} + +int merc_hom_dead(struct homun_data *hd) +{ + //There's no intimacy penalties on death (from Tharis) + struct map_session_data *sd = hd->master; + + clif_emotion(&hd->bl, E_WAH); + + //Delete timers when dead. + merc_hom_hungry_timer_delete(hd); + hd->homunculus.hp = 0; + + if (!sd) //unit remove map will invoke unit free + return 3; + + clif_emotion(&sd->bl, E_SOB); + //Remove from map (if it has no intimacy, it is auto-removed from memory) + return 3; +} + +//Vaporize a character's homun. If flag, HP needs to be 80% or above. +int merc_hom_vaporize(struct map_session_data *sd, int flag) +{ + struct homun_data *hd; + + nullpo_ret(sd); + + hd = sd->hd; + if (!hd || hd->homunculus.vaporize) + return 0; + + if (status_isdead(&hd->bl)) + return 0; //Can't vaporize a dead homun. + + if (flag && get_percentage(hd->battle_status.hp, hd->battle_status.max_hp) < 80) + return 0; + + hd->regen.state.block = 3; //Block regen while vaporized. + //Delete timers when vaporized. + merc_hom_hungry_timer_delete(hd); + hd->homunculus.vaporize = 1; + if(battle_config.hom_setting&0x40) + memset(hd->blockskill, 0, sizeof(hd->blockskill)); + clif_hominfo(sd, sd->hd, 0); + merc_save(hd); + return unit_remove_map(&hd->bl, CLR_OUTSIGHT); +} + +//delete a homunculus, completely "killing it". +//Emote is the emotion the master should use, send negative to disable. +int merc_hom_delete(struct homun_data *hd, int emote) +{ + struct map_session_data *sd; + nullpo_ret(hd); + sd = hd->master; + + if (!sd) + return unit_free(&hd->bl,CLR_DEAD); + + if (emote >= 0) + clif_emotion(&sd->bl, emote); + + //This makes it be deleted right away. + hd->homunculus.intimacy = 0; + // Send homunculus_dead to client + hd->homunculus.hp = 0; + clif_hominfo(sd, hd, 0); + return unit_remove_map(&hd->bl,CLR_OUTSIGHT); +} + +int merc_hom_calc_skilltree(struct homun_data *hd, int flag_evolve) +{ + int i, id = 0; + int j, f = 1; + int c = 0; + + nullpo_ret(hd); + /* load previous homunculus form skills first. */ + if( hd->homunculus.prev_class != 0 ) { + c = hd->homunculus.prev_class - HM_CLASS_BASE; + + for( i = 0; i < MAX_SKILL_TREE && ( id = hskill_tree[c][i].id ) > 0; i++ ) { + if( hd->homunculus.hskill[ id - HM_SKILLBASE ].id ) + continue; //Skill already known. + if(!battle_config.skillfree) { + for( j = 0; j < MAX_PC_SKILL_REQUIRE; j++ ) { + if( hskill_tree[c][i].need[j].id && + merc_hom_checkskill(hd,hskill_tree[c][i].need[j].id) < hskill_tree[c][i].need[j].lv ) { + f = 0; + break; + } + } + } + if ( f ) + hd->homunculus.hskill[id-HM_SKILLBASE].id = id; + } + + f = 1; + } + + c = hd->homunculus.class_ - HM_CLASS_BASE; + + for( i = 0; i < MAX_SKILL_TREE && ( id = hskill_tree[c][i].id ) > 0; i++ ) { + if( hd->homunculus.hskill[ id - HM_SKILLBASE ].id ) + continue; //Skill already known. + j = ( flag_evolve ) ? 10 : hd->homunculus.intimacy; + if( j < hskill_tree[c][i].intimacylv ) + continue; + if(!battle_config.skillfree) { + for( j = 0; j < MAX_PC_SKILL_REQUIRE; j++ ) { + if( hskill_tree[c][i].need[j].id && + merc_hom_checkskill(hd,hskill_tree[c][i].need[j].id) < hskill_tree[c][i].need[j].lv ) { + f = 0; + break; + } + } + } + if ( f ) + hd->homunculus.hskill[id-HM_SKILLBASE].id = id; + } + + if( hd->master ) + clif_homskillinfoblock(hd->master); + return 0; +} + +int merc_hom_checkskill(struct homun_data *hd,uint16 skill_id) +{ + int i = skill_id - HM_SKILLBASE; + if(!hd) + return 0; + + if(hd->homunculus.hskill[i].id == skill_id) + return (hd->homunculus.hskill[i].lv); + + return 0; +} + +int merc_skill_tree_get_max(int id, int b_class){ + int i, skill_id; + b_class -= HM_CLASS_BASE; + for(i=0;(skill_id=hskill_tree[b_class][i].id)>0;i++) + if (id == skill_id) + return hskill_tree[b_class][i].max; + return skill_get_max(id); +} + +void merc_hom_skillup(struct homun_data *hd,uint16 skill_id) +{ + int i = 0 ; + nullpo_retv(hd); + + if(hd->homunculus.vaporize) + return; + + i = skill_id - HM_SKILLBASE; + if(hd->homunculus.skillpts > 0 && + hd->homunculus.hskill[i].id && + hd->homunculus.hskill[i].flag == SKILL_FLAG_PERMANENT && //Don't allow raising while you have granted skills. [Skotlex] + hd->homunculus.hskill[i].lv < merc_skill_tree_get_max(skill_id, hd->homunculus.class_) + ) + { + hd->homunculus.hskill[i].lv++; + hd->homunculus.skillpts-- ; + status_calc_homunculus(hd,0); + if (hd->master) { + clif_homskillup(hd->master, skill_id); + clif_hominfo(hd->master,hd,0); + clif_homskillinfoblock(hd->master); + } + } +} + +int merc_hom_levelup(struct homun_data *hd) +{ + struct s_homunculus *hom; + struct h_stats *min, *max; + int growth_str, growth_agi, growth_vit, growth_int, growth_dex, growth_luk ; + int growth_max_hp, growth_max_sp ; + char output[256] ; + int m_class; + + if((m_class = hom_class2mapid(hd->homunculus.class_)) == -1) { + ShowError("merc_hom_levelup: Invalid class %d. \n", hd->homunculus.class_); + return 0; + } + + if((m_class&HOM_REG) && (hd->homunculus.level >= battle_config.hom_max_level || ((m_class&HOM_S) && hd->homunculus.level >= battle_config.hom_S_max_level) || !hd->exp_next || hd->homunculus.exp < hd->exp_next)) + return 0; + + hom = &hd->homunculus; + hom->level++ ; + if (!(hom->level % 3)) + hom->skillpts++ ; //1 skillpoint each 3 base level + + hom->exp -= hd->exp_next ; + hd->exp_next = hexptbl[hom->level - 1] ; + + max = &hd->homunculusDB->gmax; + min = &hd->homunculusDB->gmin; + + growth_max_hp = rnd_value(min->HP, max->HP); + growth_max_sp = rnd_value(min->SP, max->SP); + growth_str = rnd_value(min->str, max->str); + growth_agi = rnd_value(min->agi, max->agi); + growth_vit = rnd_value(min->vit, max->vit); + growth_dex = rnd_value(min->dex, max->dex); + growth_int = rnd_value(min->int_,max->int_); + growth_luk = rnd_value(min->luk, max->luk); + + //Aegis discards the decimals in the stat growth values! + growth_str-=growth_str%10; + growth_agi-=growth_agi%10; + growth_vit-=growth_vit%10; + growth_dex-=growth_dex%10; + growth_int-=growth_int%10; + growth_luk-=growth_luk%10; + + hom->max_hp += growth_max_hp; + hom->max_sp += growth_max_sp; + hom->str += growth_str; + hom->agi += growth_agi; + hom->vit += growth_vit; + hom->dex += growth_dex; + hom->int_+= growth_int; + hom->luk += growth_luk; + + if ( battle_config.homunculus_show_growth ) { + sprintf(output, + "Growth: hp:%d sp:%d str(%.2f) agi(%.2f) vit(%.2f) int(%.2f) dex(%.2f) luk(%.2f) ", + growth_max_hp, growth_max_sp, + growth_str/10.0, growth_agi/10.0, growth_vit/10.0, + growth_int/10.0, growth_dex/10.0, growth_luk/10.0); + clif_disp_onlyself(hd->master,output,strlen(output)); + } + return 1 ; +} + +int merc_hom_change_class(struct homun_data *hd, short class_) +{ + int i; + i = search_homunculusDB_index(class_,HOMUNCULUS_CLASS); + if(i < 0) + return 0; + hd->homunculusDB = &homunculus_db[i]; + hd->homunculus.class_ = class_; + status_set_viewdata(&hd->bl, class_); + merc_hom_calc_skilltree(hd, 1); + return 1; +} + +int merc_hom_evolution(struct homun_data *hd) +{ + struct s_homunculus *hom; + struct h_stats *max, *min; + struct map_session_data *sd; + nullpo_ret(hd); + + if(!hd->homunculusDB->evo_class || hd->homunculus.class_ == hd->homunculusDB->evo_class) + { + clif_emotion(&hd->bl, E_SWT); + return 0 ; + } + sd = hd->master; + if (!sd) + return 0; + + if (!merc_hom_change_class(hd, hd->homunculusDB->evo_class)) { + ShowError("merc_hom_evolution: Can't evolve homunc from %d to %d", hd->homunculus.class_, hd->homunculusDB->evo_class); + return 0; + } + + //Apply evolution bonuses + hom = &hd->homunculus; + max = &hd->homunculusDB->emax; + min = &hd->homunculusDB->emin; + hom->max_hp += rnd_value(min->HP, max->HP); + hom->max_sp += rnd_value(min->SP, max->SP); + hom->str += 10*rnd_value(min->str, max->str); + hom->agi += 10*rnd_value(min->agi, max->agi); + hom->vit += 10*rnd_value(min->vit, max->vit); + hom->int_+= 10*rnd_value(min->int_,max->int_); + hom->dex += 10*rnd_value(min->dex, max->dex); + hom->luk += 10*rnd_value(min->luk, max->luk); + hom->intimacy = 500; + + unit_remove_map(&hd->bl, CLR_OUTSIGHT); + map_addblock(&hd->bl); + + clif_spawn(&hd->bl); + clif_emotion(&sd->bl, E_NO1); + clif_specialeffect(&hd->bl,568,AREA); + + //status_Calc flag&1 will make current HP/SP be reloaded from hom structure + hom->hp = hd->battle_status.hp; + hom->sp = hd->battle_status.sp; + status_calc_homunculus(hd,1); + + if (!(battle_config.hom_setting&0x2)) + skill_unit_move(&sd->hd->bl,gettick(),1); // apply land skills immediately + + return 1 ; +} + +int hom_mutate(struct homun_data *hd, int homun_id) +{ + struct s_homunculus *hom; + struct map_session_data *sd; + int m_class, m_id, prev_class = 0; + nullpo_ret(hd); + + m_class = hom_class2mapid(hd->homunculus.class_); + m_id = hom_class2mapid(homun_id); + + if( m_class == -1 || m_id == -1 || !(m_class&HOM_EVO) || !(m_id&HOM_S) ) { + clif_emotion(&hd->bl, E_SWT); + return 0; + } + + sd = hd->master; + if (!sd) + return 0; + + prev_class = hd->homunculus.class_; + + if (!merc_hom_change_class(hd, homun_id)) { + ShowError("hom_mutate: Can't evolve homunc from %d to %d", hd->homunculus.class_, homun_id); + return 0; + } + + unit_remove_map(&hd->bl, CLR_OUTSIGHT); + map_addblock(&hd->bl); + + clif_spawn(&hd->bl); + clif_emotion(&sd->bl, E_NO1); + clif_specialeffect(&hd->bl,568,AREA); + + + //status_Calc flag&1 will make current HP/SP be reloaded from hom structure + hom = &hd->homunculus; + hom->hp = hd->battle_status.hp; + hom->sp = hd->battle_status.sp; + hom->prev_class = prev_class; + status_calc_homunculus(hd,1); + + if (!(battle_config.hom_setting&0x2)) + skill_unit_move(&sd->hd->bl,gettick(),1); // apply land skills immediately + + return 1; +} + +int merc_hom_gainexp(struct homun_data *hd,int exp) +{ + int m_class; + + if(hd->homunculus.vaporize) + return 1; + + if((m_class = hom_class2mapid(hd->homunculus.class_)) == -1) { + ShowError("merc_hom_gainexp: Invalid class %d. \n", hd->homunculus.class_); + return 0; + } + + if( hd->exp_next == 0 || + ((m_class&HOM_REG) && hd->homunculus.level >= battle_config.hom_max_level) || + ((m_class&HOM_S) && hd->homunculus.level >= battle_config.hom_S_max_level) ) { + hd->homunculus.exp = 0; + return 0; + } + + hd->homunculus.exp += exp; + + if(hd->homunculus.exp < hd->exp_next) { + clif_hominfo(hd->master,hd,0); + return 0; + } + + //levelup + do + { + merc_hom_levelup(hd) ; + } + while(hd->homunculus.exp > hd->exp_next && hd->exp_next != 0 ); + + if( hd->exp_next == 0 ) + hd->homunculus.exp = 0 ; + + clif_specialeffect(&hd->bl,568,AREA); + status_calc_homunculus(hd,0); + status_percent_heal(&hd->bl, 100, 100); + return 0; +} + +// Return the new value +int merc_hom_increase_intimacy(struct homun_data * hd, unsigned int value) +{ + if (battle_config.homunculus_friendly_rate != 100) + value = (value * battle_config.homunculus_friendly_rate) / 100; + + if (hd->homunculus.intimacy + value <= 100000) + hd->homunculus.intimacy += value; + else + hd->homunculus.intimacy = 100000; + return hd->homunculus.intimacy; +} + +// Return 0 if decrease fails or intimacy became 0 else the new value +int merc_hom_decrease_intimacy(struct homun_data * hd, unsigned int value) +{ + if (hd->homunculus.intimacy >= value) + hd->homunculus.intimacy -= value; + else + hd->homunculus.intimacy = 0; + + return hd->homunculus.intimacy; +} + +void merc_hom_heal(struct homun_data *hd) { + clif_hominfo(hd->master,hd,0); +} + +void merc_save(struct homun_data *hd) +{ + // copy data that must be saved in homunculus struct ( hp / sp ) + TBL_PC * sd = hd->master; + //Do not check for max_hp/max_sp caps as current could be higher to max due + //to status changes/skills (they will be capped as needed upon stat + //calculation on login) + hd->homunculus.hp = hd->battle_status.hp; + hd->homunculus.sp = hd->battle_status.sp; + intif_homunculus_requestsave(sd->status.account_id, &hd->homunculus); +} + +int merc_menu(struct map_session_data *sd,int menunum) +{ + nullpo_ret(sd); + if (sd->hd == NULL) + return 1; + + switch(menunum) { + case 0: + break; + case 1: + merc_hom_food(sd, sd->hd); + break; + case 2: + merc_hom_delete(sd->hd, -1); + break; + default: + ShowError("merc_menu : unknown menu choice : %d\n", menunum) ; + break; + } + return 0; +} + +int merc_hom_food(struct map_session_data *sd, struct homun_data *hd) +{ + int i, foodID, emotion; + + if(hd->homunculus.vaporize) + return 1 ; + + foodID = hd->homunculusDB->foodID; + i = pc_search_inventory(sd,foodID); + if(i < 0) { + clif_hom_food(sd,foodID,0); + return 1; + } + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); + + if ( hd->homunculus.hunger >= 91 ) { + merc_hom_decrease_intimacy(hd, 50); + emotion = E_WAH; + } else if ( hd->homunculus.hunger >= 76 ) { + merc_hom_decrease_intimacy(hd, 5); + emotion = E_SWT2; + } else if ( hd->homunculus.hunger >= 26 ) { + merc_hom_increase_intimacy(hd, 75); + emotion = E_HO; + } else if ( hd->homunculus.hunger >= 11 ) { + merc_hom_increase_intimacy(hd, 100); + emotion = E_HO; + } else { + merc_hom_increase_intimacy(hd, 50); + emotion = E_HO; + } + + hd->homunculus.hunger += 10; //dunno increase value for each food + if(hd->homunculus.hunger > 100) + hd->homunculus.hunger = 100; + + clif_emotion(&hd->bl,emotion); + clif_send_homdata(sd,SP_HUNGRY,hd->homunculus.hunger); + clif_send_homdata(sd,SP_INTIMATE,hd->homunculus.intimacy / 100); + clif_hom_food(sd,foodID,1); + + // Too much food :/ + if(hd->homunculus.intimacy == 0) + return merc_hom_delete(sd->hd, E_OMG); + + return 0; +} + +static int merc_hom_hungry(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + struct homun_data *hd; + + sd=map_id2sd(id); + if(!sd) + return 1; + + if(!sd->status.hom_id || !(hd=sd->hd)) + return 1; + + if(hd->hungry_timer != tid){ + ShowError("merc_hom_hungry_timer %d != %d\n",hd->hungry_timer,tid); + return 0; + } + + hd->hungry_timer = INVALID_TIMER; + + hd->homunculus.hunger-- ; + if(hd->homunculus.hunger <= 10) { + clif_emotion(&hd->bl, E_AN); + } else if(hd->homunculus.hunger == 25) { + clif_emotion(&hd->bl, E_HMM); + } else if(hd->homunculus.hunger == 75) { + clif_emotion(&hd->bl, E_OK); + } + + if(hd->homunculus.hunger < 0) { + hd->homunculus.hunger = 0; + // Delete the homunculus if intimacy <= 100 + if ( !merc_hom_decrease_intimacy(hd, 100) ) + return merc_hom_delete(hd, E_OMG); + clif_send_homdata(sd,SP_INTIMATE,hd->homunculus.intimacy / 100); + } + + clif_send_homdata(sd,SP_HUNGRY,hd->homunculus.hunger); + hd->hungry_timer = add_timer(tick+hd->homunculusDB->hungryDelay,merc_hom_hungry,sd->bl.id,0); //simple Fix albator + return 0; +} + +int merc_hom_hungry_timer_delete(struct homun_data *hd) +{ + nullpo_ret(hd); + if(hd->hungry_timer != INVALID_TIMER) { + delete_timer(hd->hungry_timer,merc_hom_hungry); + hd->hungry_timer = INVALID_TIMER; + } + return 1; +} + +int merc_hom_change_name(struct map_session_data *sd,char *name) +{ + int i; + struct homun_data *hd; + nullpo_retr(1, sd); + + hd = sd->hd; + if (!merc_is_hom_active(hd)) + return 1; + if(hd->homunculus.rename_flag && !battle_config.hom_rename) + return 1; + + for(i=0;i<NAME_LENGTH && name[i];i++){ + if( !(name[i]&0xe0) || name[i]==0x7f) + return 1; + } + + return intif_rename_hom(sd, name); +} + +int merc_hom_change_name_ack(struct map_session_data *sd, char* name, int flag) +{ + struct homun_data *hd = sd->hd; + if (!merc_is_hom_active(hd)) return 0; + + normalize_name(name," ");//bugreport:3032 + + if ( !flag || !strlen(name) ) { + clif_displaymessage(sd->fd, msg_txt(280)); // You cannot use this name + return 0; + } + strncpy(hd->homunculus.name,name,NAME_LENGTH); + clif_charnameack (0,&hd->bl); + hd->homunculus.rename_flag = 1; + clif_hominfo(sd,hd,0); + return 1; +} + +int search_homunculusDB_index(int key,int type) +{ + int i; + + for(i=0;i<MAX_HOMUNCULUS_CLASS;i++) { + if(homunculus_db[i].base_class <= 0) + continue; + switch(type) { + case HOMUNCULUS_CLASS: + if(homunculus_db[i].base_class == key || + homunculus_db[i].evo_class == key) + return i; + break; + case HOMUNCULUS_FOOD: + if(homunculus_db[i].foodID == key) + return i; + break; + default: + return -1; + } + } + return -1; +} + +// Create homunc structure +int merc_hom_alloc(struct map_session_data *sd, struct s_homunculus *hom) +{ + struct homun_data *hd; + int i = 0; + + nullpo_retr(1, sd); + + Assert((sd->status.hom_id == 0 || sd->hd == 0) || sd->hd->master == sd); + + i = search_homunculusDB_index(hom->class_,HOMUNCULUS_CLASS); + if(i < 0) { + ShowError("merc_hom_alloc: unknown class [%d] for homunculus '%s', requesting deletion.\n", hom->class_, hom->name); + sd->status.hom_id = 0; + intif_homunculus_requestdelete(hom->hom_id); + return 1; + } + sd->hd = hd = (struct homun_data*)aCalloc(1,sizeof(struct homun_data)); + hd->bl.type = BL_HOM; + hd->bl.id = npc_get_new_npc_id(); + + hd->master = sd; + hd->homunculusDB = &homunculus_db[i]; + memcpy(&hd->homunculus, hom, sizeof(struct s_homunculus)); + hd->exp_next = hexptbl[hd->homunculus.level - 1]; + + status_set_viewdata(&hd->bl, hd->homunculus.class_); + status_change_init(&hd->bl); + unit_dataset(&hd->bl); + hd->ud.dir = sd->ud.dir; + + // Find a random valid pos around the player + hd->bl.m = sd->bl.m; + hd->bl.x = sd->bl.x; + hd->bl.y = sd->bl.y; + unit_calc_pos(&hd->bl, sd->bl.x, sd->bl.y, sd->ud.dir); + hd->bl.x = hd->ud.to_x; + hd->bl.y = hd->ud.to_y; + + map_addiddb(&hd->bl); + status_calc_homunculus(hd,1); + + hd->hungry_timer = INVALID_TIMER; + return 0; +} + +void merc_hom_init_timers(struct homun_data * hd) +{ + if (hd->hungry_timer == INVALID_TIMER) + hd->hungry_timer = add_timer(gettick()+hd->homunculusDB->hungryDelay,merc_hom_hungry,hd->master->bl.id,0); + hd->regen.state.block = 0; //Restore HP/SP block. +} + +int merc_call_homunculus(struct map_session_data *sd) +{ + struct homun_data *hd; + + if (!sd->status.hom_id) //Create a new homun. + return merc_create_homunculus_request(sd, HM_CLASS_BASE + rnd_value(0, 7)) ; + + // If homunc not yet loaded, load it + if (!sd->hd) + return intif_homunculus_requestload(sd->status.account_id, sd->status.hom_id); + + hd = sd->hd; + + if (!hd->homunculus.vaporize) + return 0; //Can't use this if homun wasn't vaporized. + + merc_hom_init_timers(hd); + hd->homunculus.vaporize = 0; + if (hd->bl.prev == NULL) + { //Spawn him + hd->bl.x = sd->bl.x; + hd->bl.y = sd->bl.y; + hd->bl.m = sd->bl.m; + map_addblock(&hd->bl); + clif_spawn(&hd->bl); + clif_send_homdata(sd,SP_ACK,0); + clif_hominfo(sd,hd,1); + clif_hominfo(sd,hd,0); // send this x2. dunno why, but kRO does that [blackhole89] + clif_homskillinfoblock(sd); + if (battle_config.slaves_inherit_speed&1) + status_calc_bl(&hd->bl, SCB_SPEED); + merc_save(hd); + } else + //Warp him to master. + unit_warp(&hd->bl,sd->bl.m, sd->bl.x, sd->bl.y,CLR_OUTSIGHT); + return 1; +} + +// Recv homunculus data from char server +int merc_hom_recv_data(int account_id, struct s_homunculus *sh, int flag) +{ + struct map_session_data *sd; + struct homun_data *hd; + + sd = map_id2sd(account_id); + if(!sd) + return 0; + if (sd->status.char_id != sh->char_id) + { + if (sd->status.hom_id == sh->hom_id) + sh->char_id = sd->status.char_id; //Correct char id. + else + return 0; + } + if(!flag) { // Failed to load + sd->status.hom_id = 0; + return 0; + } + + if (!sd->status.hom_id) //Hom just created. + sd->status.hom_id = sh->hom_id; + if (sd->hd) //uh? Overwrite the data. + memcpy(&sd->hd->homunculus, sh, sizeof(struct s_homunculus)); + else + merc_hom_alloc(sd, sh); + + hd = sd->hd; + if(hd && hd->homunculus.hp && !hd->homunculus.vaporize && hd->bl.prev == NULL && sd->bl.prev != NULL) + { + map_addblock(&hd->bl); + clif_spawn(&hd->bl); + clif_send_homdata(sd,SP_ACK,0); + clif_hominfo(sd,hd,1); + clif_hominfo(sd,hd,0); // send this x2. dunno why, but kRO does that [blackhole89] + clif_homskillinfoblock(sd); + merc_hom_init_timers(hd); + } + return 1; +} + +// Ask homunculus creation to char server +int merc_create_homunculus_request(struct map_session_data *sd, int class_) +{ + struct s_homunculus homun; + struct h_stats *base; + int i; + + nullpo_retr(1, sd); + + i = search_homunculusDB_index(class_,HOMUNCULUS_CLASS); + if(i < 0) return 0; + + memset(&homun, 0, sizeof(struct s_homunculus)); + //Initial data + strncpy(homun.name, homunculus_db[i].name, NAME_LENGTH-1); + homun.class_ = class_; + homun.level = 1; + homun.hunger = 32; //32% + homun.intimacy = 2100; //21/1000 + homun.char_id = sd->status.char_id; + + homun.hp = 10 ; + base = &homunculus_db[i].base; + homun.max_hp = base->HP; + homun.max_sp = base->SP; + homun.str = base->str *10; + homun.agi = base->agi *10; + homun.vit = base->vit *10; + homun.int_= base->int_*10; + homun.dex = base->dex *10; + homun.luk = base->luk *10; + + // Request homunculus creation + intif_homunculus_create(sd->status.account_id, &homun); + return 1; +} + +int merc_resurrect_homunculus(struct map_session_data* sd, unsigned char per, short x, short y) +{ + struct homun_data* hd; + nullpo_ret(sd); + + if (!sd->status.hom_id) + return 0; // no homunculus + + if (!sd->hd) //Load homun data; + return intif_homunculus_requestload(sd->status.account_id, sd->status.hom_id); + + hd = sd->hd; + + if (hd->homunculus.vaporize) + return 0; // vaporized homunculi need to be 'called' + + if (!status_isdead(&hd->bl)) + return 0; // already alive + + merc_hom_init_timers(hd); + + if (!hd->bl.prev) + { //Add it back to the map. + hd->bl.m = sd->bl.m; + hd->bl.x = x; + hd->bl.y = y; + map_addblock(&hd->bl); + clif_spawn(&hd->bl); + } + status_revive(&hd->bl, per, 0); + return 1; +} + +void merc_hom_revive(struct homun_data *hd, unsigned int hp, unsigned int sp) +{ + struct map_session_data *sd = hd->master; + hd->homunculus.hp = hd->battle_status.hp; + if (!sd) + return; + clif_send_homdata(sd,SP_ACK,0); + clif_hominfo(sd,hd,1); + clif_hominfo(sd,hd,0); + clif_homskillinfoblock(sd); +} + +void merc_reset_stats(struct homun_data *hd) +{ //Resets a homunc stats back to zero (but doesn't touches hunger or intimacy) + struct s_homunculus_db *db; + struct s_homunculus *hom; + struct h_stats *base; + hom = &hd->homunculus; + db = hd->homunculusDB; + base = &db->base; + hom->level = 1; + hom->hp = 10; + hom->max_hp = base->HP; + hom->max_sp = base->SP; + hom->str = base->str *10; + hom->agi = base->agi *10; + hom->vit = base->vit *10; + hom->int_= base->int_*10; + hom->dex = base->dex *10; + hom->luk = base->luk *10; + hom->exp = 0; + hd->exp_next = hexptbl[0]; + memset(&hd->homunculus.hskill, 0, sizeof hd->homunculus.hskill); + hd->homunculus.skillpts = 0; +} + +int merc_hom_shuffle(struct homun_data *hd) +{ + struct map_session_data *sd; + int lv, i, skillpts; + unsigned int exp; + struct s_skill b_skill[MAX_HOMUNSKILL]; + + if (!merc_is_hom_active(hd)) + return 0; + + sd = hd->master; + lv = hd->homunculus.level; + exp = hd->homunculus.exp; + memcpy(&b_skill, &hd->homunculus.hskill, sizeof(b_skill)); + skillpts = hd->homunculus.skillpts; + //Reset values to level 1. + merc_reset_stats(hd); + //Level it back up + for (i = 1; i < lv && hd->exp_next; i++){ + hd->homunculus.exp += hd->exp_next; + merc_hom_levelup(hd); + } + + if(hd->homunculus.class_ == hd->homunculusDB->evo_class) { + //Evolved bonuses + struct s_homunculus *hom = &hd->homunculus; + struct h_stats *max = &hd->homunculusDB->emax, *min = &hd->homunculusDB->emin; + hom->max_hp += rnd_value(min->HP, max->HP); + hom->max_sp += rnd_value(min->SP, max->SP); + hom->str += 10*rnd_value(min->str, max->str); + hom->agi += 10*rnd_value(min->agi, max->agi); + hom->vit += 10*rnd_value(min->vit, max->vit); + hom->int_+= 10*rnd_value(min->int_,max->int_); + hom->dex += 10*rnd_value(min->dex, max->dex); + hom->luk += 10*rnd_value(min->luk, max->luk); + } + + hd->homunculus.exp = exp; + memcpy(&hd->homunculus.hskill, &b_skill, sizeof(b_skill)); + hd->homunculus.skillpts = skillpts; + clif_homskillinfoblock(sd); + status_calc_homunculus(hd,0); + status_percent_heal(&hd->bl, 100, 100); + clif_specialeffect(&hd->bl,568,AREA); + + return 1; +} + +static bool read_homunculusdb_sub(char* str[], int columns, int current) +{ + int classid; + struct s_homunculus_db *db; + + //Base Class,Evo Class + classid = atoi(str[0]); + if (classid < HM_CLASS_BASE || classid > HM_CLASS_MAX) + { + ShowError("read_homunculusdb : Invalid class %d\n", classid); + return false; + } + db = &homunculus_db[current]; + db->base_class = classid; + classid = atoi(str[1]); + if (classid < HM_CLASS_BASE || classid > HM_CLASS_MAX) + { + db->base_class = 0; + ShowError("read_homunculusdb : Invalid class %d\n", classid); + return false; + } + db->evo_class = classid; + //Name, Food, Hungry Delay, Base Size, Evo Size, Race, Element, ASPD + strncpy(db->name,str[2],NAME_LENGTH-1); + db->foodID = atoi(str[3]); + db->hungryDelay = atoi(str[4]); + db->base_size = atoi(str[5]); + db->evo_size = atoi(str[6]); + db->race = atoi(str[7]); + db->element = atoi(str[8]); + db->baseASPD = atoi(str[9]); + //base HP, SP, str, agi, vit, int, dex, luk + db->base.HP = atoi(str[10]); + db->base.SP = atoi(str[11]); + db->base.str = atoi(str[12]); + db->base.agi = atoi(str[13]); + db->base.vit = atoi(str[14]); + db->base.int_= atoi(str[15]); + db->base.dex = atoi(str[16]); + db->base.luk = atoi(str[17]); + //Growth Min/Max HP, SP, str, agi, vit, int, dex, luk + db->gmin.HP = atoi(str[18]); + db->gmax.HP = atoi(str[19]); + db->gmin.SP = atoi(str[20]); + db->gmax.SP = atoi(str[21]); + db->gmin.str = atoi(str[22]); + db->gmax.str = atoi(str[23]); + db->gmin.agi = atoi(str[24]); + db->gmax.agi = atoi(str[25]); + db->gmin.vit = atoi(str[26]); + db->gmax.vit = atoi(str[27]); + db->gmin.int_= atoi(str[28]); + db->gmax.int_= atoi(str[29]); + db->gmin.dex = atoi(str[30]); + db->gmax.dex = atoi(str[31]); + db->gmin.luk = atoi(str[32]); + db->gmax.luk = atoi(str[33]); + //Evolution Min/Max HP, SP, str, agi, vit, int, dex, luk + db->emin.HP = atoi(str[34]); + db->emax.HP = atoi(str[35]); + db->emin.SP = atoi(str[36]); + db->emax.SP = atoi(str[37]); + db->emin.str = atoi(str[38]); + db->emax.str = atoi(str[39]); + db->emin.agi = atoi(str[40]); + db->emax.agi = atoi(str[41]); + db->emin.vit = atoi(str[42]); + db->emax.vit = atoi(str[43]); + db->emin.int_= atoi(str[44]); + db->emax.int_= atoi(str[45]); + db->emin.dex = atoi(str[46]); + db->emax.dex = atoi(str[47]); + db->emin.luk = atoi(str[48]); + db->emax.luk = atoi(str[49]); + + //Check that the min/max values really are below the other one. + if(db->gmin.HP > db->gmax.HP) + db->gmin.HP = db->gmax.HP; + if(db->gmin.SP > db->gmax.SP) + db->gmin.SP = db->gmax.SP; + if(db->gmin.str > db->gmax.str) + db->gmin.str = db->gmax.str; + if(db->gmin.agi > db->gmax.agi) + db->gmin.agi = db->gmax.agi; + if(db->gmin.vit > db->gmax.vit) + db->gmin.vit = db->gmax.vit; + if(db->gmin.int_> db->gmax.int_) + db->gmin.int_= db->gmax.int_; + if(db->gmin.dex > db->gmax.dex) + db->gmin.dex = db->gmax.dex; + if(db->gmin.luk > db->gmax.luk) + db->gmin.luk = db->gmax.luk; + + if(db->emin.HP > db->emax.HP) + db->emin.HP = db->emax.HP; + if(db->emin.SP > db->emax.SP) + db->emin.SP = db->emax.SP; + if(db->emin.str > db->emax.str) + db->emin.str = db->emax.str; + if(db->emin.agi > db->emax.agi) + db->emin.agi = db->emax.agi; + if(db->emin.vit > db->emax.vit) + db->emin.vit = db->emax.vit; + if(db->emin.int_> db->emax.int_) + db->emin.int_= db->emax.int_; + if(db->emin.dex > db->emax.dex) + db->emin.dex = db->emax.dex; + if(db->emin.luk > db->emax.luk) + db->emin.luk = db->emax.luk; + + return true; +} + +int read_homunculusdb(void) +{ + int i; + const char *filename[]={"homunculus_db.txt","homunculus_db2.txt"}; + + memset(homunculus_db,0,sizeof(homunculus_db)); + for(i = 0; i<ARRAYLENGTH(filename); i++) + { + char path[256]; + + if( i > 0 ) + { + sprintf(path, "%s/%s", db_path, filename[i]); + + if( !exists(path) ) + { + continue; + } + } + + sv_readdb(db_path, filename[i], ',', 50, 50, MAX_HOMUNCULUS_CLASS, &read_homunculusdb_sub); + } + + return 0; +} + +static bool read_homunculus_skilldb_sub(char* split[], int columns, int current) +{// <hom class>,<skill id>,<max level>[,<job level>],<req id1>,<req lv1>,<req id2>,<req lv2>,<req id3>,<req lv3>,<req id4>,<req lv4>,<req id5>,<req lv5>,<intimacy lv req> + int k, classid; + int j; + int minJobLevelPresent = 0; + + if( columns == 14 ) + minJobLevelPresent = 1; // MinJobLvl has been added + + // check for bounds [celest] + classid = atoi(split[0]) - HM_CLASS_BASE; + if ( classid >= MAX_HOMUNCULUS_CLASS ) + { + ShowWarning("read_homunculus_skilldb: Invalud homunculus class %d.\n", atoi(split[0])); + return false; + } + + k = atoi(split[1]); //This is to avoid adding two lines for the same skill. [Skotlex] + // Search an empty line or a line with the same skill_id (stored in j) + ARR_FIND( 0, MAX_SKILL_TREE, j, !hskill_tree[classid][j].id || hskill_tree[classid][j].id == k ); + if (j == MAX_SKILL_TREE) + { + ShowWarning("Unable to load skill %d into homunculus %d's tree. Maximum number of skills per class has been reached.\n", k, classid); + return false; + } + + hskill_tree[classid][j].id = k; + hskill_tree[classid][j].max = atoi(split[2]); + if (minJobLevelPresent) + hskill_tree[classid][j].joblv = atoi(split[3]); + + for( k = 0; k < MAX_PC_SKILL_REQUIRE; k++ ) + { + hskill_tree[classid][j].need[k].id = atoi(split[3+k*2+minJobLevelPresent]); + hskill_tree[classid][j].need[k].lv = atoi(split[3+k*2+minJobLevelPresent+1]); + } + + hskill_tree[classid][j].intimacylv = atoi(split[13+minJobLevelPresent]); + + return true; +} + +int read_homunculus_skilldb(void) +{ + memset(hskill_tree,0,sizeof(hskill_tree)); + sv_readdb(db_path, "homun_skill_tree.txt", ',', 13, 15, -1, &read_homunculus_skilldb_sub); + + return 0; +} + +void read_homunculus_expdb(void) +{ + FILE *fp; + char line[1024]; + int i, j=0; + char *filename[]={ + DBPATH"exp_homun.txt", + "exp_homun2.txt"}; + + memset(hexptbl,0,sizeof(hexptbl)); + for(i=0; i<2; i++){ + sprintf(line, "%s/%s", db_path, filename[i]); + fp=fopen(line,"r"); + if(fp == NULL){ + if(i != 0) + continue; + ShowError("can't read %s\n",line); + return; + } + while(fgets(line, sizeof(line), fp) && j < MAX_LEVEL) + { + if(line[0] == '/' && line[1] == '/') + continue; + + hexptbl[j] = strtoul(line, NULL, 10); + if (!hexptbl[j++]) + break; + } + if (hexptbl[MAX_LEVEL - 1]) // Last permitted level have to be 0! + { + ShowWarning("read_hexptbl: Reached max level in exp_homun [%d]. Remaining lines were not read.\n ", MAX_LEVEL); + hexptbl[MAX_LEVEL - 1] = 0; + } + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' levels in '"CL_WHITE"%s"CL_RESET"'.\n", j, filename[i]); + } +} + +void merc_reload(void) +{ + read_homunculusdb(); + read_homunculus_expdb(); +} + +void merc_skill_reload(void) +{ + read_homunculus_skilldb(); +} + +int do_init_merc(void) +{ + int class_; + read_homunculusdb(); + read_homunculus_expdb(); + read_homunculus_skilldb(); + // Add homunc timer function to timer func list [Toms] + add_timer_func_list(merc_hom_hungry, "merc_hom_hungry"); + + //Stock view data for homuncs + memset(&hom_viewdb, 0, sizeof(hom_viewdb)); + for (class_ = 0; class_ < ARRAYLENGTH(hom_viewdb); class_++) + hom_viewdb[class_].class_ = HM_CLASS_BASE+class_; + return 0; +} + +int do_final_merc(void); diff --git a/src/map/homunculus.h b/src/map/homunculus.h new file mode 100644 index 000000000..50e82eeac --- /dev/null +++ b/src/map/homunculus.h @@ -0,0 +1,132 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _HOMUNCULUS_H_ +#define _HOMUNCULUS_H_ + +#include "status.h" // struct status_data, struct status_change +#include "unit.h" // struct unit_data + +struct h_stats { + unsigned int HP, SP; + unsigned short str, agi, vit, int_, dex, luk; +}; + +struct s_homunculus_db { + int base_class, evo_class; + char name[NAME_LENGTH]; + struct h_stats base, gmin, gmax, emin, emax; + int foodID ; + int baseASPD ; + long hungryDelay ; + unsigned char element, race, base_size, evo_size; +}; + +extern struct s_homunculus_db homunculus_db[MAX_HOMUNCULUS_CLASS]; +enum { HOMUNCULUS_CLASS, HOMUNCULUS_FOOD }; + +enum { MH_MD_FIGHTING=1, MH_MD_GRAPPLING }; + +enum { + SP_ACK = 0x0, + SP_INTIMATE = 0x1, + SP_HUNGRY = 0x2, +}; + +struct homun_data { + struct block_list bl; + struct unit_data ud; + struct view_data *vd; + struct status_data base_status, battle_status; + struct status_change sc; + struct regen_data regen; + struct s_homunculus_db *homunculusDB; //[orn] + struct s_homunculus homunculus; //[orn] + + struct map_session_data *master; //pointer back to its master + int hungry_timer; //[orn] + unsigned int exp_next; + char blockskill[MAX_SKILL]; // [orn] +}; + +#define MAX_HOM_SKILL_REQUIRE 5 +struct homun_skill_tree_entry { + short id; + unsigned char max; + unsigned char joblv; + short intimacylv; + struct { + short id; + unsigned char lv; + } need[MAX_HOM_SKILL_REQUIRE]; +}; // Celest + +#define HOM_EVO 0x100 //256 +#define HOM_S 0x200 //512 + +#define HOM_REG 0x1000 //4096 + +enum { +// Normal Homunculus + MAPID_LIF = HOM_REG|0x0, + MAPID_AMISTR, + MAPID_FILIR, + MAPID_VANILMIRTH, +// Evolved Homunulus + MAPID_LIF_E = HOM_REG|HOM_EVO|0x0, + MAPID_AMISTR_E, + MAPID_FILIR_E, + MAPID_VANILMIRTH_E, +// Homunculus S + MAPID_EIRA = HOM_S|0x0, + MAPID_BAYERI, + MAPID_SERA, + MAPID_DIETER, + MAPID_ELANOR, +}; + +#define homdb_checkid(id) (id >= HM_CLASS_BASE && id <= HM_CLASS_MAX) + +// merc_is_hom_alive(struct homun_data *) +#define merc_is_hom_active(x) (x && x->homunculus.vaporize != 1 && x->battle_status.hp > 0) +int do_init_merc(void); +int merc_hom_recv_data(int account_id, struct s_homunculus *sh, int flag); //albator +struct view_data* merc_get_hom_viewdata(int class_); +int hom_class2mapid(int hom_class); +void merc_damage(struct homun_data *hd); +int merc_hom_dead(struct homun_data *hd); +void merc_hom_skillup(struct homun_data *hd,uint16 skill_id); +int merc_hom_calc_skilltree(struct homun_data *hd, int flag_evolve); +int merc_hom_checkskill(struct homun_data *hd,uint16 skill_id); +int merc_hom_gainexp(struct homun_data *hd,int exp); +int merc_hom_levelup(struct homun_data *hd); +int merc_hom_evolution(struct homun_data *hd); +int hom_mutate(struct homun_data *hd,int homun_id); +void merc_hom_heal(struct homun_data *hd); +int merc_hom_vaporize(struct map_session_data *sd, int flag); +int merc_resurrect_homunculus(struct map_session_data *sd, unsigned char per, short x, short y); +void merc_hom_revive(struct homun_data *hd, unsigned int hp, unsigned int sp); +void merc_reset_stats(struct homun_data *hd); +int merc_hom_shuffle(struct homun_data *hd); // [Zephyrus] +void merc_save(struct homun_data *hd); +int merc_call_homunculus(struct map_session_data *sd); +int merc_create_homunculus_request(struct map_session_data *sd, int class_); +int search_homunculusDB_index(int key,int type); +int merc_menu(struct map_session_data *sd,int menunum); +int merc_hom_food(struct map_session_data *sd, struct homun_data *hd); +int merc_hom_hungry_timer_delete(struct homun_data *hd); +int merc_hom_change_name(struct map_session_data *sd,char *name); +int merc_hom_change_name_ack(struct map_session_data *sd, char* name, int flag); +#define merc_stop_walking(hd, type) unit_stop_walking(&(hd)->bl, type) +#define merc_stop_attack(hd) unit_stop_attack(&(hd)->bl) +int merc_hom_increase_intimacy(struct homun_data * hd, unsigned int value); +int merc_hom_decrease_intimacy(struct homun_data * hd, unsigned int value); +int merc_skill_tree_get_max(int id, int b_class); +void merc_hom_init_timers(struct homun_data * hd); +void merc_skill_reload(void); +void merc_reload(void); + +int hom_addspiritball(TBL_HOM *hd, int max); +int hom_delspiritball(TBL_HOM *hd, int count, int type); + +#endif /* _HOMUNCULUS_H_ */ diff --git a/src/map/instance.c b/src/map/instance.c new file mode 100644 index 000000000..44a208866 --- /dev/null +++ b/src/map/instance.c @@ -0,0 +1,488 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/db.h" + +#include "clif.h" +#include "instance.h" +#include "map.h" +#include "npc.h" +#include "party.h" +#include "pc.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +int instance_start = 0; // To keep the last index + 1 of normal map inserted in the map[ARRAY] +struct s_instance instance[MAX_INSTANCE]; + + +/// Checks whether given instance id is valid or not. +static bool instance_is_valid(int instance_id) +{ + if( instance_id < 1 || instance_id >= ARRAYLENGTH(instance) ) + {// out of range + return false; + } + + if( instance[instance_id].state == INSTANCE_FREE ) + {// uninitialized/freed instance slot + return false; + } + + return true; +} + + +/*-------------------------------------- + * name : instance name + * Return value could be + * -4 = already exists | -3 = no free instances | -2 = party not found | -1 = invalid type + * On success return instance_id + *--------------------------------------*/ +int instance_create(int party_id, const char *name) +{ + int i; + struct party_data* p; + + if( ( p = party_search(party_id) ) == NULL ) + { + ShowError("instance_create: party %d not found for instance '%s'.\n", party_id, name); + return -2; + } + + if( p->instance_id ) + return -4; // Party already instancing + + // Searching a Free Instance + // 0 is ignored as this mean "no instance" on maps + ARR_FIND(1, MAX_INSTANCE, i, instance[i].state == INSTANCE_FREE); + if( i == MAX_INSTANCE ) + { + ShowError("instance_create: no free instances, consider increasing MAX_INSTANCE.\n"); + return -3; + } + + instance[i].state = INSTANCE_IDLE; + instance[i].instance_id = i; + instance[i].idle_timer = INVALID_TIMER; + instance[i].idle_timeout = instance[i].idle_timeoutval = 0; + instance[i].progress_timer = INVALID_TIMER; + instance[i].progress_timeout = 0; + instance[i].users = 0; + instance[i].party_id = party_id; + instance[i].vars = idb_alloc(DB_OPT_RELEASE_DATA); + + safestrncpy( instance[i].name, name, sizeof(instance[i].name) ); + memset( instance[i].map, 0x00, sizeof(instance[i].map) ); + p->instance_id = i; + + clif_instance(i, 1, 0); // Start instancing window + ShowInfo("[Instance] Created: %s.\n", name); + return i; +} + +/*-------------------------------------- + * Add a map to the instance using src map "name" + *--------------------------------------*/ +int instance_add_map(const char *name, int instance_id, bool usebasename) +{ + int16 m = map_mapname2mapid(name); + int i, im = -1; + size_t num_cell, size; + + if( m < 0 ) + return -1; // source map not found + + if( !instance_is_valid(instance_id) ) + { + ShowError("instance_add_map: trying to attach '%s' map to non-existing instance %d.\n", name, instance_id); + return -1; + } + if( instance[instance_id].num_map >= MAX_MAP_PER_INSTANCE ) + { + ShowError("instance_add_map: trying to add '%s' map to instance %d (%s) failed. Please increase MAX_MAP_PER_INSTANCE.\n", name, instance_id, instance[instance_id].name); + return -2; + } + if( map[m].instance_id != 0 ) + { // Source map already belong to a Instance. + ShowError("instance_add_map: trying to instance already instanced map %s.\n", name); + return -4; + } + + ARR_FIND( instance_start, map_num, i, !map[i].name[0] ); // Searching for a Free Map + if( i < map_num ) im = i; // Unused map found (old instance) + else if( map_num - 1 >= MAX_MAP_PER_SERVER ) + { // No more free maps + ShowError("instance_add_map: no more free space to create maps on this server.\n"); + return -5; + } + else im = map_num++; // Using next map index + + memcpy( &map[im], &map[m], sizeof(struct map_data) ); // Copy source map + snprintf(map[im].name, MAP_NAME_LENGTH, (usebasename ? "%.3d#%s" : "%.3d%s"), instance_id, name); // Generate Name for Instance Map + map[im].index = mapindex_addmap(-1, map[im].name); // Add map index + + if( !map[im].index ) + { + map[im].name[0] = '\0'; + ShowError("instance_add_map: no more free map indexes.\n"); + return -3; // No free map index + } + + // Reallocate cells + num_cell = map[im].xs * map[im].ys; + CREATE( map[im].cell, struct mapcell, num_cell ); + memcpy( map[im].cell, map[m].cell, num_cell * sizeof(struct mapcell) ); + + size = map[im].bxs * map[im].bys * sizeof(struct block_list*); + map[im].block = (struct block_list**)aCalloc(size, 1); + map[im].block_mob = (struct block_list**)aCalloc(size, 1); + + memset(map[im].npc, 0x00, sizeof(map[i].npc)); + map[im].npc_num = 0; + + memset(map[im].moblist, 0x00, sizeof(map[im].moblist)); + map[im].mob_delete_timer = INVALID_TIMER; + + map[im].m = im; + map[im].instance_id = instance_id; + map[im].instance_src_map = m; + map[m].flag.src4instance = 1; // Flag this map as a src map for instances + + instance[instance_id].map[instance[instance_id].num_map++] = im; // Attach to actual instance + map_addmap2db(&map[im]); + + return im; +} + +/*-------------------------------------- + * m : source map of this instance + * party_id : source party of this instance + * type : result (0 = map id | 1 = instance id) + *--------------------------------------*/ +int instance_map2imap(int16 m, int instance_id) +{ + int i; + + if( !instance_is_valid(instance_id) ) + { + return -1; + } + + for( i = 0; i < instance[instance_id].num_map; i++ ) + { + if( instance[instance_id].map[i] && map[instance[instance_id].map[i]].instance_src_map == m ) + return instance[instance_id].map[i]; + } + return -1; +} + +/*-------------------------------------- + * m : source map + * instance_id : where to search + * result : mapid of map "m" in this instance + *--------------------------------------*/ +int instance_mapid2imapid(int16 m, int instance_id) +{ + if( map[m].flag.src4instance == 0 ) + return m; // not instances found for this map + else if( map[m].instance_id ) + { // This map is a instance, not a src map instance + ShowError("map_instance_mapid2imapid: already instanced (%d / %d)\n", m, instance_id); + return -1; + } + + if( !instance_is_valid(instance_id) ) + return -1; + + return instance_map2imap(m, instance_id); +} + +/*-------------------------------------- + * map_instance_map_npcsub + * Used on Init instance. Duplicates each script on source map + *--------------------------------------*/ +int instance_map_npcsub(struct block_list* bl, va_list args) +{ + struct npc_data* nd = (struct npc_data*)bl; + int16 m = va_arg(args, int); // Destination Map + + npc_duplicate4instance(nd, m); + return 1; +} + +/*-------------------------------------- + * Init all map on the instance. Npcs are created here + *--------------------------------------*/ +void instance_init(int instance_id) +{ + int i; + + if( !instance_is_valid(instance_id) ) + return; // nothing to do + + for( i = 0; i < instance[instance_id].num_map; i++ ) + map_foreachinmap(instance_map_npcsub, map[instance[instance_id].map[i]].instance_src_map, BL_NPC, instance[instance_id].map[i]); + + instance[instance_id].state = INSTANCE_BUSY; + ShowInfo("[Instance] Initialized %s.\n", instance[instance_id].name); +} + +/*-------------------------------------- + * Used on instance deleting process. + * Warps all players on each instance map to its save points. + *--------------------------------------*/ +int instance_del_load(struct map_session_data* sd, va_list args) +{ + int16 m = va_arg(args,int); + if( !sd || sd->bl.m != m ) + return 0; + + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_OUTSIGHT); + return 1; +} + +/* for npcs behave differently when being unloaded within a instance */ +int instance_cleanup_sub(struct block_list *bl, va_list ap) { + nullpo_ret(bl); + + switch(bl->type) { + case BL_PC: + map_quit((struct map_session_data *) bl); + break; + case BL_NPC: + npc_unload((struct npc_data *)bl,true); + break; + case BL_MOB: + unit_free(bl,CLR_OUTSIGHT); + break; + case BL_PET: + //There is no need for this, the pet is removed together with the player. [Skotlex] + break; + case BL_ITEM: + map_clearflooritem(bl); + break; + case BL_SKILL: + skill_delunit((struct skill_unit *) bl); + break; + } + + return 1; +} + +/*-------------------------------------- + * Removes a simple instance map + *--------------------------------------*/ +void instance_del_map(int16 m) +{ + int i; + if( m <= 0 || !map[m].instance_id ) + { + ShowError("Tried to remove non-existing instance map (%d)\n", m); + return; + } + + map_foreachpc(instance_del_load, m); + map_foreachinmap(instance_cleanup_sub, m, BL_ALL); + + if( map[m].mob_delete_timer != INVALID_TIMER ) + delete_timer(map[m].mob_delete_timer, map_removemobs_timer); + + mapindex_removemap( map[m].index ); + + // Free memory + aFree(map[m].cell); + aFree(map[m].block); + aFree(map[m].block_mob); + + // Remove from instance + for( i = 0; i < instance[map[m].instance_id].num_map; i++ ) + { + if( instance[map[m].instance_id].map[i] == m ) + { + instance[map[m].instance_id].num_map--; + for( ; i < instance[map[m].instance_id].num_map; i++ ) + instance[map[m].instance_id].map[i] = instance[map[m].instance_id].map[i+1]; + i = -1; + break; + } + } + if( i == instance[map[m].instance_id].num_map ) + ShowError("map_instance_del: failed to remove %s from instance list (%s): %d\n", map[m].name, instance[map[m].instance_id].name, m); + + map_removemapdb(&map[m]); + memset(&map[m], 0x00, sizeof(map[0])); + + /* for it is default and makes it not try to delete a non-existent timer since we did not delete this entry. */ + map[m].mob_delete_timer = INVALID_TIMER; +} + +/*-------------------------------------- + * Timer to destroy instance by process or idle + *--------------------------------------*/ +int instance_destroy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + instance_destroy(id); + return 0; +} + +/*-------------------------------------- + * Removes a instance, all its maps and npcs. + *--------------------------------------*/ +void instance_destroy(int instance_id) +{ + int last = 0, type; + struct party_data *p; + time_t now = time(NULL); + + if( !instance_is_valid(instance_id) ) + return; // nothing to do + + if( instance[instance_id].progress_timeout && instance[instance_id].progress_timeout <= now ) + type = 1; + else if( instance[instance_id].idle_timeout && instance[instance_id].idle_timeout <= now ) + type = 2; + else + type = 3; + + clif_instance(instance_id, 5, type); // Report users this instance has been destroyed + + while( instance[instance_id].num_map && last != instance[instance_id].map[0] ) + { // Remove all maps from instance + last = instance[instance_id].map[0]; + instance_del_map( instance[instance_id].map[0] ); + } + + if( instance[instance_id].vars ) + db_destroy(instance[instance_id].vars); + + if( instance[instance_id].progress_timer != INVALID_TIMER ) + delete_timer( instance[instance_id].progress_timer, instance_destroy_timer); + if( instance[instance_id].idle_timer != INVALID_TIMER ) + delete_timer( instance[instance_id].idle_timer, instance_destroy_timer); + + instance[instance_id].vars = NULL; + + if( instance[instance_id].party_id && (p = party_search(instance[instance_id].party_id)) != NULL ) + p->instance_id = 0; // Update Party information + + ShowInfo("[Instance] Destroyed %s.\n", instance[instance_id].name); + memset( &instance[instance_id], 0x00, sizeof(instance[0]) ); + + instance[instance_id].state = INSTANCE_FREE; +} + +/*-------------------------------------- + * Checks if there are users in the instance or not to start idle timer + *--------------------------------------*/ +void instance_check_idle(int instance_id) +{ + bool idle = true; + time_t now = time(NULL); + + if( !instance_is_valid(instance_id) || instance[instance_id].idle_timeoutval == 0 ) + return; + + if( instance[instance_id].users ) + idle = false; + + if( instance[instance_id].idle_timer != INVALID_TIMER && !idle ) + { + delete_timer(instance[instance_id].idle_timer, instance_destroy_timer); + instance[instance_id].idle_timer = INVALID_TIMER; + instance[instance_id].idle_timeout = 0; + clif_instance(instance_id, 3, 0); // Notify instance users normal instance expiration + } + else if( instance[instance_id].idle_timer == INVALID_TIMER && idle ) + { + instance[instance_id].idle_timeout = now + instance[instance_id].idle_timeoutval; + instance[instance_id].idle_timer = add_timer( gettick() + (unsigned int)instance[instance_id].idle_timeoutval * 1000, instance_destroy_timer, instance_id, 0); + clif_instance(instance_id, 4, 0); // Notify instance users it will be destroyed of no user join it again in "X" time + } +} + +/*-------------------------------------- + * Set instance Timers + *--------------------------------------*/ +void instance_set_timeout(int instance_id, unsigned int progress_timeout, unsigned int idle_timeout) +{ + time_t now = time(0); + + if( !instance_is_valid(instance_id) ) + return; + + if( instance[instance_id].progress_timer != INVALID_TIMER ) + delete_timer( instance[instance_id].progress_timer, instance_destroy_timer); + if( instance[instance_id].idle_timer != INVALID_TIMER ) + delete_timer( instance[instance_id].idle_timer, instance_destroy_timer); + + if( progress_timeout ) + { + instance[instance_id].progress_timeout = now + progress_timeout; + instance[instance_id].progress_timer = add_timer( gettick() + progress_timeout * 1000, instance_destroy_timer, instance_id, 0); + } + else + { + instance[instance_id].progress_timeout = 0; + instance[instance_id].progress_timer = INVALID_TIMER; + } + + if( idle_timeout ) + { + instance[instance_id].idle_timeoutval = idle_timeout; + instance[instance_id].idle_timer = INVALID_TIMER; + instance_check_idle(instance_id); + } + else + { + instance[instance_id].idle_timeoutval = 0; + instance[instance_id].idle_timeout = 0; + instance[instance_id].idle_timer = INVALID_TIMER; + } + + if( instance[instance_id].idle_timer == INVALID_TIMER && instance[instance_id].progress_timer != INVALID_TIMER ) + clif_instance(instance_id, 3, 0); +} + +/*-------------------------------------- + * Checks if sd in on a instance and should be kicked from it + *--------------------------------------*/ +void instance_check_kick(struct map_session_data *sd) +{ + int16 m = sd->bl.m; + + clif_instance_leave(sd->fd); + if( map[m].instance_id ) + { // User was on the instance map + if( map[m].save.map ) + pc_setpos(sd, map[m].save.map, map[m].save.x, map[m].save.y, CLR_TELEPORT); + else + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); + } +} + +void do_final_instance(void) +{ + int i; + + for( i = 1; i < MAX_INSTANCE; i++ ) + instance_destroy(i); +} + +void do_init_instance(void) +{ + memset(instance, 0x00, sizeof(instance)); + add_timer_func_list(instance_destroy_timer, "instance_destroy_timer"); +} diff --git a/src/map/instance.h b/src/map/instance.h new file mode 100644 index 000000000..03b0d0898 --- /dev/null +++ b/src/map/instance.h @@ -0,0 +1,51 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INSTANCE_H_ +#define _INSTANCE_H_ + +#define MAX_MAP_PER_INSTANCE 10 +#define MAX_INSTANCE 500 + +#define INSTANCE_NAME_LENGTH (60+1) + +typedef enum instance_state { INSTANCE_FREE, INSTANCE_IDLE, INSTANCE_BUSY } instance_state; + +struct s_instance { + char name[INSTANCE_NAME_LENGTH]; // Instance Name - required for clif functions. + instance_state state; + short instance_id; + int party_id; + + int map[MAX_MAP_PER_INSTANCE]; + int num_map; + int users; + + struct DBMap* vars; // Instance Variable for scripts + + int progress_timer; + time_t progress_timeout; + + int idle_timer; + time_t idle_timeout, idle_timeoutval; +}; + +extern int instance_start; +extern struct s_instance instance[MAX_INSTANCE]; + +int instance_create(int party_id, const char *name); +int instance_add_map(const char *name, int instance_id, bool usebasename); +void instance_del_map(int16 m); +int instance_map2imap(int16 m, int instance_id); +int instance_mapid2imapid(int16 m, int instance_id); +void instance_destroy(int instance_id); +void instance_init(int instance_id); + +void instance_check_idle(int instance_id); +void instance_check_kick(struct map_session_data *sd); +void instance_set_timeout(int instance_id, unsigned int progress_timeout, unsigned int idle_timeout); + +void do_final_instance(void); +void do_init_instance(void); + +#endif diff --git a/src/map/intif.c b/src/map/intif.c new file mode 100644 index 000000000..9391e0275 --- /dev/null +++ b/src/map/intif.c @@ -0,0 +1,2267 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/showmsg.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/strlib.h" +#include "map.h" +#include "battle.h" +#include "chrif.h" +#include "clif.h" +#include "pc.h" +#include "intif.h" +#include "log.h" +#include "storage.h" +#include "party.h" +#include "guild.h" +#include "pet.h" +#include "atcommand.h" +#include "mercenary.h" +#include "homunculus.h" +#include "elemental.h" +#include "mail.h" +#include "quest.h" + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> + + +static const int packet_len_table[]={ + -1,-1,27,-1, -1, 0,37,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3800-0x380f + 0, 0, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, //0x3810 + 39,-1,15,15, 14,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820 + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, //0x3830 + -1, 0, 0,14, 0, 0, 0, 0, -1,74,-1,11, 11,-1, 0, 0, //0x3840 + -1,-1, 7, 7, 7,11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] + -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish] + -1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, 3, 0, //0x3870 Mercenaries [Zephyrus] / Elemental [pakpil] + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3880 + -1,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3890 Homunculus [albator] +}; + +extern int char_fd; // inter server Fd used for char_fd +#define inter_fd char_fd // alias + +//----------------------------------------------------------------- +// Send to inter server + +int CheckForCharServer(void) +{ + return ((char_fd <= 0) || session[char_fd] == NULL || session[char_fd]->wdata == NULL); +} + +// pet +int intif_create_pet(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) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 24 + NAME_LENGTH); + WFIFOW(inter_fd,0) = 0x3080; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOW(inter_fd,10) = pet_class; + WFIFOW(inter_fd,12) = pet_lv; + WFIFOW(inter_fd,14) = pet_egg_id; + WFIFOW(inter_fd,16) = pet_equip; + WFIFOW(inter_fd,18) = intimate; + WFIFOW(inter_fd,20) = hungry; + WFIFOB(inter_fd,22) = rename_flag; + WFIFOB(inter_fd,23) = incuvate; + memcpy(WFIFOP(inter_fd,24),pet_name,NAME_LENGTH); + WFIFOSET(inter_fd,24+NAME_LENGTH); + + return 0; +} + +int intif_request_petdata(int account_id,int char_id,int pet_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 14); + WFIFOW(inter_fd,0) = 0x3081; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOL(inter_fd,10) = pet_id; + WFIFOSET(inter_fd,14); + + return 0; +} + +int intif_save_petdata(int account_id,struct s_pet *p) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, sizeof(struct s_pet) + 8); + WFIFOW(inter_fd,0) = 0x3082; + WFIFOW(inter_fd,2) = sizeof(struct s_pet) + 8; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8),p,sizeof(struct s_pet)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + + return 0; +} + +int intif_delete_petdata(int pet_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3083; + WFIFOL(inter_fd,2) = pet_id; + WFIFOSET(inter_fd,6); + + return 1; +} + +int intif_rename(struct map_session_data *sd, int type, char *name) +{ + if (CheckForCharServer()) + return 1; + + WFIFOHEAD(inter_fd,NAME_LENGTH+12); + WFIFOW(inter_fd,0) = 0x3006; + WFIFOL(inter_fd,2) = sd->status.account_id; + WFIFOL(inter_fd,6) = sd->status.char_id; + WFIFOB(inter_fd,10) = type; //Type: 0 - PC, 1 - PET, 2 - HOM + memcpy(WFIFOP(inter_fd,11),name, NAME_LENGTH); + WFIFOSET(inter_fd,NAME_LENGTH+12); + return 0; +} + +// GM Send a message +int intif_broadcast(const char* mes, int len, int type) +{ + int lp = type ? 4 : 0; + + // Send to the local players + clif_broadcast(NULL, mes, len, type, ALL_CLIENT); + + if (CheckForCharServer()) + return 0; + + if (other_mapserver_count < 1) + return 0; //No need to send. + + WFIFOHEAD(inter_fd, 16 + lp + len); + WFIFOW(inter_fd,0) = 0x3000; + WFIFOW(inter_fd,2) = 16 + lp + len; + WFIFOL(inter_fd,4) = 0xFF000000; // 0xFF000000 color signals standard broadcast + WFIFOW(inter_fd,8) = 0; // fontType not used with standard broadcast + WFIFOW(inter_fd,10) = 0; // fontSize not used with standard broadcast + WFIFOW(inter_fd,12) = 0; // fontAlign not used with standard broadcast + WFIFOW(inter_fd,14) = 0; // fontY not used with standard broadcast + if (type == 0x10) // bc_blue + WFIFOL(inter_fd,16) = 0x65756c62; //If there's "blue" at the beginning of the message, game client will display it in blue instead of yellow. + else if (type == 0x20) // bc_woe + WFIFOL(inter_fd,16) = 0x73737373; //If there's "ssss", game client will recognize message as 'WoE broadcast'. + memcpy(WFIFOP(inter_fd,16 + lp), mes, len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + return 0; +} + +int intif_broadcast2(const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY) +{ + // Send to the local players + if (fontColor == 0xFE000000) // This is main chat message [LuzZza] + clif_MainChatMessage(mes); + else + clif_broadcast2(NULL, mes, len, fontColor, fontType, fontSize, fontAlign, fontY, ALL_CLIENT); + + if (CheckForCharServer()) + return 0; + + if (other_mapserver_count < 1) + return 0; //No need to send. + + WFIFOHEAD(inter_fd, 16 + len); + WFIFOW(inter_fd,0) = 0x3000; + WFIFOW(inter_fd,2) = 16 + len; + WFIFOL(inter_fd,4) = fontColor; + WFIFOW(inter_fd,8) = fontType; + WFIFOW(inter_fd,10) = fontSize; + WFIFOW(inter_fd,12) = fontAlign; + WFIFOW(inter_fd,14) = fontY; + memcpy(WFIFOP(inter_fd,16), mes, len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + return 0; +} + +/// send a message using the main chat system +/// <sd> the source of message +/// <message> the message that was sent +int intif_main_message(struct map_session_data* sd, const char* message) +{ + char output[256]; + + nullpo_ret(sd); + + // format the message for main broadcasting + snprintf( output, sizeof(output), msg_txt(386), sd->status.name, message ); + + // send the message using the inter-server broadcast service + intif_broadcast2( output, strlen(output) + 1, 0xFE000000, 0, 0, 0, 0 ); + + // log the chat message + log_chat( LOG_CHAT_MAINCHAT, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message ); + + return 0; +} + +// The transmission of Wisp/Page to inter-server (player not found on this server) +int intif_wis_message(struct map_session_data *sd, char *nick, char *mes, int mes_len) +{ + nullpo_ret(sd); + if (CheckForCharServer()) + return 0; + + if (other_mapserver_count < 1) + { //Character not found. + clif_wis_end(sd->fd, 1); + return 0; + } + + WFIFOHEAD(inter_fd,mes_len + 52); + WFIFOW(inter_fd,0) = 0x3001; + WFIFOW(inter_fd,2) = mes_len + 52; + memcpy(WFIFOP(inter_fd,4), sd->status.name, NAME_LENGTH); + memcpy(WFIFOP(inter_fd,4+NAME_LENGTH), nick, NAME_LENGTH); + memcpy(WFIFOP(inter_fd,4+2*NAME_LENGTH), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + ShowInfo("intif_wis_message from %s to %s (message: '%s')\n", sd->status.name, nick, mes); + + return 0; +} + +// The reply of Wisp/page +int intif_wis_replay(int id, int flag) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,7); + WFIFOW(inter_fd,0) = 0x3002; + WFIFOL(inter_fd,2) = id; + WFIFOB(inter_fd,6) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + WFIFOSET(inter_fd,7); + + if (battle_config.etc_log) + ShowInfo("intif_wis_replay: id: %d, flag:%d\n", id, flag); + + return 0; +} + +// The transmission of GM only Wisp/Page from server to inter-server +int intif_wis_message_to_gm(char *wisp_name, int permission, char *mes) +{ + int mes_len; + if (CheckForCharServer()) + return 0; + mes_len = strlen(mes) + 1; // + null + WFIFOHEAD(inter_fd, mes_len + 32); + WFIFOW(inter_fd,0) = 0x3003; + WFIFOW(inter_fd,2) = mes_len + 32; + memcpy(WFIFOP(inter_fd,4), wisp_name, NAME_LENGTH); + WFIFOL(inter_fd,4+NAME_LENGTH) = permission; + memcpy(WFIFOP(inter_fd,8+NAME_LENGTH), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + ShowNotice("intif_wis_message_to_gm: from: '%s', required permission: %d, message: '%s'.\n", wisp_name, permission, mes); + + return 0; +} + +int intif_regtostr(char* str, struct global_reg *reg, int qty) +{ + int len =0, i; + + for (i = 0; i < qty; i++) { + len+= sprintf(str+len, "%s", reg[i].str)+1; //We add 1 to consider the '\0' in place. + len+= sprintf(str+len, "%s", reg[i].value)+1; + } + return len; +} + +//Request for saving registry values. +int intif_saveregistry(struct map_session_data *sd, int type) +{ + struct global_reg *reg; + int count; + int i, p; + + if (CheckForCharServer()) + return -1; + + switch (type) { + case 3: //Character reg + reg = sd->save_reg.global; + count = sd->save_reg.global_num; + sd->state.reg_dirty &= ~0x4; + break; + case 2: //Account reg + reg = sd->save_reg.account; + count = sd->save_reg.account_num; + sd->state.reg_dirty &= ~0x2; + break; + case 1: //Account2 reg + reg = sd->save_reg.account2; + count = sd->save_reg.account2_num; + sd->state.reg_dirty &= ~0x1; + break; + default: //Broken code? + ShowError("intif_saveregistry: Invalid type %d\n", type); + return -1; + } + WFIFOHEAD(inter_fd, 288 * MAX_REG_NUM+13); + WFIFOW(inter_fd,0)=0x3004; + WFIFOL(inter_fd,4)=sd->status.account_id; + WFIFOL(inter_fd,8)=sd->status.char_id; + WFIFOB(inter_fd,12)=type; + for( p = 13, i = 0; i < count; i++ ) { + if (reg[i].str[0] != '\0' && reg[i].value[0] != '\0') { + p+= sprintf((char*)WFIFOP(inter_fd,p), "%s", reg[i].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf((char*)WFIFOP(inter_fd,p), "%s", reg[i].value)+1; + } + } + WFIFOW(inter_fd,2)=p; + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +//Request the registries for this player. +int intif_request_registry(struct map_session_data *sd, int flag) +{ + nullpo_ret(sd); + + sd->save_reg.account2_num = -1; + sd->save_reg.account_num = -1; + sd->save_reg.global_num = -1; + + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3005; + WFIFOL(inter_fd,2) = sd->status.account_id; + WFIFOL(inter_fd,6) = sd->status.char_id; + WFIFOB(inter_fd,10) = (flag&1?1:0); //Request Acc Reg 2 + WFIFOB(inter_fd,11) = (flag&2?1:0); //Request Acc Reg + WFIFOB(inter_fd,12) = (flag&4?1:0); //Request Char Reg + WFIFOSET(inter_fd,13); + + return 0; +} + +int intif_request_guild_storage(int account_id,int guild_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x3018; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = guild_id; + WFIFOSET(inter_fd,10); + return 0; +} +int intif_send_guild_storage(int account_id,struct guild_storage *gstor) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,sizeof(struct guild_storage)+12); + WFIFOW(inter_fd,0) = 0x3019; + WFIFOW(inter_fd,2) = (unsigned short)sizeof(struct guild_storage)+12; + WFIFOL(inter_fd,4) = account_id; + WFIFOL(inter_fd,8) = gstor->guild_id; + memcpy( WFIFOP(inter_fd,12),gstor, sizeof(struct guild_storage) ); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// Party creation request +int intif_create_party(struct party_member *member,char *name,int item,int item2) +{ + if (CheckForCharServer()) + return 0; + nullpo_ret(member); + + WFIFOHEAD(inter_fd,64); + WFIFOW(inter_fd,0) = 0x3020; + WFIFOW(inter_fd,2) = 30+sizeof(struct party_member); + memcpy(WFIFOP(inter_fd,4),name, NAME_LENGTH); + WFIFOB(inter_fd,28)= item; + WFIFOB(inter_fd,29)= item2; + memcpy(WFIFOP(inter_fd,30), member, sizeof(struct party_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd, 2)); + return 0; +} + +// Party information request +int intif_request_partyinfo(int party_id, int char_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x3021; + WFIFOL(inter_fd,2) = party_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOSET(inter_fd,10); + return 0; +} + +// Request to add a member to party +int intif_party_addmember(int party_id,struct party_member *member) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,42); + WFIFOW(inter_fd,0)=0x3022; + WFIFOW(inter_fd,2)=8+sizeof(struct party_member); + WFIFOL(inter_fd,4)=party_id; + memcpy(WFIFOP(inter_fd,8),member,sizeof(struct party_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd, 2)); + return 1; +} + +// Request to change party configuration (exp,item share) +int intif_party_changeoption(int party_id,int account_id,int exp,int item) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,14); + WFIFOW(inter_fd,0)=0x3023; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOW(inter_fd,10)=exp; + WFIFOW(inter_fd,12)=item; + WFIFOSET(inter_fd,14); + return 0; +} + +// Request to leave party +int intif_party_leave(int party_id,int account_id, int char_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,14); + WFIFOW(inter_fd,0)=0x3024; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOL(inter_fd,10)=char_id; + WFIFOSET(inter_fd,14); + return 0; +} + +// Request keeping party for new map ?? +int intif_party_changemap(struct map_session_data *sd,int online) +{ + int16 m, mapindex; + + if (CheckForCharServer()) + return 0; + if(!sd) + return 0; + + if( (m=map_mapindex2mapid(sd->mapindex)) >= 0 && map[m].instance_id ) + mapindex = map[map[m].instance_src_map].index; + else + mapindex = sd->mapindex; + + WFIFOHEAD(inter_fd,19); + WFIFOW(inter_fd,0)=0x3025; + WFIFOL(inter_fd,2)=sd->status.party_id; + WFIFOL(inter_fd,6)=sd->status.account_id; + WFIFOL(inter_fd,10)=sd->status.char_id; + WFIFOW(inter_fd,14)=mapindex; + WFIFOB(inter_fd,16)=online; + WFIFOW(inter_fd,17)=sd->status.base_level; + WFIFOSET(inter_fd,19); + return 1; +} + +// Request breaking party +int intif_break_party(int party_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0)=0x3026; + WFIFOL(inter_fd,2)=party_id; + WFIFOSET(inter_fd,6); + return 0; +} + +// Sending party chat +int intif_party_message(int party_id,int account_id,const char *mes,int len) +{ + if (CheckForCharServer()) + return 0; + + if (other_mapserver_count < 1) + return 0; //No need to send. + + WFIFOHEAD(inter_fd,len + 12); + WFIFOW(inter_fd,0)=0x3027; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=party_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + return 0; +} + +// Request a new leader for party +int intif_party_leaderchange(int party_id,int account_id,int char_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,14); + WFIFOW(inter_fd,0)=0x3029; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOL(inter_fd,10)=char_id; + WFIFOSET(inter_fd,14); + return 0; +} + +// Request a Guild creation +int intif_guild_create(const char *name,const struct guild_member *master) +{ + if (CheckForCharServer()) + return 0; + nullpo_ret(master); + + WFIFOHEAD(inter_fd,sizeof(struct guild_member)+(8+NAME_LENGTH)); + WFIFOW(inter_fd,0)=0x3030; + WFIFOW(inter_fd,2)=sizeof(struct guild_member)+(8+NAME_LENGTH); + WFIFOL(inter_fd,4)=master->account_id; + memcpy(WFIFOP(inter_fd,8),name,NAME_LENGTH); + memcpy(WFIFOP(inter_fd,8+NAME_LENGTH),master,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// Request Guild information +int intif_guild_request_info(int guild_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3031; + WFIFOL(inter_fd,2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} + +// Request to add member to the guild +int intif_guild_addmember(int guild_id,struct guild_member *m) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,sizeof(struct guild_member)+8); + WFIFOW(inter_fd,0) = 0x3032; + WFIFOW(inter_fd,2) = sizeof(struct guild_member)+8; + WFIFOL(inter_fd,4) = guild_id; + memcpy(WFIFOP(inter_fd,8),m,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// Request a new leader for guild +int intif_guild_change_gm(int guild_id, const char* name, int len) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, len + 8); + WFIFOW(inter_fd, 0)=0x3033; + WFIFOW(inter_fd, 2)=len+8; + WFIFOL(inter_fd, 4)=guild_id; + memcpy(WFIFOP(inter_fd,8),name,len); + WFIFOSET(inter_fd,len+8); + return 0; +} + +// Request to leave guild +int intif_guild_leave(int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 55); + WFIFOW(inter_fd, 0) = 0x3034; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = flag; + safestrncpy((char*)WFIFOP(inter_fd,15),mes,40); + WFIFOSET(inter_fd,55); + return 0; +} + +//Update request / Lv online status of the guild members +int intif_guild_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class_) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 19); + WFIFOW(inter_fd, 0) = 0x3035; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = online; + WFIFOW(inter_fd,15) = lv; + WFIFOW(inter_fd,17) = class_; + WFIFOSET(inter_fd,19); + return 0; +} + +//Guild disbanded notification +int intif_guild_break(int guild_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 6); + WFIFOW(inter_fd, 0) = 0x3036; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} + +// Send a guild message +int intif_guild_message(int guild_id,int account_id,const char *mes,int len) +{ + if (CheckForCharServer()) + return 0; + + if (other_mapserver_count < 1) + return 0; //No need to send. + + WFIFOHEAD(inter_fd, len + 12); + WFIFOW(inter_fd,0)=0x3037; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + + return 0; +} + +// Request a change of Guild basic information +int intif_guild_change_basicinfo(int guild_id,int type,const void *data,int len) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, len + 10); + WFIFOW(inter_fd,0)=0x3039; + WFIFOW(inter_fd,2)=len+10; + WFIFOL(inter_fd,4)=guild_id; + WFIFOW(inter_fd,8)=type; + memcpy(WFIFOP(inter_fd,10),data,len); + WFIFOSET(inter_fd,len+10); + return 0; +} + +// Request a change of Guild member information +int intif_guild_change_memberinfo(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, len + 18); + WFIFOW(inter_fd, 0)=0x303a; + WFIFOW(inter_fd, 2)=len+18; + WFIFOL(inter_fd, 4)=guild_id; + WFIFOL(inter_fd, 8)=account_id; + WFIFOL(inter_fd,12)=char_id; + WFIFOW(inter_fd,16)=type; + memcpy(WFIFOP(inter_fd,18),data,len); + WFIFOSET(inter_fd,len+18); + return 0; +} + +// Request a change of Guild title +int intif_guild_position(int guild_id,int idx,struct guild_position *p) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, sizeof(struct guild_position)+12); + WFIFOW(inter_fd,0)=0x303b; + WFIFOW(inter_fd,2)=sizeof(struct guild_position)+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=idx; + memcpy(WFIFOP(inter_fd,12),p,sizeof(struct guild_position)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// Request an update of Guildskill skill_id +int intif_guild_skillup(int guild_id, uint16 skill_id, int account_id, int max) +{ + if( CheckForCharServer() ) + return 0; + WFIFOHEAD(inter_fd, 18); + WFIFOW(inter_fd, 0) = 0x303c; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = skill_id; + WFIFOL(inter_fd, 10) = account_id; + WFIFOL(inter_fd, 14) = max; + WFIFOSET(inter_fd, 18); + return 0; +} + +// Request a new guild relationship +int intif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2,int flag) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,19); + WFIFOW(inter_fd, 0)=0x303d; + WFIFOL(inter_fd, 2)=guild_id1; + WFIFOL(inter_fd, 6)=guild_id2; + WFIFOL(inter_fd,10)=account_id1; + WFIFOL(inter_fd,14)=account_id2; + WFIFOB(inter_fd,18)=flag; + WFIFOSET(inter_fd,19); + return 0; +} + +// Request to change guild notice +int intif_guild_notice(int guild_id,const char *mes1,const char *mes2) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,186); + WFIFOW(inter_fd,0)=0x303e; + WFIFOL(inter_fd,2)=guild_id; + memcpy(WFIFOP(inter_fd,6),mes1,MAX_GUILDMES1); + memcpy(WFIFOP(inter_fd,66),mes2,MAX_GUILDMES2); + WFIFOSET(inter_fd,186); + return 0; +} + +// Request to change guild emblem +int intif_guild_emblem(int guild_id,int len,const char *data) +{ + if (CheckForCharServer()) + return 0; + if(guild_id<=0 || len<0 || len>2000) + return 0; + WFIFOHEAD(inter_fd,len + 12); + WFIFOW(inter_fd,0)=0x303f; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=0; + memcpy(WFIFOP(inter_fd,12),data,len); + WFIFOSET(inter_fd,len+12); + return 0; +} + +/** + * Requests guild castles data from char-server. + * @param num Number of castles, size of castle_ids array. + * @param castle_ids Pointer to array of castle IDs. + */ +int intif_guild_castle_dataload(int num, int *castle_ids) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 4 + num * sizeof(int)); + WFIFOW(inter_fd, 0) = 0x3040; + WFIFOW(inter_fd, 2) = 4 + num * sizeof(int); + memcpy(WFIFOP(inter_fd, 4), castle_ids, num * sizeof(int)); + WFIFOSET(inter_fd, WFIFOW(inter_fd, 2)); + return 1; +} + + +// Request change castle guild owner and save data +int intif_guild_castle_datasave(int castle_id,int index, int value) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd,9); + WFIFOW(inter_fd,0)=0x3041; + WFIFOW(inter_fd,2)=castle_id; + WFIFOB(inter_fd,4)=index; + WFIFOL(inter_fd,5)=value; + WFIFOSET(inter_fd,9); + return 1; +} + +//----------------------------------------------------------------- +// Homunculus Packets send to Inter server [albator] +//----------------------------------------------------------------- + +int intif_homunculus_create(int account_id, struct s_homunculus *sh) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, sizeof(struct s_homunculus)+8); + WFIFOW(inter_fd,0) = 0x3090; + WFIFOW(inter_fd,2) = sizeof(struct s_homunculus)+8; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8),sh,sizeof(struct s_homunculus)); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + return 0; +} + +int intif_homunculus_requestload(int account_id, int homun_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 10); + WFIFOW(inter_fd,0) = 0x3091; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = homun_id; + WFIFOSET(inter_fd, 10); + return 1; +} + +int intif_homunculus_requestsave(int account_id, struct s_homunculus* sh) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, sizeof(struct s_homunculus)+8); + WFIFOW(inter_fd,0) = 0x3092; + WFIFOW(inter_fd,2) = sizeof(struct s_homunculus)+8; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8),sh,sizeof(struct s_homunculus)); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + return 0; + +} + +int intif_homunculus_requestdelete(int homun_id) +{ + if (CheckForCharServer()) + return 0; + WFIFOHEAD(inter_fd, 6); + WFIFOW(inter_fd, 0) = 0x3093; + WFIFOL(inter_fd,2) = homun_id; + WFIFOSET(inter_fd,6); + return 0; + +} + + +//----------------------------------------------------------------- +// Packets receive from inter server + +// Wisp/Page reception // rewritten by [Yor] +int intif_parse_WisMessage(int fd) +{ + struct map_session_data* sd; + char *wisp_source; + char name[NAME_LENGTH]; + int id, i; + + id=RFIFOL(fd,4); + + safestrncpy(name, (char*)RFIFOP(fd,32), NAME_LENGTH); + sd = map_nick2sd(name); + if(sd == NULL || strcmp(sd->status.name, name) != 0) + { //Not found + intif_wis_replay(id,1); + return 0; + } + if(sd->state.ignoreAll) { + intif_wis_replay(id, 2); + return 0; + } + wisp_source = (char *) RFIFOP(fd,8); // speed up [Yor] + for(i=0; i < MAX_IGNORE_LIST && + sd->ignore[i].name[0] != '\0' && + strcmp(sd->ignore[i].name, wisp_source) != 0 + ; i++); + + if (i < MAX_IGNORE_LIST && sd->ignore[i].name[0] != '\0') + { //Ignored + intif_wis_replay(id, 2); + return 0; + } + //Success to send whisper. + clif_wis_message(sd->fd, wisp_source, (char*)RFIFOP(fd,56),RFIFOW(fd,2)-56); + intif_wis_replay(id,0); // succes + return 0; +} + +// Wisp/page transmission result reception +int intif_parse_WisEnd(int fd) +{ + struct map_session_data* sd; + + if (battle_config.etc_log) + ShowInfo("intif_parse_wisend: player: %s, flag: %d\n", RFIFOP(fd,2), RFIFOB(fd,26)); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + sd = (struct map_session_data *)map_nick2sd((char *) RFIFOP(fd,2)); + if (sd != NULL) + clif_wis_end(sd->fd, RFIFOB(fd,26)); + + return 0; +} + +static int mapif_parse_WisToGM_sub(struct map_session_data* sd,va_list va) +{ + int permission = va_arg(va, int); + char *wisp_name; + char *message; + int len; + + if (!pc_has_permission(sd, permission)) + return 0; + wisp_name = va_arg(va, char*); + message = va_arg(va, char*); + len = va_arg(va, int); + clif_wis_message(sd->fd, wisp_name, message, len); + return 1; +} + +// Received wisp message from map-server via char-server for ALL gm +// 0x3003/0x3803 <packet_len>.w <wispname>.24B <permission>.l <message>.?B +int mapif_parse_WisToGM(int fd) +{ + int permission, mes_len; + char Wisp_name[NAME_LENGTH]; + char mbuf[255]; + char *message; + + mes_len = RFIFOW(fd,2) - 32; + message = (char *) (mes_len >= 255 ? (char *) aMalloc(mes_len) : mbuf); + + permission = RFIFOL(fd,28); + safestrncpy(Wisp_name, (char*)RFIFOP(fd,4), NAME_LENGTH); + safestrncpy(message, (char*)RFIFOP(fd,32), mes_len); + // information is sent to all online GM + map_foreachpc(mapif_parse_WisToGM_sub, permission, Wisp_name, message, mes_len); + + if (message != mbuf) + aFree(message); + return 0; +} + +// Request player registre +int intif_parse_Registers(int fd) +{ + int j,p,len,max, flag; + struct map_session_data *sd; + struct global_reg *reg; + int *qty; + int account_id = RFIFOL(fd,4), char_id = RFIFOL(fd,8); + struct auth_node *node = chrif_auth_check(account_id, char_id, ST_LOGIN); + if (node) + sd = node->sd; + else { //Normally registries should arrive for in log-in chars. + sd = map_id2sd(account_id); + if (sd && RFIFOB(fd,12) == 3 && sd->status.char_id != char_id) + sd = NULL; //Character registry from another character. + } + if (!sd) return 1; + + flag = (sd->save_reg.global_num == -1 || sd->save_reg.account_num == -1 || sd->save_reg.account2_num == -1); + + switch (RFIFOB(fd,12)) { + case 3: //Character Registry + reg = sd->save_reg.global; + qty = &sd->save_reg.global_num; + max = GLOBAL_REG_NUM; + break; + case 2: //Account Registry + reg = sd->save_reg.account; + qty = &sd->save_reg.account_num; + max = ACCOUNT_REG_NUM; + break; + case 1: //Account2 Registry + reg = sd->save_reg.account2; + qty = &sd->save_reg.account2_num; + max = ACCOUNT_REG2_NUM; + break; + default: + ShowError("intif_parse_Registers: Unrecognized type %d\n",RFIFOB(fd,12)); + return 0; + } + for(j=0,p=13;j<max && p<RFIFOW(fd,2);j++){ + sscanf((char*)RFIFOP(fd,p), "%31c%n", reg[j].str,&len); + reg[j].str[len]='\0'; + p += len+1; //+1 to skip the '\0' between strings. + sscanf((char*)RFIFOP(fd,p), "%255c%n", reg[j].value,&len); + reg[j].value[len]='\0'; + p += len+1; + } + *qty = j; + + if (flag && sd->save_reg.global_num > -1 && sd->save_reg.account_num > -1 && sd->save_reg.account2_num > -1) + pc_reg_received(sd); //Received all registry values, execute init scripts and what-not. [Skotlex] + return 1; +} + +int intif_parse_LoadGuildStorage(int fd) +{ + struct guild_storage *gstor; + struct map_session_data *sd; + int guild_id; + + guild_id = RFIFOL(fd,8); + if(guild_id <= 0) + return 1; + sd=map_id2sd( RFIFOL(fd,4) ); + if(sd==NULL){ + ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4)); + return 1; + } + gstor=guild2storage(guild_id); + if(!gstor) { + ShowWarning("intif_parse_LoadGuildStorage: error guild_id %d not exist\n",guild_id); + return 1; + } + if (gstor->storage_status == 1) { // Already open.. lets ignore this update + ShowWarning("intif_parse_LoadGuildStorage: storage received for a client already open (User %d:%d)\n", sd->status.account_id, sd->status.char_id); + return 1; + } + if (gstor->dirty) { // Already have storage, and it has been modified and not saved yet! Exploit! [Skotlex] + ShowWarning("intif_parse_LoadGuildStorage: received storage for an already modified non-saved storage! (User %d:%d)\n", sd->status.account_id, sd->status.char_id); + return 1; + } + if( RFIFOW(fd,2)-12 != sizeof(struct guild_storage) ){ + ShowError("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-12 , sizeof(struct guild_storage)); + gstor->storage_status = 0; + return 1; + } + + memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage)); + storage_guild_storageopen(sd); + return 0; +} + +// ACK guild_storage saved +int intif_parse_SaveGuildStorage(int fd) +{ + storage_guild_storagesaved(/*RFIFOL(fd,2), */RFIFOL(fd,6)); + return 0; +} + +// ACK party creation +int intif_parse_PartyCreated(int fd) +{ + if(battle_config.etc_log) + ShowInfo("intif: party created by account %d\n\n", RFIFOL(fd,2)); + party_created(RFIFOL(fd,2), RFIFOL(fd,6),RFIFOB(fd,10),RFIFOL(fd,11), (char *)RFIFOP(fd,15)); + return 0; +} + +// Receive party info +int intif_parse_PartyInfo(int fd) +{ + if( RFIFOW(fd,2) == 12 ){ + ShowWarning("intif: party noinfo (char_id=%d party_id=%d)\n", RFIFOL(fd,4), RFIFOL(fd,8)); + party_recv_noinfo(RFIFOL(fd,8), RFIFOL(fd,4)); + return 0; + } + + if( RFIFOW(fd,2) != 8+sizeof(struct party) ) + ShowError("intif: party info : data size error (char_id=%d party_id=%d packet_len=%d expected_len=%d)\n", RFIFOL(fd,4), RFIFOL(fd,8), RFIFOW(fd,2), 8+sizeof(struct party)); + party_recv_info((struct party *)RFIFOP(fd,8), RFIFOL(fd,4)); + return 0; +} + +// ACK adding party member +int intif_parse_PartyMemberAdded(int fd) +{ + if(battle_config.etc_log) + ShowInfo("intif: party member added Party (%d), Account(%d), Char(%d)\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + party_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10), RFIFOB(fd, 14)); + return 0; +} + +// ACK changing party option +int intif_parse_PartyOptionChanged(int fd) +{ + party_optionchanged(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOB(fd,14)); + return 0; +} + +// ACK member leaving party +int intif_parse_PartyMemberWithdraw(int fd) +{ + if(battle_config.etc_log) + ShowInfo("intif: party member withdraw: Party(%d), Account(%d), Char(%d)\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + party_member_withdraw(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +// ACK party break +int intif_parse_PartyBroken(int fd) +{ + party_broken(RFIFOL(fd,2)); + return 0; +} + +// ACK party on new map +int intif_parse_PartyMove(int fd) +{ + party_recv_movemap(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOW(fd,14),RFIFOB(fd,16),RFIFOW(fd,17)); + return 0; +} + +// ACK party messages +int intif_parse_PartyMessage(int fd) +{ + party_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),(char *) RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} + +// ACK guild creation +int intif_parse_GuildCreated(int fd) +{ + guild_created(RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} + +// ACK guild infos +int intif_parse_GuildInfo(int fd) +{ + if(RFIFOW(fd,2) == 8) { + ShowWarning("intif: guild noinfo %d\n",RFIFOL(fd,4)); + guild_recv_noinfo(RFIFOL(fd,4)); + return 0; + } + if( RFIFOW(fd,2)!=sizeof(struct guild)+4 ) + ShowError("intif: guild info : data size error Gid: %d recv size: %d Expected size: %d\n",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild)+4); + guild_recv_info((struct guild *)RFIFOP(fd,4)); + return 0; +} + +// ACK adding guild member +int intif_parse_GuildMemberAdded(int fd) +{ + if(battle_config.etc_log) + ShowInfo("intif: guild member added %d %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + guild_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + return 0; +} + +// ACK member leaving guild +int intif_parse_GuildMemberWithdraw(int fd) +{ + guild_member_withdraw(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),(char *)RFIFOP(fd,55),(char *)RFIFOP(fd,15)); + return 0; +} + +// ACK guild member basic info +int intif_parse_GuildMemberInfoShort(int fd) +{ + guild_recv_memberinfoshort(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); + return 0; +} + +// ACK guild break +int intif_parse_GuildBroken(int fd) +{ + guild_broken(RFIFOL(fd,2),RFIFOB(fd,6)); + return 0; +} + +// basic guild info change notice +// 0x3839 <packet len>.w <guild id>.l <type>.w <data>.?b +int intif_parse_GuildBasicInfoChanged(int fd) +{ + //int len = RFIFOW(fd,2) - 10; + int guild_id = RFIFOL(fd,4); + int type = RFIFOW(fd,8); + //void* data = RFIFOP(fd,10); + + struct guild* g = guild_search(guild_id); + if( g == NULL ) + return 0; + + switch(type) { + case GBI_EXP: g->exp = RFIFOQ(fd,10); break; + case GBI_GUILDLV: g->guild_lv = RFIFOW(fd,10); break; + case GBI_SKILLPOINT: g->skill_point = RFIFOL(fd,10); break; + } + + return 0; +} + +// guild member info change notice +// 0x383a <packet len>.w <guild id>.l <account id>.l <char id>.l <type>.w <data>.?b +int intif_parse_GuildMemberInfoChanged(int fd) +{ + //int len = RFIFOW(fd,2) - 18; + int guild_id = RFIFOL(fd,4); + int account_id = RFIFOL(fd,8); + int char_id = RFIFOL(fd,12); + int type = RFIFOW(fd,16); + //void* data = RFIFOP(fd,18); + + struct guild* g; + int idx; + + g = guild_search(guild_id); + if( g == NULL ) + return 0; + + idx = guild_getindex(g,account_id,char_id); + if( idx == -1 ) + return 0; + + switch( type ) { + case GMI_POSITION: g->member[idx].position = RFIFOW(fd,18); guild_memberposition_changed(g,idx,RFIFOW(fd,18)); break; + case GMI_EXP: g->member[idx].exp = RFIFOQ(fd,18); break; + case GMI_HAIR: g->member[idx].hair = RFIFOW(fd,18); break; + case GMI_HAIR_COLOR: g->member[idx].hair_color = RFIFOW(fd,18); break; + case GMI_GENDER: g->member[idx].gender = RFIFOW(fd,18); break; + case GMI_CLASS: g->member[idx].class_ = RFIFOW(fd,18); break; + case GMI_LEVEL: g->member[idx].lv = RFIFOW(fd,18); break; + } + return 0; +} + +// ACK change of guild title +int intif_parse_GuildPosition(int fd) +{ + if( RFIFOW(fd,2)!=sizeof(struct guild_position)+12 ) + ShowError("intif: guild info : data size error\n %d %d %d",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild_position)+12); + guild_position_changed(RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); + return 0; +} + +// ACK change of guild skill update +int intif_parse_GuildSkillUp(int fd) +{ + guild_skillupack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +// ACK change of guild relationship +int intif_parse_GuildAlliance(int fd) +{ + guild_allianceack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14),RFIFOB(fd,18),(char *) RFIFOP(fd,19),(char *) RFIFOP(fd,43)); + return 0; +} + +// ACK change of guild notice +int intif_parse_GuildNotice(int fd) +{ + guild_notice_changed(RFIFOL(fd,2),(char *) RFIFOP(fd,6),(char *) RFIFOP(fd,66)); + return 0; +} + +// ACK change of guild emblem +int intif_parse_GuildEmblem(int fd) +{ + guild_emblem_changed(RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8), (char *)RFIFOP(fd,12)); + return 0; +} + +// ACK guild message +int intif_parse_GuildMessage(int fd) +{ + guild_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),(char *) RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} + +// Reply guild castle data request +int intif_parse_GuildCastleDataLoad(int fd) +{ + return guild_castledataloadack(RFIFOW(fd,2), (struct guild_castle *)RFIFOP(fd,4)); +} + +// ACK change of guildmaster +int intif_parse_GuildMasterChanged(int fd) +{ + return guild_gm_changed(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); +} + +// Request pet creation +int intif_parse_CreatePet(int fd) +{ + pet_get_egg(RFIFOL(fd,2),RFIFOL(fd,7),RFIFOB(fd,6)); + return 0; +} + +// ACK pet data +int intif_parse_RecvPetData(int fd) +{ + struct s_pet p; + int len; + len=RFIFOW(fd,2); + if(sizeof(struct s_pet)!=len-9) { + if(battle_config.etc_log) + ShowError("intif: pet data: data size error %d %d\n",sizeof(struct s_pet),len-9); + } + else{ + memcpy(&p,RFIFOP(fd,9),sizeof(struct s_pet)); + pet_recv_petdata(RFIFOL(fd,4),&p,RFIFOB(fd,8)); + } + + return 0; +} + +// ACK pet save data +int intif_parse_SavePetOk(int fd) +{ + if(RFIFOB(fd,6) == 1) + ShowError("pet data save failure\n"); + + return 0; +} + +// ACK deleting pet +int intif_parse_DeletePetOk(int fd) +{ + if(RFIFOB(fd,2) == 1) + ShowError("pet data delete failure\n"); + + return 0; +} + +// ACK changing name resquest, players,pets,hommon +int intif_parse_ChangeNameOk(int fd) +{ + struct map_session_data *sd = NULL; + if((sd=map_id2sd(RFIFOL(fd,2)))==NULL || + sd->status.char_id != RFIFOL(fd,6)) + return 0; + + switch (RFIFOB(fd,10)) { + case 0: //Players [NOT SUPPORTED YET] + break; + case 1: //Pets + pet_change_name_ack(sd, (char*)RFIFOP(fd,12), RFIFOB(fd,11)); + break; + case 2: //Hom + merc_hom_change_name_ack(sd, (char*)RFIFOP(fd,12), RFIFOB(fd,11)); + break; + } + return 0; +} + +//---------------------------------------------------------------- +// Homunculus recv packets [albator] + +int intif_parse_CreateHomunculus(int fd) +{ + int len; + len=RFIFOW(fd,2)-9; + if(sizeof(struct s_homunculus)!=len) { + if(battle_config.etc_log) + ShowError("intif: create homun data: data size error %d != %d\n",sizeof(struct s_homunculus),len); + return 0; + } + merc_hom_recv_data(RFIFOL(fd,4), (struct s_homunculus*)RFIFOP(fd,9), RFIFOB(fd,8)) ; + return 0; +} + +int intif_parse_RecvHomunculusData(int fd) +{ + int len; + + len=RFIFOW(fd,2)-9; + + if(sizeof(struct s_homunculus)!=len) { + if(battle_config.etc_log) + ShowError("intif: homun data: data size error %d %d\n",sizeof(struct s_homunculus),len); + return 0; + } + merc_hom_recv_data(RFIFOL(fd,4), (struct s_homunculus*)RFIFOP(fd,9), RFIFOB(fd,8)); + return 0; +} + +int intif_parse_SaveHomunculusOk(int fd) +{ + if(RFIFOB(fd,6) != 1) + ShowError("homunculus data save failure for account %d\n", RFIFOL(fd,2)); + + return 0; +} + +int intif_parse_DeleteHomunculusOk(int fd) +{ + if(RFIFOB(fd,2) != 1) + ShowError("Homunculus data delete failure\n"); + + return 0; +} + +/************************************** + +QUESTLOG SYSTEM FUNCTIONS + +***************************************/ + +int intif_request_questlog(TBL_PC *sd) +{ + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3060; + WFIFOL(inter_fd,2) = sd->status.char_id; + WFIFOSET(inter_fd,6); + return 0; +} + +int intif_parse_questlog(int fd) +{ + int char_id = RFIFOL(fd, 4); + int i; + TBL_PC * sd = map_charid2sd(char_id); + + //User not online anymore + if(!sd) + return -1; + + sd->avail_quests = sd->num_quests = (RFIFOW(fd, 2)-8)/sizeof(struct quest); + + memset(&sd->quest_log, 0, sizeof(sd->quest_log)); + + for( i = 0; i < sd->num_quests; i++ ) + { + memcpy(&sd->quest_log[i], RFIFOP(fd, i*sizeof(struct quest)+8), sizeof(struct quest)); + + sd->quest_index[i] = quest_search_db(sd->quest_log[i].quest_id); + + if( sd->quest_index[i] < 0 ) + { + ShowError("intif_parse_questlog: quest %d not found in DB.\n",sd->quest_log[i].quest_id); + sd->avail_quests--; + sd->num_quests--; + i--; + continue; + } + + if( sd->quest_log[i].state == Q_COMPLETE ) + sd->avail_quests--; + } + + quest_pc_login(sd); + + return 0; +} + +int intif_parse_questsave(int fd) +{ + int cid = RFIFOL(fd, 2); + TBL_PC *sd = map_id2sd(cid); + + if( !RFIFOB(fd, 6) ) + ShowError("intif_parse_questsave: Failed to save quest(s) for character %d!\n", cid); + else if( sd ) + sd->save_quest = false; + + return 0; +} + +int intif_quest_save(TBL_PC *sd) +{ + int len; + + if(CheckForCharServer()) + return 0; + + len = sizeof(struct quest)*sd->num_quests + 8; + + WFIFOHEAD(inter_fd, len); + WFIFOW(inter_fd,0) = 0x3061; + WFIFOW(inter_fd,2) = len; + WFIFOL(inter_fd,4) = sd->status.char_id; + if( sd->num_quests ) + memcpy(WFIFOP(inter_fd,8), &sd->quest_log, sizeof(struct quest)*sd->num_quests); + WFIFOSET(inter_fd, len); + + return 0; +} + +/*========================================== + * MAIL SYSTEM + * By Zephyrus + *==========================================*/ + +/*------------------------------------------ + * Inbox Request + * flag: 0 Update Inbox | 1 OpenMail + *------------------------------------------*/ +int intif_Mail_requestinbox(int char_id, unsigned char flag) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,7); + WFIFOW(inter_fd,0) = 0x3048; + WFIFOL(inter_fd,2) = char_id; + WFIFOB(inter_fd,6) = flag; + WFIFOSET(inter_fd,7); + + return 0; +} + +int intif_parse_Mail_inboxreceived(int fd) +{ + struct map_session_data *sd; + unsigned char flag = RFIFOB(fd,8); + + sd = map_charid2sd(RFIFOL(fd,4)); + + if (sd == NULL) + { + ShowError("intif_parse_Mail_inboxreceived: char not found %d\n",RFIFOL(fd,4)); + return 1; + } + + if (RFIFOW(fd,2) - 9 != sizeof(struct mail_data)) + { + ShowError("intif_parse_Mail_inboxreceived: data size error %d %d\n", RFIFOW(fd,2) - 9, sizeof(struct mail_data)); + return 1; + } + + //FIXME: this operation is not safe [ultramage] + memcpy(&sd->mail.inbox, RFIFOP(fd,9), sizeof(struct mail_data)); + sd->mail.changed = false; // cache is now in sync + + if (flag) + clif_Mail_refreshinbox(sd); + else if( battle_config.mail_show_status && ( battle_config.mail_show_status == 1 || sd->mail.inbox.unread ) ) + { + char output[128]; + sprintf(output, msg_txt(510), sd->mail.inbox.unchecked, sd->mail.inbox.unread + sd->mail.inbox.unchecked); + clif_disp_onlyself(sd, output, strlen(output)); + } + return 0; +} +/*------------------------------------------ + * Mail Read + *------------------------------------------*/ +int intif_Mail_read(int mail_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3049; + WFIFOL(inter_fd,2) = mail_id; + WFIFOSET(inter_fd,6); + + return 0; +} +/*------------------------------------------ + * Get Attachment + *------------------------------------------*/ +int intif_Mail_getattach(int char_id, int mail_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x304a; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = mail_id; + WFIFOSET(inter_fd, 10); + + return 0; +} + +int intif_parse_Mail_getattach(int fd) +{ + struct map_session_data *sd; + struct item item; + int zeny = RFIFOL(fd,8); + + sd = map_charid2sd( RFIFOL(fd,4) ); + + if (sd == NULL) + { + ShowError("intif_parse_Mail_getattach: char not found %d\n",RFIFOL(fd,4)); + return 1; + } + + if (RFIFOW(fd,2) - 12 != sizeof(struct item)) + { + ShowError("intif_parse_Mail_getattach: data size error %d %d\n", RFIFOW(fd,2) - 16, sizeof(struct item)); + return 1; + } + + memcpy(&item, RFIFOP(fd,12), sizeof(struct item)); + + mail_getattachment(sd, zeny, &item); + return 0; +} +/*------------------------------------------ + * Delete Message + *------------------------------------------*/ +int intif_Mail_delete(int char_id, int mail_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x304b; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = mail_id; + WFIFOSET(inter_fd,10); + + return 0; +} + +int intif_parse_Mail_delete(int fd) +{ + int char_id = RFIFOL(fd,2); + int mail_id = RFIFOL(fd,6); + bool failed = RFIFOB(fd,10); + + struct map_session_data *sd = map_charid2sd(char_id); + if (sd == NULL) + { + ShowError("intif_parse_Mail_delete: char not found %d\n", char_id); + return 1; + } + + if (!failed) + { + int i; + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i < MAIL_MAX_INBOX ) + { + memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message)); + sd->mail.inbox.amount--; + } + + if( sd->mail.inbox.full ) + intif_Mail_requestinbox(sd->status.char_id, 1); // Free space is available for new mails + } + + clif_Mail_delete(sd->fd, mail_id, failed); + return 0; +} +/*------------------------------------------ + * Return Message + *------------------------------------------*/ +int intif_Mail_return(int char_id, int mail_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x304c; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = mail_id; + WFIFOSET(inter_fd,10); + + return 0; +} + +int intif_parse_Mail_return(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + int mail_id = RFIFOL(fd,6); + short fail = RFIFOB(fd,10); + + if( sd == NULL ) + { + ShowError("intif_parse_Mail_return: char not found %d\n",RFIFOL(fd,2)); + return 1; + } + + if( !fail ) + { + int i; + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i < MAIL_MAX_INBOX ) + { + memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message)); + sd->mail.inbox.amount--; + } + + if( sd->mail.inbox.full ) + intif_Mail_requestinbox(sd->status.char_id, 1); // Free space is available for new mails + } + + clif_Mail_return(sd->fd, mail_id, fail); + return 0; +} +/*------------------------------------------ + * Send Mail + *------------------------------------------*/ +int intif_Mail_send(int account_id, struct mail_message *msg) +{ + int len = sizeof(struct mail_message) + 8; + + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,len); + WFIFOW(inter_fd,0) = 0x304d; + WFIFOW(inter_fd,2) = len; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8), msg, sizeof(struct mail_message)); + WFIFOSET(inter_fd,len); + + return 1; +} + +static void intif_parse_Mail_send(int fd) +{ + struct mail_message msg; + struct map_session_data *sd; + bool fail; + + if( RFIFOW(fd,2) - 4 != sizeof(struct mail_message) ) + { + ShowError("intif_parse_Mail_send: data size error %d %d\n", RFIFOW(fd,2) - 4, sizeof(struct mail_message)); + return; + } + + memcpy(&msg, RFIFOP(fd,4), sizeof(struct mail_message)); + fail = (msg.id == 0); + + // notify sender + sd = map_charid2sd(msg.send_id); + if( sd != NULL ) + { + if( fail ) + mail_deliveryfail(sd, &msg); + else + { + clif_Mail_send(sd->fd, false); + if( save_settings&16 ) + chrif_save(sd, 0); + } + } +} + +static void intif_parse_Mail_new(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + int mail_id = RFIFOL(fd,6); + const char* sender_name = (char*)RFIFOP(fd,10); + const char* title = (char*)RFIFOP(fd,34); + + if( sd == NULL ) + return; + + sd->mail.changed = true; + clif_Mail_new(sd->fd, mail_id, sender_name, title); +} + +/*========================================== + * AUCTION SYSTEM + * By Zephyrus + *==========================================*/ +int intif_Auction_requestlist(int char_id, short type, int price, const char* searchtext, short page) +{ + int len = NAME_LENGTH + 16; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,len); + WFIFOW(inter_fd,0) = 0x3050; + WFIFOW(inter_fd,2) = len; + WFIFOL(inter_fd,4) = char_id; + WFIFOW(inter_fd,8) = type; + WFIFOL(inter_fd,10) = price; + WFIFOW(inter_fd,14) = page; + memcpy(WFIFOP(inter_fd,16), searchtext, NAME_LENGTH); + WFIFOSET(inter_fd,len); + + return 0; +} + +static void intif_parse_Auction_results(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,4)); + short count = RFIFOW(fd,8); + short pages = RFIFOW(fd,10); + uint8* data = RFIFOP(fd,12); + + if( sd == NULL ) + return; + + clif_Auction_results(sd, count, pages, data); +} + +int intif_Auction_register(struct auction_data *auction) +{ + int len = sizeof(struct auction_data) + 4; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,len); + WFIFOW(inter_fd,0) = 0x3051; + WFIFOW(inter_fd,2) = len; + memcpy(WFIFOP(inter_fd,4), auction, sizeof(struct auction_data)); + WFIFOSET(inter_fd,len); + + return 1; +} + +static void intif_parse_Auction_register(int fd) +{ + struct map_session_data *sd; + struct auction_data auction; + + if( RFIFOW(fd,2) - 4 != sizeof(struct auction_data) ) + { + ShowError("intif_parse_Auction_register: data size error %d %d\n", RFIFOW(fd,2) - 4, sizeof(struct auction_data)); + return; + } + + memcpy(&auction, RFIFOP(fd,4), sizeof(struct auction_data)); + if( (sd = map_charid2sd(auction.seller_id)) == NULL ) + return; + + if( auction.auction_id > 0 ) + { + clif_Auction_message(sd->fd, 1); // Confirmation Packet ?? + if( save_settings&32 ) + chrif_save(sd,0); + } + else + { + int zeny = auction.hours*battle_config.auction_feeperhour; + + clif_Auction_message(sd->fd, 4); + pc_additem(sd, &auction.item, auction.item.amount, LOG_TYPE_AUCTION); + + pc_getzeny(sd, zeny, LOG_TYPE_AUCTION, NULL); + } +} + +int intif_Auction_cancel(int char_id, unsigned int auction_id) +{ + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x3052; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = auction_id; + WFIFOSET(inter_fd,10); + + return 0; +} + +static void intif_parse_Auction_cancel(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + int result = RFIFOB(fd,6); + + if( sd == NULL ) + return; + + switch( result ) + { + case 0: clif_Auction_message(sd->fd, 2); break; + case 1: clif_Auction_close(sd->fd, 2); break; + case 2: clif_Auction_close(sd->fd, 1); break; + case 3: clif_Auction_message(sd->fd, 3); break; + } +} + +int intif_Auction_close(int char_id, unsigned int auction_id) +{ + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x3053; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = auction_id; + WFIFOSET(inter_fd,10); + + return 0; +} + +static void intif_parse_Auction_close(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + unsigned char result = RFIFOB(fd,6); + + if( sd == NULL ) + return; + + clif_Auction_close(sd->fd, result); + if( result == 0 ) + { + // FIXME: Leeching off a parse function + clif_parse_Auction_cancelreg(fd, sd); + intif_Auction_requestlist(sd->status.char_id, 6, 0, "", 1); + } +} + +int intif_Auction_bid(int char_id, const char* name, unsigned int auction_id, int bid) +{ + int len = 16 + NAME_LENGTH; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,len); + WFIFOW(inter_fd,0) = 0x3055; + WFIFOW(inter_fd,2) = len; + WFIFOL(inter_fd,4) = char_id; + WFIFOL(inter_fd,8) = auction_id; + WFIFOL(inter_fd,12) = bid; + memcpy(WFIFOP(inter_fd,16), name, NAME_LENGTH); + WFIFOSET(inter_fd,len); + + return 0; +} + +static void intif_parse_Auction_bid(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + int bid = RFIFOL(fd,6); + unsigned char result = RFIFOB(fd,10); + + if( sd == NULL ) + return; + + clif_Auction_message(sd->fd, result); + if( bid > 0 ) + { + pc_getzeny(sd, bid, LOG_TYPE_AUCTION,NULL); + } + if( result == 1 ) + { // To update the list, display your buy list + clif_parse_Auction_cancelreg(fd, sd); + intif_Auction_requestlist(sd->status.char_id, 7, 0, "", 1); + } +} + +// Used to send 'You have won the auction' and 'You failed to won the auction' messages +static void intif_parse_Auction_message(int fd) +{ + struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2)); + unsigned char result = RFIFOB(fd,6); + + if( sd == NULL ) + return; + + clif_Auction_message(sd->fd, result); +} + +/*========================================== + * Mercenary's System + *------------------------------------------*/ +int intif_mercenary_create(struct s_mercenary *merc) +{ + int size = sizeof(struct s_mercenary) + 4; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,size); + WFIFOW(inter_fd,0) = 0x3070; + WFIFOW(inter_fd,2) = size; + memcpy(WFIFOP(inter_fd,4), merc, sizeof(struct s_mercenary)); + WFIFOSET(inter_fd,size); + return 0; +} + +int intif_parse_mercenary_received(int fd) +{ + int len = RFIFOW(fd,2) - 5; + if( sizeof(struct s_mercenary) != len ) + { + if( battle_config.etc_log ) + ShowError("intif: create mercenary data size error %d != %d\n", sizeof(struct s_mercenary), len); + return 0; + } + + merc_data_received((struct s_mercenary*)RFIFOP(fd,5), RFIFOB(fd,4)); + return 0; +} + +int intif_mercenary_request(int merc_id, int char_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x3071; + WFIFOL(inter_fd,2) = merc_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOSET(inter_fd,10); + return 0; +} + +int intif_mercenary_delete(int merc_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x3072; + WFIFOL(inter_fd,2) = merc_id; + WFIFOSET(inter_fd,6); + return 0; +} + +int intif_parse_mercenary_deleted(int fd) +{ + if( RFIFOB(fd,2) != 1 ) + ShowError("Mercenary data delete failure\n"); + + return 0; +} + +int intif_mercenary_save(struct s_mercenary *merc) +{ + int size = sizeof(struct s_mercenary) + 4; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,size); + WFIFOW(inter_fd,0) = 0x3073; + WFIFOW(inter_fd,2) = size; + memcpy(WFIFOP(inter_fd,4), merc, sizeof(struct s_mercenary)); + WFIFOSET(inter_fd,size); + return 0; +} + +int intif_parse_mercenary_saved(int fd) +{ + if( RFIFOB(fd,2) != 1 ) + ShowError("Mercenary data save failure\n"); + + return 0; +} + +/*========================================== + * Elemental's System + *------------------------------------------*/ +int intif_elemental_create(struct s_elemental *ele) +{ + int size = sizeof(struct s_elemental) + 4; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,size); + WFIFOW(inter_fd,0) = 0x307c; + WFIFOW(inter_fd,2) = size; + memcpy(WFIFOP(inter_fd,4), ele, sizeof(struct s_elemental)); + WFIFOSET(inter_fd,size); + return 0; +} + +int intif_parse_elemental_received(int fd) +{ + int len = RFIFOW(fd,2) - 5; + if( sizeof(struct s_elemental) != len ) + { + if( battle_config.etc_log ) + ShowError("intif: create elemental data size error %d != %d\n", sizeof(struct s_elemental), len); + return 0; + } + + elemental_data_received((struct s_elemental*)RFIFOP(fd,5), RFIFOB(fd,4)); + return 0; +} + +int intif_elemental_request(int ele_id, int char_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,10); + WFIFOW(inter_fd,0) = 0x307d; + WFIFOL(inter_fd,2) = ele_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOSET(inter_fd,10); + return 0; +} + +int intif_elemental_delete(int ele_id) +{ + if (CheckForCharServer()) + return 0; + + WFIFOHEAD(inter_fd,6); + WFIFOW(inter_fd,0) = 0x307e; + WFIFOL(inter_fd,2) = ele_id; + WFIFOSET(inter_fd,6); + return 0; +} + +int intif_parse_elemental_deleted(int fd) +{ + if( RFIFOB(fd,2) != 1 ) + ShowError("Elemental data delete failure\n"); + + return 0; +} + +int intif_elemental_save(struct s_elemental *ele) +{ + int size = sizeof(struct s_elemental) + 4; + + if( CheckForCharServer() ) + return 0; + + WFIFOHEAD(inter_fd,size); + WFIFOW(inter_fd,0) = 0x307f; + WFIFOW(inter_fd,2) = size; + memcpy(WFIFOP(inter_fd,4), ele, sizeof(struct s_elemental)); + WFIFOSET(inter_fd,size); + return 0; +} + +int intif_parse_elemental_saved(int fd) +{ + if( RFIFOB(fd,2) != 1 ) + ShowError("Elemental data save failure\n"); + + return 0; +} + +void intif_request_accinfo( int u_fd, int aid, int group_id, char* query ) { + + + WFIFOHEAD(inter_fd,2 + 4 + 4 + 4 + NAME_LENGTH); + + WFIFOW(inter_fd,0) = 0x3007; + WFIFOL(inter_fd,2) = u_fd; + WFIFOL(inter_fd,6) = aid; + WFIFOL(inter_fd,10) = group_id; + safestrncpy((char *)WFIFOP(inter_fd,14), query, NAME_LENGTH); + + WFIFOSET(inter_fd,2 + 4 + 4 + 4 + NAME_LENGTH); + + return; +} + +void intif_parse_MessageToFD(int fd) { + int u_fd = RFIFOL(fd,4); + + if( session[u_fd] && session[u_fd]->session_data ) { + int aid = RFIFOL(fd,8); + struct map_session_data * sd = session[u_fd]->session_data; + /* matching e.g. previous fd owner didn't dc during request or is still the same */ + if( sd->bl.id == aid ) { + char msg[512]; + safestrncpy(msg, (char*)RFIFOP(fd,12), RFIFOW(fd,2) - 12); + clif_displaymessage(u_fd,msg); + } + + } + + return; +} + +//----------------------------------------------------------------- +// Communication from the inter server +// Return a 0 (false) if there were any errors. +// 1, 2 if there are not enough to return the length of the packet if the packet processing +int intif_parse(int fd) +{ + int packet_len, cmd; + cmd = RFIFOW(fd,0); + // Verify ID of the packet + if(cmd<0x3800 || cmd>=0x3800+(sizeof(packet_len_table)/sizeof(packet_len_table[0])) || + packet_len_table[cmd-0x3800]==0){ + return 0; + } + // Check the length of the packet + packet_len = packet_len_table[cmd-0x3800]; + if(packet_len==-1){ + if(RFIFOREST(fd)<4) + return 2; + packet_len = RFIFOW(fd,2); + } + if((int)RFIFOREST(fd)<packet_len){ + return 2; + } + // Processing branch + switch(cmd){ + case 0x3800: + if (RFIFOL(fd,4) == 0xFF000000) //Normal announce. + clif_broadcast(NULL, (char *) RFIFOP(fd,16), packet_len-16, 0, ALL_CLIENT); + else if (RFIFOL(fd,4) == 0xFE000000) //Main chat message [LuzZza] + clif_MainChatMessage((char *)RFIFOP(fd,16)); + else //Color announce. + clif_broadcast2(NULL, (char *) RFIFOP(fd,16), packet_len-16, RFIFOL(fd,4), RFIFOW(fd,8), RFIFOW(fd,10), RFIFOW(fd,12), RFIFOW(fd,14), ALL_CLIENT); + break; + case 0x3801: intif_parse_WisMessage(fd); break; + case 0x3802: intif_parse_WisEnd(fd); break; + case 0x3803: mapif_parse_WisToGM(fd); break; + case 0x3804: intif_parse_Registers(fd); break; + case 0x3806: intif_parse_ChangeNameOk(fd); break; + case 0x3807: intif_parse_MessageToFD(fd); break; + case 0x3818: intif_parse_LoadGuildStorage(fd); break; + case 0x3819: intif_parse_SaveGuildStorage(fd); break; + case 0x3820: intif_parse_PartyCreated(fd); break; + case 0x3821: intif_parse_PartyInfo(fd); break; + case 0x3822: intif_parse_PartyMemberAdded(fd); break; + case 0x3823: intif_parse_PartyOptionChanged(fd); break; + case 0x3824: intif_parse_PartyMemberWithdraw(fd); break; + case 0x3825: intif_parse_PartyMove(fd); break; + case 0x3826: intif_parse_PartyBroken(fd); break; + case 0x3827: intif_parse_PartyMessage(fd); break; + case 0x3830: intif_parse_GuildCreated(fd); break; + case 0x3831: intif_parse_GuildInfo(fd); break; + case 0x3832: intif_parse_GuildMemberAdded(fd); break; + case 0x3834: intif_parse_GuildMemberWithdraw(fd); break; + case 0x3835: intif_parse_GuildMemberInfoShort(fd); break; + case 0x3836: intif_parse_GuildBroken(fd); break; + case 0x3837: intif_parse_GuildMessage(fd); break; + case 0x3839: intif_parse_GuildBasicInfoChanged(fd); break; + case 0x383a: intif_parse_GuildMemberInfoChanged(fd); break; + case 0x383b: intif_parse_GuildPosition(fd); break; + case 0x383c: intif_parse_GuildSkillUp(fd); break; + case 0x383d: intif_parse_GuildAlliance(fd); break; + case 0x383e: intif_parse_GuildNotice(fd); break; + case 0x383f: intif_parse_GuildEmblem(fd); break; + case 0x3840: intif_parse_GuildCastleDataLoad(fd); break; + case 0x3843: intif_parse_GuildMasterChanged(fd); break; + + //Quest system + case 0x3860: intif_parse_questlog(fd); break; + case 0x3861: intif_parse_questsave(fd); break; + +// Mail System + case 0x3848: intif_parse_Mail_inboxreceived(fd); break; + case 0x3849: intif_parse_Mail_new(fd); break; + case 0x384a: intif_parse_Mail_getattach(fd); break; + case 0x384b: intif_parse_Mail_delete(fd); break; + case 0x384c: intif_parse_Mail_return(fd); break; + case 0x384d: intif_parse_Mail_send(fd); break; +// Auction System + case 0x3850: intif_parse_Auction_results(fd); break; + case 0x3851: intif_parse_Auction_register(fd); break; + case 0x3852: intif_parse_Auction_cancel(fd); break; + case 0x3853: intif_parse_Auction_close(fd); break; + case 0x3854: intif_parse_Auction_message(fd); break; + case 0x3855: intif_parse_Auction_bid(fd); break; + +// Mercenary System + case 0x3870: intif_parse_mercenary_received(fd); break; + case 0x3871: intif_parse_mercenary_deleted(fd); break; + case 0x3872: intif_parse_mercenary_saved(fd); break; +// Elemental System + case 0x387c: intif_parse_elemental_received(fd); break; + case 0x387d: intif_parse_elemental_deleted(fd); break; + case 0x387e: intif_parse_elemental_saved(fd); break; + + case 0x3880: intif_parse_CreatePet(fd); break; + case 0x3881: intif_parse_RecvPetData(fd); break; + case 0x3882: intif_parse_SavePetOk(fd); break; + case 0x3883: intif_parse_DeletePetOk(fd); break; + case 0x3890: intif_parse_CreateHomunculus(fd); break; + case 0x3891: intif_parse_RecvHomunculusData(fd); break; + case 0x3892: intif_parse_SaveHomunculusOk(fd); break; + case 0x3893: intif_parse_DeleteHomunculusOk(fd); break; + default: + ShowError("intif_parse : unknown packet %d %x\n",fd,RFIFOW(fd,0)); + return 0; + } + // Skip packet + RFIFOSKIP(fd,packet_len); + return 1; +} diff --git a/src/map/intif.h b/src/map/intif.h new file mode 100644 index 000000000..65cc19830 --- /dev/null +++ b/src/map/intif.h @@ -0,0 +1,112 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTIF_H_ +#define _INFIF_H_ + +//#include "../common/mmo.h" +struct party_member; +struct guild_member; +struct guild_position; +struct s_pet; +struct s_homunculus; +struct s_mercenary; +struct s_elemental; +struct mail_message; +struct auction_data; + +int intif_parse(int fd); + +int intif_broadcast(const char* mes, int len, int type); +int intif_broadcast2(const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY); +int intif_main_message(struct map_session_data* sd, const char* message); + +int intif_wis_message(struct map_session_data *sd,char *nick,char *mes,int mes_len); +int intif_wis_message_to_gm(char *Wisp_name, int permission, char *mes); + +int intif_saveregistry(struct map_session_data *sd, int type); +int intif_request_registry(struct map_session_data *sd, int flag); + +int intif_request_guild_storage(int account_id, int guild_id); +int intif_send_guild_storage(int account_id, struct guild_storage *gstor); + + +int intif_create_party(struct party_member *member,char *name,int item,int item2); +int intif_request_partyinfo(int party_id, int char_id); + +int intif_party_addmember(int party_id,struct party_member *member); +int intif_party_changeoption(int party_id, int account_id, int exp, int item); +int intif_party_leave(int party_id,int account_id, int char_id); +int intif_party_changemap(struct map_session_data *sd, int online); +int intif_break_party(int party_id); +int intif_party_message(int party_id, int account_id, const char *mes,int len); +int intif_party_leaderchange(int party_id,int account_id,int char_id); + + +int intif_guild_create(const char *name, const struct guild_member *master); +int intif_guild_request_info(int guild_id); +int intif_guild_addmember(int guild_id, struct guild_member *m); +int intif_guild_leave(int guild_id, int account_id, int char_id, int flag, const char *mes); +int intif_guild_memberinfoshort(int guild_id, int account_id, int char_id, int online, int lv, int class_); +int intif_guild_break(int guild_id); +int intif_guild_message(int guild_id, int account_id, const char *mes, int len); +int intif_guild_change_gm(int guild_id, const char* name, int len); +int intif_guild_change_basicinfo(int guild_id, int type, const void *data, int len); +int intif_guild_change_memberinfo(int guild_id, int account_id, int char_id, int type, const void *data, int len); +int intif_guild_position(int guild_id, int idx, struct guild_position *p); +int intif_guild_skillup(int guild_id, uint16 skill_id, int account_id, int max); +int intif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag); +int intif_guild_notice(int guild_id, const char *mes1, const char *mes2); +int intif_guild_emblem(int guild_id, int len, const char *data); +int intif_guild_castle_dataload(int num, int *castle_ids); +int intif_guild_castle_datasave(int castle_id, int index, int value); + +int intif_create_pet(int account_id, int char_id, short pet_type, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name); +int intif_request_petdata(int account_id, int char_id, int pet_id); +int intif_save_petdata(int account_id, struct s_pet *p); +int intif_delete_petdata(int pet_id); +int intif_rename(struct map_session_data *sd, int type, char *name); +#define intif_rename_pc(sd, name) intif_rename(sd, 0, name) +#define intif_rename_pet(sd, name) intif_rename(sd, 1, name) +#define intif_rename_hom(sd, name) intif_rename(sd, 2, name) +int intif_homunculus_create(int account_id, struct s_homunculus *sh); +int intif_homunculus_requestload(int account_id, int homun_id); +int intif_homunculus_requestsave(int account_id, struct s_homunculus* sh); +int intif_homunculus_requestdelete(int homun_id); + +/******QUEST SYTEM*******/ +int intif_request_questlog(struct map_session_data * sd); +int intif_quest_save(struct map_session_data * sd); + +// MERCENARY SYSTEM +int intif_mercenary_create(struct s_mercenary *merc); +int intif_mercenary_request(int merc_id, int char_id); +int intif_mercenary_delete(int merc_id); +int intif_mercenary_save(struct s_mercenary *merc); + +// MAIL SYSTEM +int intif_Mail_requestinbox(int char_id, unsigned char flag); +int intif_Mail_read(int mail_id); +int intif_Mail_getattach(int char_id, int mail_id); +int intif_Mail_delete(int char_id, int mail_id); +int intif_Mail_return(int char_id, int mail_id); +int intif_Mail_send(int account_id, struct mail_message *msg); +// AUCTION SYSTEM +int intif_Auction_requestlist(int char_id, short type, int price, const char* searchtext, short page); +int intif_Auction_register(struct auction_data *auction); +int intif_Auction_cancel(int char_id, unsigned int auction_id); +int intif_Auction_close(int char_id, unsigned int auction_id); +int intif_Auction_bid(int char_id, const char* name, unsigned int auction_id, int bid); +// ELEMENTAL SYSTEM +int intif_elemental_create(struct s_elemental *ele); +int intif_elemental_request(int ele_id, int char_id); +int intif_elemental_delete(int ele_id); +int intif_elemental_save(struct s_elemental *ele); + +/* @accinfo */ +void intif_request_accinfo( int u_fd, int aid, int group_id, char* query ); + +int CheckForCharServer(void); + +#endif /* _INTIF_H_ */ diff --git a/src/map/itemdb.c b/src/map/itemdb.c new file mode 100644 index 000000000..68baae1e1 --- /dev/null +++ b/src/map/itemdb.c @@ -0,0 +1,1457 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "itemdb.h" +#include "map.h" +#include "battle.h" // struct battle_config +#include "script.h" // item script processing +#include "pc.h" // W_MUSICAL, W_WHIP + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct item_data* itemdb_array[MAX_ITEMDB]; +static DBMap* itemdb_other;// int nameid -> struct item_data* + +static struct item_group itemgroup_db[MAX_ITEMGROUP]; + +struct item_data dummy_item; //This is the default dummy item used for non-existant items. [Skotlex] + +/** + * Search for item name + * name = item alias, so we should find items aliases first. if not found then look for "jname" (full name) + * @see DBApply + */ +static int itemdb_searchname_sub(DBKey key, DBData *data, va_list ap) +{ + struct item_data *item = db_data2ptr(data), **dst, **dst2; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct item_data **); + dst2=va_arg(ap,struct item_data **); + if(item == &dummy_item) return 0; + + //Absolute priority to Aegis code name. + if (*dst != NULL) return 0; + if( strcmpi(item->name,str)==0 ) + *dst=item; + + //Second priority to Client displayed name. + if (*dst2 != NULL) return 0; + if( strcmpi(item->jname,str)==0 ) + *dst2=item; + return 0; +} + +/*========================================== + * Return item data from item name. (lookup) + *------------------------------------------*/ +struct item_data* itemdb_searchname(const char *str) +{ + struct item_data* item; + struct item_data* item2=NULL; + int i; + + for( i = 0; i < ARRAYLENGTH(itemdb_array); ++i ) + { + item = itemdb_array[i]; + if( item == NULL ) + continue; + + // Absolute priority to Aegis code name. + if( strcasecmp(item->name,str) == 0 ) + return item; + + //Second priority to Client displayed name. + if( strcasecmp(item->jname,str) == 0 ) + item2 = item; + } + + item = NULL; + itemdb_other->foreach(itemdb_other,itemdb_searchname_sub,str,&item,&item2); + return item?item:item2; +} + +/** + * @see DBMatcher + */ +static int itemdb_searchname_array_sub(DBKey key, DBData data, va_list ap) +{ + struct item_data *item = db_data2ptr(&data); + char *str; + str=va_arg(ap,char *); + if (item == &dummy_item) + return 1; //Invalid item. + if(stristr(item->jname,str)) + return 0; + if(stristr(item->name,str)) + return 0; + return strcmpi(item->jname,str); +} + +/*========================================== + * Founds up to N matches. Returns number of matches [Skotlex] + *------------------------------------------*/ +int itemdb_searchname_array(struct item_data** data, int size, const char *str) +{ + struct item_data* item; + int i; + int count=0; + + // Search in the array + for( i = 0; i < ARRAYLENGTH(itemdb_array); ++i ) + { + item = itemdb_array[i]; + if( item == NULL ) + continue; + + if( stristr(item->jname,str) || stristr(item->name,str) ) + { + if( count < size ) + data[count] = item; + ++count; + } + } + + // search in the db + if( count < size ) + { + DBData *db_data[MAX_SEARCH]; + int db_count = 0; + size -= count; + db_count = itemdb_other->getall(itemdb_other, (DBData**)&db_data, size, itemdb_searchname_array_sub, str); + for (i = 0; i < db_count; i++) + data[count++] = db_data2ptr(db_data[i]); + count += db_count; + } + return count; +} + + +/*========================================== + * Return a random item id from group. (takes into account % chance giving/tot group) + *------------------------------------------*/ +int itemdb_searchrandomid(int group) +{ + if(group<1 || group>=MAX_ITEMGROUP) { + ShowError("itemdb_searchrandomid: Invalid group id %d\n", group); + return UNKNOWN_ITEM_ID; + } + if (itemgroup_db[group].qty) + return itemgroup_db[group].nameid[rnd()%itemgroup_db[group].qty]; + + ShowError("itemdb_searchrandomid: No item entries for group id %d\n", group); + return UNKNOWN_ITEM_ID; +} + +/*========================================== + * Calculates total item-group related bonuses for the given item + *------------------------------------------*/ +int itemdb_group_bonus(struct map_session_data* sd, int itemid) +{ + int bonus = 0, i, j; + for (i=0; i < MAX_ITEMGROUP; i++) { + if (!sd->itemgrouphealrate[i]) + continue; + ARR_FIND( 0, itemgroup_db[i].qty, j, itemgroup_db[i].nameid[j] == itemid ); + if( j < itemgroup_db[i].qty ) + bonus += sd->itemgrouphealrate[i]; + } + return bonus; +} + +/// Searches for the item_data. +/// Returns the item_data or NULL if it does not exist. +struct item_data* itemdb_exists(int nameid) +{ + struct item_data* item; + + if( nameid >= 0 && nameid < ARRAYLENGTH(itemdb_array) ) + return itemdb_array[nameid]; + item = (struct item_data*)idb_get(itemdb_other,nameid); + if( item == &dummy_item ) + return NULL;// dummy data, doesn't exist + return item; +} + +/// Returns human readable name for given item type. +/// @param type Type id to retrieve name for ( IT_* ). +const char* itemdb_typename(int type) +{ + switch(type) + { + case IT_HEALING: return "Potion/Food"; + case IT_USABLE: return "Usable"; + case IT_ETC: return "Etc."; + case IT_WEAPON: return "Weapon"; + case IT_ARMOR: return "Armor"; + case IT_CARD: return "Card"; + case IT_PETEGG: return "Pet Egg"; + case IT_PETARMOR: return "Pet Accessory"; + case IT_AMMO: return "Arrow/Ammunition"; + case IT_DELAYCONSUME: return "Delay-Consume Usable"; + case IT_CASH: return "Cash Usable"; + } + return "Unknown Type"; +} + +/*========================================== + * Converts the jobid from the format in itemdb + * to the format used by the map server. [Skotlex] + *------------------------------------------*/ +static void itemdb_jobid2mapid(unsigned int *bclass, unsigned int jobmask) +{ + int i; + bclass[0]= bclass[1]= bclass[2]= 0; + //Base classes + if (jobmask & 1<<JOB_NOVICE) + { //Both Novice/Super-Novice are counted with the same ID + bclass[0] |= 1<<MAPID_NOVICE; + bclass[1] |= 1<<MAPID_NOVICE; + } + for (i = JOB_NOVICE+1; i <= JOB_THIEF; i++) + { + if (jobmask & 1<<i) + bclass[0] |= 1<<(MAPID_NOVICE+i); + } + //2-1 classes + if (jobmask & 1<<JOB_KNIGHT) + bclass[1] |= 1<<MAPID_SWORDMAN; + if (jobmask & 1<<JOB_PRIEST) + bclass[1] |= 1<<MAPID_ACOLYTE; + if (jobmask & 1<<JOB_WIZARD) + bclass[1] |= 1<<MAPID_MAGE; + if (jobmask & 1<<JOB_BLACKSMITH) + bclass[1] |= 1<<MAPID_MERCHANT; + if (jobmask & 1<<JOB_HUNTER) + bclass[1] |= 1<<MAPID_ARCHER; + if (jobmask & 1<<JOB_ASSASSIN) + bclass[1] |= 1<<MAPID_THIEF; + //2-2 classes + if (jobmask & 1<<JOB_CRUSADER) + bclass[2] |= 1<<MAPID_SWORDMAN; + if (jobmask & 1<<JOB_MONK) + bclass[2] |= 1<<MAPID_ACOLYTE; + if (jobmask & 1<<JOB_SAGE) + bclass[2] |= 1<<MAPID_MAGE; + if (jobmask & 1<<JOB_ALCHEMIST) + bclass[2] |= 1<<MAPID_MERCHANT; + if (jobmask & 1<<JOB_BARD) + bclass[2] |= 1<<MAPID_ARCHER; +// Bard/Dancer share the same slot now. +// if (jobmask & 1<<JOB_DANCER) +// bclass[2] |= 1<<MAPID_ARCHER; + if (jobmask & 1<<JOB_ROGUE) + bclass[2] |= 1<<MAPID_THIEF; + //Special classes that don't fit above. + if (jobmask & 1<<21) //Taekwon boy + bclass[0] |= 1<<MAPID_TAEKWON; + if (jobmask & 1<<22) //Star Gladiator + bclass[1] |= 1<<MAPID_TAEKWON; + if (jobmask & 1<<23) //Soul Linker + bclass[2] |= 1<<MAPID_TAEKWON; + if (jobmask & 1<<JOB_GUNSLINGER) + bclass[0] |= 1<<MAPID_GUNSLINGER; + if (jobmask & 1<<JOB_NINJA) + {bclass[0] |= 1<<MAPID_NINJA; + bclass[1] |= 1<<MAPID_NINJA;}//Kagerou/Oboro jobs can equip Ninja equips. [Rytech] + if (jobmask & 1<<26) //Bongun/Munak + bclass[0] |= 1<<MAPID_GANGSI; + if (jobmask & 1<<27) //Death Knight + bclass[1] |= 1<<MAPID_GANGSI; + if (jobmask & 1<<28) //Dark Collector + bclass[2] |= 1<<MAPID_GANGSI; + if (jobmask & 1<<29) //Kagerou / Oboro + bclass[1] |= 1<<MAPID_NINJA; +} + +static void create_dummy_data(void) +{ + memset(&dummy_item, 0, sizeof(struct item_data)); + dummy_item.nameid=500; + dummy_item.weight=1; + dummy_item.value_sell=1; + dummy_item.type=IT_ETC; //Etc item + safestrncpy(dummy_item.name,"UNKNOWN_ITEM",sizeof(dummy_item.name)); + safestrncpy(dummy_item.jname,"UNKNOWN_ITEM",sizeof(dummy_item.jname)); + dummy_item.view_id=UNKNOWN_ITEM_ID; +} + +static struct item_data* create_item_data(int nameid) +{ + struct item_data *id; + CREATE(id, struct item_data, 1); + id->nameid = nameid; + id->weight = 1; + id->type = IT_ETC; + return id; +} + +/*========================================== + * Loads (and creates if not found) an item from the db. + *------------------------------------------*/ +struct item_data* itemdb_load(int nameid) +{ + struct item_data *id; + + if( nameid >= 0 && nameid < ARRAYLENGTH(itemdb_array) ) + { + id = itemdb_array[nameid]; + if( id == NULL || id == &dummy_item ) + id = itemdb_array[nameid] = create_item_data(nameid); + return id; + } + + id = (struct item_data*)idb_get(itemdb_other, nameid); + if( id == NULL || id == &dummy_item ) + { + id = create_item_data(nameid); + idb_put(itemdb_other, nameid, id); + } + return id; +} + +/*========================================== + * Loads an item from the db. If not found, it will return the dummy item. + *------------------------------------------*/ +struct item_data* itemdb_search(int nameid) +{ + struct item_data* id; + if( nameid >= 0 && nameid < ARRAYLENGTH(itemdb_array) ) + id = itemdb_array[nameid]; + else + id = (struct item_data*)idb_get(itemdb_other, nameid); + + if( id == NULL ) + { + ShowWarning("itemdb_search: Item ID %d does not exists in the item_db. Using dummy data.\n", nameid); + id = &dummy_item; + dummy_item.nameid = nameid; + } + return id; +} + +/*========================================== + * Returns if given item is a player-equippable piece. + *------------------------------------------*/ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + switch (type) { + case IT_WEAPON: + case IT_ARMOR: + case IT_AMMO: + return 1; + default: + return 0; + } +} + +/*========================================== + * Alternate version of itemdb_isequip + *------------------------------------------*/ +int itemdb_isequip2(struct item_data *data) +{ + nullpo_ret(data); + switch(data->type) { + case IT_WEAPON: + case IT_ARMOR: + case IT_AMMO: + return 1; + default: + return 0; + } +} + +/*========================================== + * Returns if given item's type is stackable. + *------------------------------------------*/ +int itemdb_isstackable(int nameid) +{ + int type=itemdb_type(nameid); + switch(type) { + case IT_WEAPON: + case IT_ARMOR: + case IT_PETEGG: + case IT_PETARMOR: + return 0; + default: + return 1; + } +} + +/*========================================== + * Alternate version of itemdb_isstackable + *------------------------------------------*/ +int itemdb_isstackable2(struct item_data *data) +{ + nullpo_ret(data); + switch(data->type) { + case IT_WEAPON: + case IT_ARMOR: + case IT_PETEGG: + case IT_PETARMOR: + return 0; + default: + return 1; + } +} + + +/*========================================== + * Trade Restriction functions [Skotlex] + *------------------------------------------*/ +int itemdb_isdropable_sub(struct item_data *item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&1) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_cantrade_sub(struct item_data* item, int gmlv, int gmlv2) { + return (item && (!(item->flag.trade_restriction&2) || gmlv >= item->gm_lv_trade_override || gmlv2 >= item->gm_lv_trade_override)); +} + +int itemdb_canpartnertrade_sub(struct item_data* item, int gmlv, int gmlv2) { + return (item && (item->flag.trade_restriction&4 || gmlv >= item->gm_lv_trade_override || gmlv2 >= item->gm_lv_trade_override)); +} + +int itemdb_cansell_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&8) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_cancartstore_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&16) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_canstore_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&32) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_canguildstore_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&64) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_canmail_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&128) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_canauction_sub(struct item_data* item, int gmlv, int unused) { + return (item && (!(item->flag.trade_restriction&256) || gmlv >= item->gm_lv_trade_override)); +} + +int itemdb_isrestricted(struct item* item, int gmlv, int gmlv2, int (*func)(struct item_data*, int, int)) +{ + struct item_data* item_data = itemdb_search(item->nameid); + int i; + + if (!func(item_data, gmlv, gmlv2)) + return 0; + + if(item_data->slot == 0 || itemdb_isspecial(item->card[0])) + return 1; + + for(i = 0; i < item_data->slot; i++) { + if (!item->card[i]) continue; + if (!func(itemdb_search(item->card[i]), gmlv, gmlv2)) + return 0; + } + return 1; +} + +/*========================================== + * Specifies if item-type should drop unidentified. + *------------------------------------------*/ +int itemdb_isidentified(int nameid) +{ + int type=itemdb_type(nameid); + switch (type) { + case IT_WEAPON: + case IT_ARMOR: + case IT_PETARMOR: + return 0; + default: + return 1; + } +} + +/*========================================== + * Search by name for the override flags available items + * (Give item another sprite) + *------------------------------------------*/ +static bool itemdb_read_itemavail(char* str[], int columns, int current) +{// <nameid>,<sprite> + int nameid, sprite; + struct item_data *id; + + nameid = atoi(str[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_itemavail: Invalid item id %d.\n", nameid); + return false; + } + + sprite = atoi(str[1]); + + if( sprite > 0 ) + { + id->flag.available = 1; + id->view_id = sprite; + } + else + { + id->flag.available = 0; + } + + return true; +} + +/*========================================== + * read item group data + *------------------------------------------*/ +static void itemdb_read_itemgroup_sub(const char* filename) +{ + FILE *fp; + char line[1024]; + int ln=0; + int groupid,j,k,nameid; + char *str[3],*p; + char w1[1024], w2[1024]; + + if( (fp=fopen(filename,"r"))==NULL ){ + ShowError("can't read %s\n", filename); + return; + } + + while(fgets(line, sizeof(line), fp)) + { + ln++; + if(line[0]=='/' && line[1]=='/') + continue; + if(strstr(line,"import")) { + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2 && + strcmpi(w1, "import") == 0) { + itemdb_read_itemgroup_sub(w2); + continue; + } + } + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(str[0]==NULL) + continue; + if (j<3) { + if (j>1) //Or else it barks on blank lines... + ShowWarning("itemdb_read_itemgroup: Insufficient fields for entry at %s:%d\n", filename, ln); + continue; + } + groupid = atoi(str[0]); + if (groupid < 0 || groupid >= MAX_ITEMGROUP) { + ShowWarning("itemdb_read_itemgroup: Invalid group %d in %s:%d\n", groupid, filename, ln); + continue; + } + nameid = atoi(str[1]); + if (!itemdb_exists(nameid)) { + ShowWarning("itemdb_read_itemgroup: Non-existant item %d in %s:%d\n", nameid, filename, ln); + continue; + } + k = atoi(str[2]); + if (itemgroup_db[groupid].qty+k >= MAX_RANDITEM) { + ShowWarning("itemdb_read_itemgroup: Group %d is full (%d entries) in %s:%d\n", groupid, MAX_RANDITEM, filename, ln); + continue; + } + for(j=0;j<k;j++) + itemgroup_db[groupid].nameid[itemgroup_db[groupid].qty++] = nameid; + } + fclose(fp); + return; +} + +static void itemdb_read_itemgroup(void) +{ + char path[256]; + snprintf(path, 255, "%s/"DBPATH"item_group_db.txt", db_path); + memset(&itemgroup_db, 0, sizeof(itemgroup_db)); + itemdb_read_itemgroup_sub(path); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", "item_group_db.txt"); + return; +} + +/*========================================== + * Read item forbidden by mapflag (can't equip item) + *------------------------------------------*/ +static bool itemdb_read_noequip(char* str[], int columns, int current) +{// <nameid>,<mode> + int nameid; + struct item_data *id; + + nameid = atoi(str[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_noequip: Invalid item id %d.\n", nameid); + return false; + } + + id->flag.no_equip |= atoi(str[1]); + + return true; +} + +/*========================================== + * Reads item trade restrictions [Skotlex] + *------------------------------------------*/ +static bool itemdb_read_itemtrade(char* str[], int columns, int current) +{// <nameid>,<mask>,<gm level> + int nameid, flag, gmlv; + struct item_data *id; + + nameid = atoi(str[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + //ShowWarning("itemdb_read_itemtrade: Invalid item id %d.\n", nameid); + //return false; + // FIXME: item_trade.txt contains items, which are commented in item database. + return true; + } + + flag = atoi(str[1]); + gmlv = atoi(str[2]); + + if( flag < 0 || flag > 511 ) {//Check range + ShowWarning("itemdb_read_itemtrade: Invalid trading mask %d for item id %d.\n", flag, nameid); + return false; + } + if( gmlv < 1 ) + { + ShowWarning("itemdb_read_itemtrade: Invalid override GM level %d for item id %d.\n", gmlv, nameid); + return false; + } + + id->flag.trade_restriction = flag; + id->gm_lv_trade_override = gmlv; + + return true; +} + +/*========================================== + * Reads item delay amounts [Paradox924X] + *------------------------------------------*/ +static bool itemdb_read_itemdelay(char* str[], int columns, int current) +{// <nameid>,<delay> + int nameid, delay; + struct item_data *id; + + nameid = atoi(str[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_itemdelay: Invalid item id %d.\n", nameid); + return false; + } + + delay = atoi(str[1]); + + if( delay < 0 ) + { + ShowWarning("itemdb_read_itemdelay: Invalid delay %d for item id %d.\n", id->delay, nameid); + return false; + } + + id->delay = delay; + + return true; +} + +/*================================================================== + * Reads item stacking restrictions + *----------------------------------------------------------------*/ +static bool itemdb_read_stack(char* fields[], int columns, int current) +{// <item id>,<stack limit amount>,<type> + unsigned short nameid, amount; + unsigned int type; + struct item_data* id; + + nameid = (unsigned short)strtoul(fields[0], NULL, 10); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_stack: Unknown item id '%hu'.\n", nameid); + return false; + } + + if( !itemdb_isstackable2(id) ) + { + ShowWarning("itemdb_read_stack: Item id '%hu' is not stackable.\n", nameid); + return false; + } + + amount = (unsigned short)strtoul(fields[1], NULL, 10); + type = strtoul(fields[2], NULL, 10); + + if( !amount ) + {// ignore + return true; + } + + id->stack.amount = amount; + id->stack.inventory = (type&1)!=0; + id->stack.cart = (type&2)!=0; + id->stack.storage = (type&4)!=0; + id->stack.guildstorage = (type&8)!=0; + + return true; +} + + +/// Reads items allowed to be sold in buying stores +static bool itemdb_read_buyingstore(char* fields[], int columns, int current) +{// <nameid> + int nameid; + struct item_data* id; + + nameid = atoi(fields[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_buyingstore: Invalid item id %d.\n", nameid); + return false; + } + + if( !itemdb_isstackable2(id) ) + { + ShowWarning("itemdb_read_buyingstore: Non-stackable item id %d cannot be enabled for buying store.\n", nameid); + return false; + } + + id->flag.buyingstore = true; + + return true; +} +/** + * @return: amount of retrieved entries. + **/ +int itemdb_combo_split_atoi (char *str, int *val) { + int i; + + for (i=0; i<MAX_ITEMS_PER_COMBO; i++) { + if (!str) break; + + val[i] = atoi(str); + + str = strchr(str,':'); + + if (str) + *str++=0; + } + + if( i == 0 ) //No data found. + return 0; + + return i; +} +/** + * <combo{:combo{:combo:{..}}}>,<{ script }> + **/ +void itemdb_read_combos() { + uint32 lines = 0, count = 0; + char line[1024]; + + char path[256]; + FILE* fp; + + sprintf(path, "%s/%s", db_path, DBPATH"item_combo_db.txt"); + + if ((fp = fopen(path, "r")) == NULL) { + ShowError("itemdb_read_combos: File not found \"%s\".\n", path); + return; + } + + // process rows one by one + while(fgets(line, sizeof(line), fp)) { + char *str[2], *p; + + lines++; + + if (line[0] == '/' && line[1] == '/') + continue; + + memset(str, 0, sizeof(str)); + + p = line; + + p = trim(p); + + if (*p == '\0') + continue;// empty line + + if (!strchr(p,',')) + { + /* is there even a single column? */ + ShowError("itemdb_read_combos: Insufficient columns in line %d of \"%s\", skipping.\n", lines, path); + continue; + } + + str[0] = p; + p = strchr(p,','); + *p = '\0'; + p++; + + str[1] = p; + p = strchr(p,','); + p++; + + if (str[1][0] != '{') { + ShowError("itemdb_read_combos(#1): Invalid format (Script column) in line %d of \"%s\", skipping.\n", lines, path); + continue; + } + + /* no ending key anywhere (missing \}\) */ + if ( str[1][strlen(str[1])-1] != '}' ) { + ShowError("itemdb_read_combos(#2): Invalid format (Script column) in line %d of \"%s\", skipping.\n", lines, path); + continue; + } else { + int items[MAX_ITEMS_PER_COMBO]; + int v = 0, retcount = 0; + struct item_data * id = NULL; + int idx = 0; + + if((retcount = itemdb_combo_split_atoi(str[0], items)) < 2) { + ShowError("itemdb_read_combos: line %d of \"%s\" doesn't have enough items to make for a combo (min:2), skipping.\n", lines, path); + continue; + } + + /* validate */ + for(v = 0; v < retcount; v++) { + if( !itemdb_exists(items[v]) ) { + ShowError("itemdb_read_combos: line %d of \"%s\" contains unknown item ID %d, skipping.\n", lines, path,items[v]); + break; + } + } + /* failed at some item */ + if( v < retcount ) + continue; + + id = itemdb_exists(items[0]); + + idx = id->combos_count; + + /* first entry, create */ + if( id->combos == NULL ) { + CREATE(id->combos, struct item_combo*, 1); + id->combos_count = 1; + } else { + RECREATE(id->combos, struct item_combo*, ++id->combos_count); + } + + CREATE(id->combos[idx],struct item_combo,1); + + id->combos[idx]->nameid = aMalloc( retcount * sizeof(unsigned short) ); + id->combos[idx]->count = retcount; + id->combos[idx]->script = parse_script(str[1], path, lines, 0); + id->combos[idx]->id = count; + id->combos[idx]->isRef = false; + /* populate ->nameid field */ + for( v = 0; v < retcount; v++ ) { + id->combos[idx]->nameid[v] = items[v]; + } + + /* populate the children to refer to this combo */ + for( v = 1; v < retcount; v++ ) { + struct item_data * it = NULL; + int index; + + it = itemdb_exists(items[v]); + + index = it->combos_count; + + if( it->combos == NULL ) { + CREATE(it->combos, struct item_combo*, 1); + it->combos_count = 1; + } else { + RECREATE(it->combos, struct item_combo*, ++it->combos_count); + } + + CREATE(it->combos[index],struct item_combo,1); + + /* we copy previously alloc'd pointers and just set it to reference */ + memcpy(it->combos[index],id->combos[idx],sizeof(struct item_combo)); + /* we flag this way to ensure we don't double-dealloc same data */ + it->combos[index]->isRef = true; + } + + } + + count++; + } + + fclose(fp); + + ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"item_combo_db"CL_RESET"'.\n", count); + + return; +} + + + +/*====================================== + * Applies gender restrictions according to settings. [Skotlex] + *======================================*/ +static int itemdb_gendercheck(struct item_data *id) +{ + if (id->nameid == WEDDING_RING_M) //Grom Ring + return 1; + if (id->nameid == WEDDING_RING_F) //Bride Ring + return 0; + if (id->look == W_MUSICAL && id->type == IT_WEAPON) //Musical instruments are always male-only + return 1; + if (id->look == W_WHIP && id->type == IT_WEAPON) //Whips are always female-only + return 0; + + return (battle_config.ignore_items_gender) ? 2 : id->sex; +} +/** + * [RRInd] + * For backwards compatibility, in Renewal mode, MATK from weapons comes from the atk slot + * We use a ':' delimiter which, if not found, assumes the weapon does not provide any matk. + **/ +void itemdb_re_split_atoi(char *str, int *atk, int *matk) { + int i, val[2]; + + for (i=0; i<2; i++) { + if (!str) break; + val[i] = atoi(str); + str = strchr(str,':'); + if (str) + *str++=0; + } + if( i == 0 ) { + *atk = *matk = 0; + return;//no data found + } + if( i == 1 ) {//Single Value, we assume it's the ATK + *atk = val[0]; + *matk = 0; + return; + } + //We assume we have 2 values. + *atk = val[0]; + *matk = val[1]; + return; +} +/*========================================== + * processes one itemdb entry + *------------------------------------------*/ +static bool itemdb_parse_dbrow(char** str, const char* source, int line, int scriptopt) { + /* + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ + | id | name_english | name_japanese | type | price_buy | price_sell | weight | attack | defence | range | slots | equip_jobs | equip_upper | equip_genders | equip_locations | weapon_level | equip_level | refineable | view | script | equip_script | unequip_script | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ + */ + int nameid; + struct item_data* id; + + nameid = atoi(str[0]); + if( nameid <= 0 ) + { + ShowWarning("itemdb_parse_dbrow: Invalid id %d in line %d of \"%s\", skipping.\n", nameid, line, source); + return false; + } + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Job Upper,Gender,Loc,wLV,eLV,refineable,View + id = itemdb_load(nameid); + safestrncpy(id->name, str[1], sizeof(id->name)); + safestrncpy(id->jname, str[2], sizeof(id->jname)); + + id->type = atoi(str[3]); + + if( id->type < 0 || id->type == IT_UNKNOWN || id->type == IT_UNKNOWN2 || ( id->type > IT_DELAYCONSUME && id->type < IT_CASH ) || id->type >= IT_MAX ) + {// catch invalid item types + ShowWarning("itemdb_parse_dbrow: Invalid item type %d for item %d. IT_ETC will be used.\n", id->type, nameid); + id->type = IT_ETC; + } + + if (id->type == IT_DELAYCONSUME) + { //Items that are consumed only after target confirmation + id->type = IT_USABLE; + id->flag.delay_consume = 1; + } else //In case of an itemdb reload and the item type changed. + id->flag.delay_consume = 0; + + //When a particular price is not given, we should base it off the other one + //(it is important to make a distinction between 'no price' and 0z) + if ( str[4][0] ) + id->value_buy = atoi(str[4]); + else + id->value_buy = atoi(str[5]) * 2; + + if ( str[5][0] ) + id->value_sell = atoi(str[5]); + else + id->value_sell = id->value_buy / 2; + /* + if ( !str[4][0] && !str[5][0]) + { + ShowWarning("itemdb_parse_dbrow: No buying/selling price defined for item %d (%s), using 20/10z\n", nameid, id->jname); + id->value_buy = 20; + id->value_sell = 10; + } else + */ + if (id->value_buy/124. < id->value_sell/75.) + ShowWarning("itemdb_parse_dbrow: Buying/Selling [%d/%d] price of item %d (%s) allows Zeny making exploit through buying/selling at discounted/overcharged prices!\n", + id->value_buy, id->value_sell, nameid, id->jname); + + id->weight = atoi(str[6]); +#ifdef RENEWAL + itemdb_re_split_atoi(str[7],&id->atk,&id->matk); +#else + id->atk = atoi(str[7]); +#endif + id->def = atoi(str[8]); + id->range = atoi(str[9]); + id->slot = atoi(str[10]); + + if (id->slot > MAX_SLOTS) + { + ShowWarning("itemdb_parse_dbrow: Item %d (%s) specifies %d slots, but the server only supports up to %d. Using %d slots.\n", nameid, id->jname, id->slot, MAX_SLOTS, MAX_SLOTS); + id->slot = MAX_SLOTS; + } + + itemdb_jobid2mapid(id->class_base, (unsigned int)strtoul(str[11],NULL,0)); + id->class_upper = atoi(str[12]); + id->sex = atoi(str[13]); + id->equip = atoi(str[14]); + + if (!id->equip && itemdb_isequip2(id)) + { + ShowWarning("Item %d (%s) is an equipment with no equip-field! Making it an etc item.\n", nameid, id->jname); + id->type = IT_ETC; + } + + id->wlv = cap_value(atoi(str[15]), REFINE_TYPE_ARMOR, REFINE_TYPE_MAX); +#ifdef RENEWAL + itemdb_re_split_atoi(str[16],&id->elv,&id->elvmax); +#else + id->elv = atoi(str[16]); +#endif + id->flag.no_refine = atoi(str[17]) ? 0 : 1; //FIXME: verify this + id->look = atoi(str[18]); + + id->flag.available = 1; + id->view_id = 0; + id->sex = itemdb_gendercheck(id); //Apply gender filtering. + + if (id->script) { + script_free_code(id->script); + id->script = NULL; + } + if (id->equip_script) { + script_free_code(id->equip_script); + id->equip_script = NULL; + } + if (id->unequip_script) { + script_free_code(id->unequip_script); + id->unequip_script = NULL; + } + + if (*str[19]) + id->script = parse_script(str[19], source, line, scriptopt); + if (*str[20]) + id->equip_script = parse_script(str[20], source, line, scriptopt); + if (*str[21]) + id->unequip_script = parse_script(str[21], source, line, scriptopt); + + return true; +} + +/*========================================== + * Reading item from item db + * item_db2 overwriting item_db + *------------------------------------------*/ +static int itemdb_readdb(void) +{ + const char* filename[] = { + DBPATH"item_db.txt", + "item_db2.txt" }; + + int fi; + + for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) { + uint32 lines = 0, count = 0; + char line[1024]; + + char path[256]; + FILE* fp; + + sprintf(path, "%s/%s", db_path, filename[fi]); + fp = fopen(path, "r"); + if( fp == NULL ) { + ShowWarning("itemdb_readdb: File not found \"%s\", skipping.\n", path); + continue; + } + + // process rows one by one + while(fgets(line, sizeof(line), fp)) + { + char *str[32], *p; + int i; + lines++; + if(line[0] == '/' && line[1] == '/') + continue; + memset(str, 0, sizeof(str)); + + p = line; + while( ISSPACE(*p) ) + ++p; + if( *p == '\0' ) + continue;// empty line + for( i = 0; i < 19; ++i ) + { + str[i] = p; + p = strchr(p,','); + if( p == NULL ) + break;// comma not found + *p = '\0'; + ++p; + } + + if( p == NULL ) + { + ShowError("itemdb_readdb: Insufficient columns in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + + // Script + if( *p != '{' ) + { + ShowError("itemdb_readdb: Invalid format (Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + str[19] = p; + p = strstr(p+1,"},"); + if( p == NULL ) + { + ShowError("itemdb_readdb: Invalid format (Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + p[1] = '\0'; + p += 2; + + // OnEquip_Script + if( *p != '{' ) + { + ShowError("itemdb_readdb: Invalid format (OnEquip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + str[20] = p; + p = strstr(p+1,"},"); + if( p == NULL ) + { + ShowError("itemdb_readdb: Invalid format (OnEquip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + p[1] = '\0'; + p += 2; + + // OnUnequip_Script (last column) + if( *p != '{' ) + { + ShowError("itemdb_readdb: Invalid format (OnUnequip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + str[21] = p; + + if ( str[21][strlen(str[21])-2] != '}' ) { + /* lets count to ensure it's not something silly e.g. a extra space at line ending */ + int v, lcurly = 0, rcurly = 0; + + for( v = 0; v < strlen(str[21]); v++ ) { + if( str[21][v] == '{' ) + lcurly++; + else if ( str[21][v] == '}' ) + rcurly++; + } + + if( lcurly != rcurly ) { + ShowError("itemdb_readdb: Mismatching curly braces in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); + continue; + } + } + + if (!itemdb_parse_dbrow(str, path, lines, 0)) + continue; + + count++; + } + + fclose(fp); + + ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename[fi]); + } + + return 0; +} + +/*====================================== + * item_db table reading + *======================================*/ +static int itemdb_read_sqldb(void) { + + const char* item_db_name[] = { + #ifdef RENEWAL + item_db_re_db, + #else + item_db_db, + #endif + item_db2_db }; + int fi; + + for( fi = 0; fi < ARRAYLENGTH(item_db_name); ++fi ) { + uint32 lines = 0, count = 0; + + // retrieve all rows from the item database + if( SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", item_db_name[fi]) ) { + Sql_ShowDebug(mmysql_handle); + continue; + } + + // process rows one by one + while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) {// wrap the result into a TXT-compatible format + char* str[22]; + char* dummy = ""; + int i; + ++lines; + for( i = 0; i < 22; ++i ) { + Sql_GetData(mmysql_handle, i, &str[i], NULL); + if( str[i] == NULL ) + str[i] = dummy; // get rid of NULL columns + } + + if (!itemdb_parse_dbrow(str, item_db_name[fi], lines, SCRIPT_IGNORE_EXTERNAL_BRACKETS)) + continue; + ++count; + } + + // free the query result + Sql_FreeResult(mmysql_handle); + + ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, item_db_name[fi]); + } + + return 0; +} + +/*========================================== +* Unique item ID function +* Only one operation by once +* Flag: +* 0 return new id +* 1 set new value, checked with current value +* 2 set new value bypassing anything +* 3/other return last value +*------------------------------------------*/ +uint64 itemdb_unique_id(int8 flag, int64 value) { + static uint64 item_uid = 0; + + if(flag) + { + if(flag == 1) + { if(item_uid < value) + return (item_uid = value); + }else if(flag == 2) + return (item_uid = value); + + return item_uid; + } + + return ++item_uid; +} +int itemdb_uid_load(){ + + char * uid; + if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT `value` FROM `interreg` WHERE `varname`='unique_id'")) + Sql_ShowDebug(mmysql_handle); + + if( SQL_SUCCESS != Sql_NextRow(mmysql_handle) ) + { + ShowError("itemdb_uid_load: Unable to fetch unique_id data\n"); + Sql_FreeResult(mmysql_handle); + return -1; + } + + Sql_GetData(mmysql_handle, 0, &uid, NULL); + itemdb_unique_id(1, (uint64)strtoull(uid, NULL, 10)); + Sql_FreeResult(mmysql_handle); + + return 0; +} + +/*==================================== + * read all item-related databases + *------------------------------------*/ +static void itemdb_read(void) { + + if (db_use_sqldbs) + itemdb_read_sqldb(); + else + itemdb_readdb(); + + itemdb_read_combos(); + itemdb_read_itemgroup(); + sv_readdb(db_path, "item_avail.txt", ',', 2, 2, -1, &itemdb_read_itemavail); + sv_readdb(db_path, DBPATH"item_noequip.txt", ',', 2, 2, -1, &itemdb_read_noequip); + sv_readdb(db_path, DBPATH"item_trade.txt", ',', 3, 3, -1, &itemdb_read_itemtrade); + sv_readdb(db_path, "item_delay.txt", ',', 2, 2, -1, &itemdb_read_itemdelay); + sv_readdb(db_path, "item_stack.txt", ',', 3, 3, -1, &itemdb_read_stack); + sv_readdb(db_path, DBPATH"item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore); + + itemdb_uid_load(); +} + +/*========================================== + * Initialize / Finalize + *------------------------------------------*/ + +/// Destroys the item_data. +static void destroy_item_data(struct item_data* self, int free_self) +{ + if( self == NULL ) + return; + // free scripts + if( self->script ) + script_free_code(self->script); + if( self->equip_script ) + script_free_code(self->equip_script); + if( self->unequip_script ) + script_free_code(self->unequip_script); + if( self->combos_count ) { + int i; + for( i = 0; i < self->combos_count; i++ ) { + if( !self->combos[i]->isRef ) { + aFree(self->combos[i]->nameid); + script_free_code(self->combos[i]->script); + } + aFree(self->combos[i]); + } + aFree(self->combos); + } +#if defined(DEBUG) + // trash item + memset(self, 0xDD, sizeof(struct item_data)); +#endif + // free self + if( free_self ) + aFree(self); +} + +/** + * @see DBApply + */ +static int itemdb_final_sub(DBKey key, DBData *data, va_list ap) +{ + struct item_data *id = db_data2ptr(data); + + if( id != &dummy_item ) + destroy_item_data(id, 1); + + return 0; +} + +void itemdb_reload(void) +{ + struct s_mapiterator* iter; + struct map_session_data* sd; + + int i,d,k; + + // clear the previous itemdb data + for( i = 0; i < ARRAYLENGTH(itemdb_array); ++i ) + if( itemdb_array[i] ) + destroy_item_data(itemdb_array[i], 1); + + itemdb_other->clear(itemdb_other, itemdb_final_sub); + + memset(itemdb_array, 0, sizeof(itemdb_array)); + + // read new data + itemdb_read(); + + //Epoque's awesome @reloaditemdb fix - thanks! [Ind] + //- Fixes the need of a @reloadmobdb after a @reloaditemdb to re-link monster drop data + for( i = 0; i < MAX_MOB_DB; i++ ) { + struct mob_db *entry; + if( !((i < 1324 || i > 1363) && (i < 1938 || i > 1946)) ) + continue; + entry = mob_db(i); + for(d = 0; d < MAX_MOB_DROP; d++) { + struct item_data *id; + if( !entry->dropitem[d].nameid ) + continue; + id = itemdb_search(entry->dropitem[d].nameid); + + for (k = 0; k < MAX_SEARCH; k++) { + if (id->mob[k].chance <= entry->dropitem[d].p) + break; + } + + if (k == MAX_SEARCH) + continue; + + if (id->mob[k].id != i) + memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); + id->mob[k].chance = entry->dropitem[d].p; + id->mob[k].id = i; + } + } + + // readjust itemdb pointer cache for each player + iter = mapit_geteachpc(); + for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) { + memset(sd->item_delay, 0, sizeof(sd->item_delay)); // reset item delays + pc_setinventorydata(sd); + /* clear combo bonuses */ + if( sd->combos.count ) { + aFree(sd->combos.bonus); + aFree(sd->combos.id); + sd->combos.bonus = NULL; + sd->combos.id = NULL; + sd->combos.count = 0; + if( pc_load_combo(sd) > 0 ) + status_calc_pc(sd,0); + } + + } + mapit_free(iter); +} + +void do_final_itemdb(void) +{ + int i; + + for( i = 0; i < ARRAYLENGTH(itemdb_array); ++i ) + if( itemdb_array[i] ) + destroy_item_data(itemdb_array[i], 1); + + itemdb_other->destroy(itemdb_other, itemdb_final_sub); + destroy_item_data(&dummy_item, 0); +} + +int do_init_itemdb(void) { + memset(itemdb_array, 0, sizeof(itemdb_array)); + itemdb_other = idb_alloc(DB_OPT_BASE); + create_dummy_data(); //Dummy data item. + itemdb_read(); + + return 0; +} diff --git a/src/map/itemdb.h b/src/map/itemdb.h new file mode 100644 index 000000000..e308b248b --- /dev/null +++ b/src/map/itemdb.h @@ -0,0 +1,229 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ + +#include "../common/db.h" +#include "../common/mmo.h" // ITEM_NAME_LENGTH +#include "map.h" + +// 32k array entries in array (the rest goes to the db) +#define MAX_ITEMDB 0x8000 + +#define MAX_RANDITEM 11000 + +// The maximum number of item delays +#define MAX_ITEMDELAYS 10 + +#define MAX_SEARCH 5 //Designed for search functions, species max number of matches to display. + +/* maximum amount of items a combo may require */ +#define MAX_ITEMS_PER_COMBO 6 + +enum item_itemid { + ITEMID_EMPERIUM = 714, + ITEMID_YELLOW_GEMSTONE = 715, + ITEMID_RED_GEMSTONE = 716, + ITEMID_BLUE_GEMSTONE = 717, + ITEMID_TRAP = 1065, + ITEMID_STONE = 7049, + ITEMID_SKULL_ = 7420, + ITEMID_TOKEN_OF_SIEGFRIED = 7621, + ITEMID_TRAP_ALLOY = 7940, + ITEMID_ANCILLA = 12333, + ITEMID_REINS_OF_MOUNT = 12622, +}; + +/** + * Rune Knight + **/ + +enum { + ITEMID_NAUTHIZ = 12725, + ITEMID_RAIDO, + ITEMID_BERKANA, + ITEMID_ISA, + ITEMID_OTHILA, + ITEMID_URUZ, + ITEMID_THURISAZ, + ITEMID_WYRD, + ITEMID_HAGALAZ, +} rune_list; + +/** + * Mechanic + **/ +enum { + ITEMID_ACCELERATOR = 2800, + ITEMID_HOVERING_BOOSTER, + ITEMID_SUICIDAL_DEVICE, + ITEMID_SHAPE_SHIFTER, + ITEMID_COOLING_DEVICE, + ITEMID_MAGNETIC_FIELD_GENERATOR, + ITEMID_BARRIER_BUILDER, + ITEMID_REPAIR_KIT, + ITEMID_CAMOUFLAGE_GENERATOR, + ITEMID_HIGH_QUALITY_COOLER, + ITEMID_SPECIAL_COOLER, + } mecha_item_list; + +//The only item group required by the code to be known. See const.txt for the full list. +#define IG_FINDINGORE 6 +#define IG_POTION 37 +//The max. item group count (increase this when needed). +#define MAX_ITEMGROUP 63 + +#define CARD0_FORGE 0x00FF +#define CARD0_CREATE 0x00FE +#define CARD0_PET ((short)0xFF00) + +//Marks if the card0 given is "special" (non-item id used to mark pets/created items. [Skotlex] +#define itemdb_isspecial(i) (i == CARD0_FORGE || i == CARD0_CREATE || i == CARD0_PET) + +//Use apple for unknown items. +#define UNKNOWN_ITEM_ID 512 + +struct item_data { + uint16 nameid; + char name[ITEM_NAME_LENGTH],jname[ITEM_NAME_LENGTH]; + + //Do not add stuff between value_buy and view_id (see how getiteminfo works) + int value_buy; + int value_sell; + int type; + int maxchance; //For logs, for external game info, for scripts: Max drop chance of this item (e.g. 0.01% , etc.. if it = 0, then monsters don't drop it, -1 denotes items sold in shops only) [Lupus] + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + int view_id; +#ifdef RENEWAL + int matk; + int elvmax;/* maximum level for this item */ +#endif + + int delay; +//Lupus: I rearranged order of these fields due to compatibility with ITEMINFO script command +// some script commands should be revised as well... + unsigned int class_base[3]; //Specifies if the base can wear this item (split in 3 indexes per type: 1-1, 2-1, 2-2) + unsigned class_upper : 4; //Specifies if the upper-type can equip it (bitfield, 1: normal, 2: upper, 3: baby,4:third) + struct { + unsigned short chance; + int id; + } mob[MAX_SEARCH]; //Holds the mobs that have the highest drop rate for this item. [Skotlex] + struct script_code *script; //Default script for everything. + struct script_code *equip_script; //Script executed once when equipping. + struct script_code *unequip_script;//Script executed once when unequipping. + struct { + unsigned available : 1; + short no_equip; + unsigned no_refine : 1; // [celest] + unsigned delay_consume : 1; // Signifies items that are not consumed immediately upon double-click [Skotlex] + unsigned trade_restriction : 9; //Item restrictions mask [Skotlex] + unsigned autoequip: 1; + unsigned buyingstore : 1; + } flag; + struct {// item stacking limitation + unsigned short amount; + unsigned int inventory:1; + unsigned int cart:1; + unsigned int storage:1; + unsigned int guildstorage:1; + } stack; + short gm_lv_trade_override; //GM-level to override trade_restriction + /* bugreport:309 */ + struct item_combo **combos; + unsigned char combos_count; +}; + +struct item_group { + int nameid[MAX_RANDITEM]; + int qty; //Counts amount of items in the group. +}; + +struct item_combo { + struct script_code *script; + unsigned short *nameid;/* nameid array */ + unsigned char count; + unsigned short id;/* id of this combo */ + bool isRef;/* whether this struct is a reference or the master */ +}; + +struct item_data* itemdb_searchname(const char *name); +int itemdb_searchname_array(struct item_data** data, int size, const char *str); +struct item_data* itemdb_load(int nameid); +struct item_data* itemdb_search(int nameid); +struct item_data* itemdb_exists(int nameid); +#define itemdb_name(n) itemdb_search(n)->name +#define itemdb_jname(n) itemdb_search(n)->jname +#define itemdb_type(n) itemdb_search(n)->type +#define itemdb_atk(n) itemdb_search(n)->atk +#define itemdb_def(n) itemdb_search(n)->def +#define itemdb_look(n) itemdb_search(n)->look +#define itemdb_weight(n) itemdb_search(n)->weight +#define itemdb_equip(n) itemdb_search(n)->equip +#define itemdb_usescript(n) itemdb_search(n)->script +#define itemdb_equipscript(n) itemdb_search(n)->script +#define itemdb_wlv(n) itemdb_search(n)->wlv +#define itemdb_range(n) itemdb_search(n)->range +#define itemdb_slot(n) itemdb_search(n)->slot +#define itemdb_available(n) (itemdb_search(n)->flag.available) +#define itemdb_viewid(n) (itemdb_search(n)->view_id) +#define itemdb_autoequip(n) (itemdb_search(n)->flag.autoequip) +#define itemdb_is_rune(n) (n >= ITEMID_NAUTHIZ && n <= ITEMID_HAGALAZ) +#define itemdb_is_element(n) (n >= 990 && n <= 993) +#define itemdb_is_spellbook(n) (n >= 6188 && n <= 6205) +#define itemdb_is_poison(n) (n >= 12717 && n <= 12724) +#define itemid_isgemstone(id) ( (id) >= ITEMID_YELLOW_GEMSTONE && (id) <= ITEMID_BLUE_GEMSTONE ) +#define itemdb_iscashfood(id) ( (id) >= 12202 && (id) <= 12207 ) +#define itemdb_is_GNbomb(n) (n >= 13260 && n <= 13267) +#define itemdb_is_GNthrowable(n) (n >= 13268 && n <= 13290) +const char* itemdb_typename(int type); + +int itemdb_group_bonus(struct map_session_data* sd, int itemid); +int itemdb_searchrandomid(int flags); + +#define itemdb_value_buy(n) itemdb_search(n)->value_buy +#define itemdb_value_sell(n) itemdb_search(n)->value_sell +#define itemdb_canrefine(n) (!itemdb_search(n)->flag.no_refine) +//Item trade restrictions [Skotlex] +int itemdb_isdropable_sub(struct item_data *, int, int); +int itemdb_cantrade_sub(struct item_data*, int, int); +int itemdb_canpartnertrade_sub(struct item_data*, int, int); +int itemdb_cansell_sub(struct item_data*,int, int); +int itemdb_cancartstore_sub(struct item_data*, int, int); +int itemdb_canstore_sub(struct item_data*, int, int); +int itemdb_canguildstore_sub(struct item_data*, int, int); +int itemdb_canmail_sub(struct item_data*, int, int); +int itemdb_canauction_sub(struct item_data*, int, int); +int itemdb_isrestricted(struct item* item, int gmlv, int gmlv2, int (*func)(struct item_data*, int, int)); +#define itemdb_isdropable(item, gmlv) itemdb_isrestricted(item, gmlv, 0, itemdb_isdropable_sub) +#define itemdb_cantrade(item, gmlv, gmlv2) itemdb_isrestricted(item, gmlv, gmlv2, itemdb_cantrade_sub) +#define itemdb_canpartnertrade(item, gmlv, gmlv2) itemdb_isrestricted(item, gmlv, gmlv2, itemdb_canpartnertrade_sub) +#define itemdb_cansell(item, gmlv) itemdb_isrestricted(item, gmlv, 0, itemdb_cansell_sub) +#define itemdb_cancartstore(item, gmlv) itemdb_isrestricted(item, gmlv, 0, itemdb_cancartstore_sub) +#define itemdb_canstore(item, gmlv) itemdb_isrestricted(item, gmlv, 0, itemdb_canstore_sub) +#define itemdb_canguildstore(item, gmlv) itemdb_isrestricted(item , gmlv, 0, itemdb_canguildstore_sub) +#define itemdb_canmail(item, gmlv) itemdb_isrestricted(item , gmlv, 0, itemdb_canmail_sub) +#define itemdb_canauction(item, gmlv) itemdb_isrestricted(item , gmlv, 0, itemdb_canauction_sub) + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); +int itemdb_isidentified(int); +int itemdb_isstackable(int); +int itemdb_isstackable2(struct item_data *); +uint64 itemdb_unique_id(int8 flag, int64 value); // Unique Item ID + +void itemdb_reload(void); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif /* _ITEMDB_H_ */ diff --git a/src/map/log.c b/src/map/log.c new file mode 100644 index 000000000..ca10c97ab --- /dev/null +++ b/src/map/log.c @@ -0,0 +1,583 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/sql.h" // SQL_INNODB +#include "../common/strlib.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "battle.h" +#include "itemdb.h" +#include "log.h" +#include "map.h" +#include "mob.h" +#include "pc.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/// filters for item logging +typedef enum e_log_filter +{ + LOG_FILTER_NONE = 0x000, + LOG_FILTER_ALL = 0x001, + // bits + LOG_FILTER_HEALING = 0x002, // Healing items (0) + LOG_FILTER_ETC_AMMO = 0x004, // Etc Items(3) + Arrows (10) + LOG_FILTER_USABLE = 0x008, // Usable Items(2) + Scrolls, Lures(11) + Usable Cash Items(18) + LOG_FILTER_WEAPON = 0x010, // Weapons(4) + LOG_FILTER_ARMOR = 0x020, // Shields, Armors, Headgears, Accessories, Garments and Shoes(5) + LOG_FILTER_CARD = 0x040, // Cards(6) + LOG_FILTER_PETITEM = 0x080, // Pet Accessories(8) + Eggs(7) (well, monsters don't drop 'em but we'll use the same system for ALL logs) + LOG_FILTER_PRICE = 0x100, // Log expensive items ( >= price_log ) + LOG_FILTER_AMOUNT = 0x200, // Log large amount of items ( >= amount_log ) + LOG_FILTER_REFINE = 0x400, // Log refined items ( refine >= refine_log ) [not implemented] + LOG_FILTER_CHANCE = 0x800, // Log rare items and Emperium ( drop chance <= rare_log ) +} +e_log_filter; + + +struct Log_Config log_config; + + +#ifdef SQL_INNODB +// database is using an InnoDB engine so do not use DELAYED +#define LOG_QUERY "INSERT" +#else +// database is using a MyISAM engine so use DELAYED +#define LOG_QUERY "INSERT DELAYED" +#endif + + +/// obtain log type character for item/zeny logs +static char log_picktype2char(e_log_pick_type type) +{ + switch( type ) + { + case LOG_TYPE_TRADE: return 'T'; // (T)rade + case LOG_TYPE_VENDING: return 'V'; // (V)ending + case LOG_TYPE_PICKDROP_PLAYER: return 'P'; // (P)player + case LOG_TYPE_PICKDROP_MONSTER: return 'M'; // (M)onster + case LOG_TYPE_NPC: return 'S'; // NPC (S)hop + case LOG_TYPE_SCRIPT: return 'N'; // (N)PC Script + case LOG_TYPE_STEAL: return 'D'; // Steal/Snatcher + case LOG_TYPE_CONSUME: return 'C'; // (C)onsumed + case LOG_TYPE_PRODUCE: return 'O'; // Pr(O)duced/Ingredients + case LOG_TYPE_MVP: return 'U'; // MVP Rewards + case LOG_TYPE_COMMAND: return 'A'; // (A)dmin command + case LOG_TYPE_STORAGE: return 'R'; // Sto(R)age + case LOG_TYPE_GSTORAGE: return 'G'; // (G)uild storage + case LOG_TYPE_MAIL: return 'E'; // (E)mail attachment + case LOG_TYPE_AUCTION: return 'I'; // Auct(I)on + case LOG_TYPE_BUYING_STORE: return 'B'; // (B)uying Store + case LOG_TYPE_LOOT: return 'L'; // (L)oot (consumed monster pick/drop) + case LOG_TYPE_OTHER: return 'X'; // Other + } + + // should not get here, fallback + ShowDebug("log_picktype2char: Unknown pick type %d.\n", type); + return 'X'; +} + + +/// obtain log type character for chat logs +static char log_chattype2char(e_log_chat_type type) +{ + switch( type ) + { + case LOG_CHAT_GLOBAL: return 'O'; // Gl(O)bal + case LOG_CHAT_WHISPER: return 'W'; // (W)hisper + case LOG_CHAT_PARTY: return 'P'; // (P)arty + case LOG_CHAT_GUILD: return 'G'; // (G)uild + case LOG_CHAT_MAINCHAT: return 'M'; // (M)ain chat + } + + // should not get here, fallback + ShowDebug("log_chattype2char: Unknown chat type %d.\n", type); + return 'O'; +} + + +/// check if this item should be logged according the settings +static bool should_log_item(int nameid, int amount, int refine) +{ + int filter = log_config.filter; + struct item_data* id; + + if( ( id = itemdb_exists(nameid) ) == NULL ) + return false; + + if( ( filter&LOG_FILTER_ALL ) || + ( filter&LOG_FILTER_HEALING && id->type == IT_HEALING ) || + ( filter&LOG_FILTER_ETC_AMMO && ( id->type == IT_ETC || id->type == IT_AMMO ) ) || + ( filter&LOG_FILTER_USABLE && ( id->type == IT_USABLE || id->type == IT_CASH ) ) || + ( filter&LOG_FILTER_WEAPON && id->type == IT_WEAPON ) || + ( filter&LOG_FILTER_ARMOR && id->type == IT_ARMOR ) || + ( filter&LOG_FILTER_CARD && id->type == IT_CARD ) || + ( filter&LOG_FILTER_PETITEM && ( id->type == IT_PETEGG || id->type == IT_PETARMOR ) ) || + ( filter&LOG_FILTER_PRICE && id->value_buy >= log_config.price_items_log ) || + ( filter&LOG_FILTER_AMOUNT && abs(amount) >= log_config.amount_items_log ) || + ( filter&LOG_FILTER_REFINE && refine >= log_config.refine_items_log ) || + ( filter&LOG_FILTER_CHANCE && ( ( id->maxchance != -1 && id->maxchance <= log_config.rare_items_log ) || id->nameid == ITEMID_EMPERIUM ) ) + ) + return true; + + return false; +} + + +/// logs items, that summon monsters +void log_branch(struct map_session_data* sd) +{ + nullpo_retv(sd); + + if( !log_config.branch ) + return; + + if( log_config.sql_logs ) { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`branch_date`, `account_id`, `char_id`, `char_name`, `map`) VALUES (NOW(), '%d', '%d', '%s', '%s')", log_config.log_branch, sd->status.account_id, sd->status.char_id, sd->status.name, mapindex_id2name(sd->mapindex)); + queryThread_log(entry,e_length); +#else + SqlStmt* stmt; + stmt = SqlStmt_Malloc(logmysql_handle); + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, LOG_QUERY " INTO `%s` (`branch_date`, `account_id`, `char_id`, `char_name`, `map`) VALUES (NOW(), '%d', '%d', ?, '%s')", log_config.log_branch, sd->status.account_id, sd->status.char_id, mapindex_id2name(sd->mapindex) ) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, sd->status.name, strnlen(sd->status.name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return; + } + SqlStmt_Free(stmt); +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_branch, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp,"%s - %s[%d:%d]\t%s\n", timestring, sd->status.name, sd->status.account_id, sd->status.char_id, mapindex_id2name(sd->mapindex)); + fclose(logfp); + } +} + +/// logs item transactions (generic) +void log_pick(int id, int16 m, e_log_pick_type type, int amount, struct item* itm) +{ + nullpo_retv(itm); + if( ( log_config.enable_logs&type ) == 0 ) + {// disabled + return; + } + + if( !should_log_item(itm->nameid, amount, itm->refine) ) + return; //we skip logging this item set - it doesn't meet our logging conditions [Lupus] + + if( log_config.sql_logs ) + { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`time`, `char_id`, `type`, `nameid`, `amount`, `refine`, `card0`, `card1`, `card2`, `card3`, `map`, `unique_id`) VALUES (NOW(), '%d', '%c', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%"PRIu64"')", + log_config.log_pick, id, log_picktype2char(type), itm->nameid, amount, itm->refine, itm->card[0], itm->card[1], itm->card[2], itm->card[3], map[m].name?map[m].name:"", itm->unique_id); + queryThread_log(entry,e_length); +#else + if( SQL_ERROR == Sql_Query(logmysql_handle, LOG_QUERY " INTO `%s` (`time`, `char_id`, `type`, `nameid`, `amount`, `refine`, `card0`, `card1`, `card2`, `card3`, `map`, `unique_id`) VALUES (NOW(), '%d', '%c', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%"PRIu64"')", + log_config.log_pick, id, log_picktype2char(type), itm->nameid, amount, itm->refine, itm->card[0], itm->card[1], itm->card[2], itm->card[3], map[m].name?map[m].name:"", itm->unique_id) ) + { + Sql_ShowDebug(logmysql_handle); + return; + } +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_pick, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp,"%s - %d\t%c\t%d,%d,%d,%d,%d,%d,%d,%s,'%"PRIu64"'\n", timestring, id, log_picktype2char(type), itm->nameid, amount, itm->refine, itm->card[0], itm->card[1], itm->card[2], itm->card[3], map[m].name?map[m].name:"", itm->unique_id); + fclose(logfp); + } +} + +/// logs item transactions (players) +void log_pick_pc(struct map_session_data* sd, e_log_pick_type type, int amount, struct item* itm) +{ + nullpo_retv(sd); + log_pick(sd->status.char_id, sd->bl.m, type, amount, itm); +} + + +/// logs item transactions (monsters) +void log_pick_mob(struct mob_data* md, e_log_pick_type type, int amount, struct item* itm) +{ + nullpo_retv(md); + log_pick(md->class_, md->bl.m, type, amount, itm); +} + +/// logs zeny transactions +void log_zeny(struct map_session_data* sd, e_log_pick_type type, struct map_session_data* src_sd, int amount) +{ + nullpo_retv(sd); + + if( !log_config.zeny || ( log_config.zeny != 1 && abs(amount) < log_config.zeny ) ) + return; + + if( log_config.sql_logs ) + { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`time`, `char_id`, `src_id`, `type`, `amount`, `map`) VALUES (NOW(), '%d', '%d', '%c', '%d', '%s')", + log_config.log_zeny, sd->status.char_id, src_sd->status.char_id, log_picktype2char(type), amount, mapindex_id2name(sd->mapindex)); + queryThread_log(entry,e_length); +#else + if( SQL_ERROR == Sql_Query(logmysql_handle, LOG_QUERY " INTO `%s` (`time`, `char_id`, `src_id`, `type`, `amount`, `map`) VALUES (NOW(), '%d', '%d', '%c', '%d', '%s')", + log_config.log_zeny, sd->status.char_id, src_sd->status.char_id, log_picktype2char(type), amount, mapindex_id2name(sd->mapindex)) ) + { + Sql_ShowDebug(logmysql_handle); + return; + } +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_zeny, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp, "%s - %s[%d]\t%s[%d]\t%d\t\n", timestring, src_sd->status.name, src_sd->status.account_id, sd->status.name, sd->status.account_id, amount); + fclose(logfp); + } +} + + +/// logs MVP monster rewards +void log_mvpdrop(struct map_session_data* sd, int monster_id, int* log_mvp) +{ + nullpo_retv(sd); + + if( !log_config.mvpdrop ) + return; + + if( log_config.sql_logs ) + { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`mvp_date`, `kill_char_id`, `monster_id`, `prize`, `mvpexp`, `map`) VALUES (NOW(), '%d', '%d', '%d', '%d', '%s') ", + log_config.log_mvpdrop, sd->status.char_id, monster_id, log_mvp[0], log_mvp[1], mapindex_id2name(sd->mapindex)); + queryThread_log(entry,e_length); +#else + if( SQL_ERROR == Sql_Query(logmysql_handle, LOG_QUERY " INTO `%s` (`mvp_date`, `kill_char_id`, `monster_id`, `prize`, `mvpexp`, `map`) VALUES (NOW(), '%d', '%d', '%d', '%d', '%s') ", + log_config.log_mvpdrop, sd->status.char_id, monster_id, log_mvp[0], log_mvp[1], mapindex_id2name(sd->mapindex)) ) + { + Sql_ShowDebug(logmysql_handle); + return; + } +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_mvpdrop,"a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp,"%s - %s[%d:%d]\t%d\t%d,%d\n", timestring, sd->status.name, sd->status.account_id, sd->status.char_id, monster_id, log_mvp[0], log_mvp[1]); + fclose(logfp); + } +} + + +/// logs used atcommands +void log_atcommand(struct map_session_data* sd, const char* message) +{ + nullpo_retv(sd); + + if( !log_config.commands || + !pc_should_log_commands(sd) ) + return; + + if( log_config.sql_logs ) + { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`atcommand_date`, `account_id`, `char_id`, `char_name`, `map`, `command`) VALUES (NOW(), '%d', '%d', '%s', '%s', '%s')", log_config.log_gm, sd->status.account_id, sd->status.char_id, sd->status.name ,mapindex_id2name(sd->mapindex), message); + queryThread_log(entry,e_length); +#else + SqlStmt* stmt; + + stmt = SqlStmt_Malloc(logmysql_handle); + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, LOG_QUERY " INTO `%s` (`atcommand_date`, `account_id`, `char_id`, `char_name`, `map`, `command`) VALUES (NOW(), '%d', '%d', ?, '%s', ?)", log_config.log_gm, sd->status.account_id, sd->status.char_id, mapindex_id2name(sd->mapindex) ) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, sd->status.name, strnlen(sd->status.name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (char*)message, safestrnlen(message, 255)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return; + } + SqlStmt_Free(stmt); +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_gm, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp, "%s - %s[%d]: %s\n", timestring, sd->status.name, sd->status.account_id, message); + fclose(logfp); + } +} + + +/// logs messages passed to script command 'logmes' +void log_npc(struct map_session_data* sd, const char* message) +{ + nullpo_retv(sd); + + if( !log_config.npc ) + return; + + if( log_config.sql_logs ) + { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`npc_date`, `account_id`, `char_id`, `char_name`, `map`, `mes`) VALUES (NOW(), '%d', '%d', '%s', '%s', '%s')", log_config.log_npc, sd->status.account_id, sd->status.char_id, sd->status.name, mapindex_id2name(sd->mapindex), message ); + queryThread_log(entry,e_length); +#else + SqlStmt* stmt; + stmt = SqlStmt_Malloc(logmysql_handle); + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, LOG_QUERY " INTO `%s` (`npc_date`, `account_id`, `char_id`, `char_name`, `map`, `mes`) VALUES (NOW(), '%d', '%d', ?, '%s', ?)", log_config.log_npc, sd->status.account_id, sd->status.char_id, mapindex_id2name(sd->mapindex) ) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, sd->status.name, strnlen(sd->status.name, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (char*)message, safestrnlen(message, 255)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return; + } + SqlStmt_Free(stmt); +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_npc, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp, "%s - %s[%d]: %s\n", timestring, sd->status.name, sd->status.account_id, message); + fclose(logfp); + } +} + + +/// logs chat +void log_chat(e_log_chat_type type, int type_id, int src_charid, int src_accid, const char* map, int x, int y, const char* dst_charname, const char* message) +{ + if( ( log_config.chat&type ) == 0 ) + {// disabled + return; + } + + if( log_config.log_chat_woe_disable && ( agit_flag || agit2_flag ) ) + {// no chat logging during woe + return; + } + + if( log_config.sql_logs ) { +#ifdef BETA_THREAD_TEST + char entry[512]; + int e_length = 0; + e_length = sprintf(entry, LOG_QUERY " INTO `%s` (`time`, `type`, `type_id`, `src_charid`, `src_accountid`, `src_map`, `src_map_x`, `src_map_y`, `dst_charname`, `message`) VALUES (NOW(), '%c', '%d', '%d', '%d', '%s', '%d', '%d', '%s', '%s')", log_config.log_chat, log_chattype2char(type), type_id, src_charid, src_accid, map, x, y, dst_charname, message ); + queryThread_log(entry,e_length); +#else + SqlStmt* stmt; + + stmt = SqlStmt_Malloc(logmysql_handle); + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, LOG_QUERY " INTO `%s` (`time`, `type`, `type_id`, `src_charid`, `src_accountid`, `src_map`, `src_map_x`, `src_map_y`, `dst_charname`, `message`) VALUES (NOW(), '%c', '%d', '%d', '%d', '%s', '%d', '%d', ?, ?)", log_config.log_chat, log_chattype2char(type), type_id, src_charid, src_accid, map, x, y) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (char*)dst_charname, safestrnlen(dst_charname, NAME_LENGTH)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (char*)message, safestrnlen(message, CHAT_SIZE_MAX)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) ) + { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return; + } + SqlStmt_Free(stmt); +#endif + } + else + { + char timestring[255]; + time_t curtime; + FILE* logfp; + + if( ( logfp = fopen(log_config.log_chat, "a") ) == NULL ) + return; + time(&curtime); + strftime(timestring, sizeof(timestring), "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(logfp, "%s - %c,%d,%d,%d,%s,%d,%d,%s,%s\n", timestring, log_chattype2char(type), type_id, src_charid, src_accid, map, x, y, dst_charname, message); + fclose(logfp); + } +} + + +void log_set_defaults(void) +{ + memset(&log_config, 0, sizeof(log_config)); + + //LOG FILTER Default values + log_config.refine_items_log = 5; // log refined items, with refine >= +5 + log_config.rare_items_log = 100; // log rare items. drop chance <= 1% + log_config.price_items_log = 1000; // 1000z + log_config.amount_items_log = 100; +} + + +int log_config_read(const char* cfgName) +{ + static int count = 0; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if( count++ == 0 ) + log_set_defaults(); + + if( ( fp = fopen(cfgName, "r") ) == NULL ) + { + ShowError("Log configuration file not found at: %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 ) + { + if( strcmpi(w1, "enable_logs") == 0 ) + log_config.enable_logs = (e_log_pick_type)config_switch(w2); + else if( strcmpi(w1, "sql_logs") == 0 ) + log_config.sql_logs = (bool)config_switch(w2); +//start of common filter settings + else if( strcmpi(w1, "rare_items_log") == 0 ) + log_config.rare_items_log = atoi(w2); + else if( strcmpi(w1, "refine_items_log") == 0 ) + log_config.refine_items_log = atoi(w2); + else if( strcmpi(w1, "price_items_log") == 0 ) + log_config.price_items_log = atoi(w2); + else if( strcmpi(w1, "amount_items_log") == 0 ) + log_config.amount_items_log = atoi(w2); +//end of common filter settings + else if( strcmpi(w1, "log_branch") == 0 ) + log_config.branch = config_switch(w2); + else if( strcmpi(w1, "log_filter") == 0 ) + log_config.filter = config_switch(w2); + else if( strcmpi(w1, "log_zeny") == 0 ) + log_config.zeny = config_switch(w2); + else if( strcmpi(w1, "log_commands") == 0 ) + log_config.commands = config_switch(w2); + else if( strcmpi(w1, "log_npc") == 0 ) + log_config.npc = config_switch(w2); + else if( strcmpi(w1, "log_chat") == 0 ) + log_config.chat = config_switch(w2); + else if( strcmpi(w1, "log_mvpdrop") == 0 ) + log_config.mvpdrop = config_switch(w2); + else if( strcmpi(w1, "log_chat_woe_disable") == 0 ) + log_config.log_chat_woe_disable = (bool)config_switch(w2); + else if( strcmpi(w1, "log_branch_db") == 0 ) + safestrncpy(log_config.log_branch, w2, sizeof(log_config.log_branch)); + else if( strcmpi(w1, "log_pick_db") == 0 ) + safestrncpy(log_config.log_pick, w2, sizeof(log_config.log_pick)); + else if( strcmpi(w1, "log_zeny_db") == 0 ) + safestrncpy(log_config.log_zeny, w2, sizeof(log_config.log_zeny)); + else if( strcmpi(w1, "log_mvpdrop_db") == 0 ) + safestrncpy(log_config.log_mvpdrop, w2, sizeof(log_config.log_mvpdrop)); + else if( strcmpi(w1, "log_gm_db") == 0 ) + safestrncpy(log_config.log_gm, w2, sizeof(log_config.log_gm)); + else if( strcmpi(w1, "log_npc_db") == 0 ) + safestrncpy(log_config.log_npc, w2, sizeof(log_config.log_npc)); + else if( strcmpi(w1, "log_chat_db") == 0 ) + safestrncpy(log_config.log_chat, w2, sizeof(log_config.log_chat)); + //support the import command, just like any other config + else if( strcmpi(w1,"import") == 0 ) + log_config_read(w2); + else + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + } + + fclose(fp); + + if( --count == 0 ) + {// report final logging state + const char* target = log_config.sql_logs ? "table" : "file"; + + if( log_config.enable_logs && log_config.filter ) + { + ShowInfo("Logging item transactions to %s '%s'.\n", target, log_config.log_pick); + } + if( log_config.branch ) + { + ShowInfo("Logging monster summon item usage to %s '%s'.\n", target, log_config.log_pick); + } + if( log_config.chat ) + { + ShowInfo("Logging chat to %s '%s'.\n", target, log_config.log_chat); + } + if( log_config.commands ) + { + ShowInfo("Logging commands to %s '%s'.\n", target, log_config.log_gm); + } + if( log_config.mvpdrop ) + { + ShowInfo("Logging MVP monster rewards to %s '%s'.\n", target, log_config.log_mvpdrop); + } + if( log_config.npc ) + { + ShowInfo("Logging 'logmes' messages to %s '%s'.\n", target, log_config.log_npc); + } + if( log_config.zeny ) + { + ShowInfo("Logging Zeny transactions to %s '%s'.\n", target, log_config.log_zeny); + } + } + + return 0; +} diff --git a/src/map/log.h b/src/map/log.h new file mode 100644 index 000000000..a40a3fcf4 --- /dev/null +++ b/src/map/log.h @@ -0,0 +1,89 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LOG_H_ +#define _LOG_H_ + +//#include "map.h" +struct block_list; +struct map_session_data; +struct mob_data; +struct item; + + +typedef enum e_log_chat_type +{ + LOG_CHAT_GLOBAL = 0x01, + LOG_CHAT_WHISPER = 0x02, + LOG_CHAT_PARTY = 0x04, + LOG_CHAT_GUILD = 0x08, + LOG_CHAT_MAINCHAT = 0x10, + // all + LOG_CHAT_ALL = 0xFF, +} +e_log_chat_type; + + +typedef enum e_log_pick_type +{ + LOG_TYPE_NONE = 0, + LOG_TYPE_TRADE = 0x00001, + LOG_TYPE_VENDING = 0x00002, + LOG_TYPE_PICKDROP_PLAYER = 0x00004, + LOG_TYPE_PICKDROP_MONSTER = 0x00008, + LOG_TYPE_NPC = 0x00010, + LOG_TYPE_SCRIPT = 0x00020, + LOG_TYPE_STEAL = 0x00040, + LOG_TYPE_CONSUME = 0x00080, + LOG_TYPE_PRODUCE = 0x00100, + LOG_TYPE_MVP = 0x00200, + LOG_TYPE_COMMAND = 0x00400, + LOG_TYPE_STORAGE = 0x00800, + LOG_TYPE_GSTORAGE = 0x01000, + LOG_TYPE_MAIL = 0x02000, + LOG_TYPE_AUCTION = 0x04000, + LOG_TYPE_BUYING_STORE = 0x08000, + LOG_TYPE_OTHER = 0x10000, + // combinations + LOG_TYPE_LOOT = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME, + // all + LOG_TYPE_ALL = 0xFFFFF, +} +e_log_pick_type; + + +/// new logs +void log_pick_pc(struct map_session_data* sd, e_log_pick_type type, int amount, struct item* itm); +void log_pick_mob(struct mob_data* md, e_log_pick_type type, int amount, struct item* itm); +void log_zeny(struct map_session_data* sd, e_log_pick_type type, struct map_session_data* src_sd, int amount); + +void log_npc(struct map_session_data* sd, const char *message); +void log_chat(e_log_chat_type type, int type_id, int src_charid, int src_accid, const char* map, int x, int y, const char* dst_charname, const char* message); +void log_atcommand(struct map_session_data* sd, const char* message); + +/// old, but useful logs +void log_branch(struct map_session_data* sd); +void log_mvpdrop(struct map_session_data* sd, int monster_id, int* log_mvp); + +int log_config_read(const char* cfgName); + +extern struct Log_Config +{ + e_log_pick_type enable_logs; + int filter; + bool sql_logs; + bool log_chat_woe_disable; + int rare_items_log,refine_items_log,price_items_log,amount_items_log; //for filter + int branch, mvpdrop, zeny, commands, npc, chat; + char log_branch[64], log_pick[64], log_zeny[64], log_mvpdrop[64], log_gm[64], log_npc[64], log_chat[64]; +} +log_config; + +#ifdef BETA_THREAD_TEST + struct { + char** entry; + int count; + } logThreadData; +#endif + +#endif /* _LOG_H_ */ diff --git a/src/map/mail.c b/src/map/mail.c new file mode 100644 index 000000000..03b8227b5 --- /dev/null +++ b/src/map/mail.c @@ -0,0 +1,185 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/nullpo.h" +#include "../common/showmsg.h" + +#include "mail.h" +#include "atcommand.h" +#include "itemdb.h" +#include "clif.h" +#include "pc.h" +#include "log.h" + +#include <time.h> +#include <string.h> + +void mail_clear(struct map_session_data *sd) +{ + sd->mail.nameid = 0; + sd->mail.index = 0; + sd->mail.amount = 0; + sd->mail.zeny = 0; + + return; +} + +int mail_removeitem(struct map_session_data *sd, short flag) +{ + nullpo_ret(sd); + + if( sd->mail.amount ) + { + if (flag) // Item send + pc_delitem(sd, sd->mail.index, sd->mail.amount, 1, 0, LOG_TYPE_MAIL); + else + clif_additem(sd, sd->mail.index, sd->mail.amount, 0); + } + + sd->mail.nameid = 0; + sd->mail.index = 0; + sd->mail.amount = 0; + return 1; +} + +int mail_removezeny(struct map_session_data *sd, short flag) +{ + nullpo_ret(sd); + + if (flag && sd->mail.zeny > 0) + { //Zeny send + pc_payzeny(sd,sd->mail.zeny,LOG_TYPE_MAIL, NULL); + } + sd->mail.zeny = 0; + + return 1; +} + +unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount) { + + if( pc_istrading(sd) ) + return 1; + + if( idx == 0 ) { // Zeny Transfer + if( amount < 0 || !pc_can_give_items(sd) ) + return 1; + + if( amount > sd->status.zeny ) + amount = sd->status.zeny; + + sd->mail.zeny = amount; + // clif_updatestatus(sd, SP_ZENY); + return 0; + } else { // Item Transfer + idx -= 2; + mail_removeitem(sd, 0); + + if( idx < 0 || idx >= MAX_INVENTORY ) + return 1; + if( amount < 0 || amount > sd->status.inventory[idx].amount ) + return 1; + if( !pc_can_give_items(sd) || sd->status.inventory[idx].expire_time || + !itemdb_canmail(&sd->status.inventory[idx],pc_get_group_level(sd)) ) + return 1; + + sd->mail.index = idx; + sd->mail.nameid = sd->status.inventory[idx].nameid; + sd->mail.amount = amount; + + return 0; + } +} + +bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg) +{ + int n; + + nullpo_retr(false,sd); + nullpo_retr(false,msg); + + if( sd->mail.zeny < 0 || sd->mail.zeny > sd->status.zeny ) + return false; + + n = sd->mail.index; + if( sd->mail.amount ) + { + if( sd->status.inventory[n].nameid != sd->mail.nameid ) + return false; + + if( sd->status.inventory[n].amount < sd->mail.amount ) + return false; + + if( sd->weight > sd->max_weight ) + return false; + + memcpy(&msg->item, &sd->status.inventory[n], sizeof(struct item)); + msg->item.amount = sd->mail.amount; + } + else + memset(&msg->item, 0x00, sizeof(struct item)); + + msg->zeny = sd->mail.zeny; + + // Removes the attachment from sender + mail_removeitem(sd,1); + mail_removezeny(sd,1); + + return true; +} + +void mail_getattachment(struct map_session_data* sd, int zeny, struct item* item) +{ + if( item->nameid > 0 && item->amount > 0 ) + { + pc_additem(sd, item, item->amount, LOG_TYPE_MAIL); + clif_Mail_getattachment(sd->fd, 0); + } + + if( zeny > 0 ) + { //Zeny receive + pc_getzeny(sd, zeny,LOG_TYPE_MAIL, NULL); + } +} + +int mail_openmail(struct map_session_data *sd) +{ + nullpo_ret(sd); + + if( sd->state.storage_flag || sd->state.vending || sd->state.buyingstore || sd->state.trading ) + return 0; + + clif_Mail_window(sd->fd, 0); + + return 1; +} + +void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg) +{ + nullpo_retv(sd); + nullpo_retv(msg); + + if( msg->item.amount > 0 ) + { + // Item receive (due to failure) + pc_additem(sd, &msg->item, msg->item.amount, LOG_TYPE_MAIL); + } + + if( msg->zeny > 0 ) + { + pc_getzeny(sd,msg->zeny,LOG_TYPE_MAIL, NULL); //Zeny receive (due to failure) + } + + clif_Mail_send(sd->fd, true); +} + +// This function only check if the mail operations are valid +bool mail_invalid_operation(struct map_session_data *sd) +{ + if( !map[sd->bl.m].flag.town && !pc_can_use_command(sd, "mail", COMMAND_ATCOMMAND) ) + { + ShowWarning("clif_parse_Mail: char '%s' trying to do invalid mail operations.\n", sd->status.name); + return true; + } + + return false; +} diff --git a/src/map/mail.h b/src/map/mail.h new file mode 100644 index 000000000..cab582e55 --- /dev/null +++ b/src/map/mail.h @@ -0,0 +1,19 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MAIL_H_ +#define _MAIL_H_ + +#include "../common/mmo.h" + +void mail_clear(struct map_session_data *sd); +int mail_removeitem(struct map_session_data *sd, short flag); +int mail_removezeny(struct map_session_data *sd, short flag); +unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount); +bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg); +void mail_getattachment(struct map_session_data* sd, int zeny, struct item* item); +int mail_openmail(struct map_session_data *sd); +void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg); +bool mail_invalid_operation(struct map_session_data *sd); + +#endif /* _MAIL_H_ */ diff --git a/src/map/map.c b/src/map/map.c new file mode 100644 index 000000000..f6468e180 --- /dev/null +++ b/src/map/map.c @@ -0,0 +1,3952 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/core.h" +#include "../common/timer.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "../common/socket.h" // WFIFO*() +#include "../common/showmsg.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "map.h" +#include "path.h" +#include "chrif.h" +#include "clif.h" +#include "duel.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "status.h" +#include "mob.h" +#include "npc.h" // npc_setcells(), npc_unsetcells() +#include "chat.h" +#include "itemdb.h" +#include "storage.h" +#include "skill.h" +#include "trade.h" +#include "party.h" +#include "unit.h" +#include "battle.h" +#include "battleground.h" +#include "quest.h" +#include "script.h" +#include "mapreg.h" +#include "guild.h" +#include "pet.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "atcommand.h" +#include "log.h" +#include "mail.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> +#ifndef _WIN32 +#include <unistd.h> +#endif + +char default_codepage[32] = ""; + +int map_server_port = 3306; +char map_server_ip[32] = "127.0.0.1"; +char map_server_id[32] = "ragnarok"; +char map_server_pw[32] = "ragnarok"; +char map_server_db[32] = "ragnarok"; +Sql* mmysql_handle; + +int db_use_sqldbs = 0; +char item_db_db[32] = "item_db"; +char item_db2_db[32] = "item_db2"; +char item_db_re_db[32] = "item_db_re"; +char mob_db_db[32] = "mob_db"; +char mob_db2_db[32] = "mob_db2"; +char mob_skill_db_db[32] = "mob_skill_db"; +char mob_skill_db2_db[32] = "mob_skill_db2"; + +// log database +char log_db_ip[32] = "127.0.0.1"; +int log_db_port = 3306; +char log_db_id[32] = "ragnarok"; +char log_db_pw[32] = "ragnarok"; +char log_db_db[32] = "log"; +Sql* logmysql_handle; + +// This param using for sending mainchat +// messages like whispers to this nick. [LuzZza] +char main_chat_nick[16] = "Main"; + +char *INTER_CONF_NAME; +char *LOG_CONF_NAME; +char *MAP_CONF_NAME; +char *BATTLE_CONF_FILENAME; +char *ATCOMMAND_CONF_FILENAME; +char *SCRIPT_CONF_NAME; +char *MSG_CONF_NAME; +char *GRF_PATH_FILENAME; + +// DBMap declaartion +static DBMap* id_db=NULL; // int id -> struct block_list* +static DBMap* pc_db=NULL; // int id -> struct map_session_data* +static DBMap* mobid_db=NULL; // int id -> struct mob_data* +static DBMap* bossid_db=NULL; // int id -> struct mob_data* (MVP db) +static DBMap* map_db=NULL; // unsigned int mapindex -> struct map_data* +static DBMap* nick_db=NULL; // int char_id -> struct charid2nick* (requested names of offline characters) +static DBMap* charid_db=NULL; // int char_id -> struct map_session_data* +static DBMap* regen_db=NULL; // int id -> struct block_list* (status_natural_heal processing) + +static int map_users=0; + +#define BLOCK_SIZE 8 +#define block_free_max 1048576 +struct block_list *block_free[block_free_max]; +static int block_free_count = 0, block_free_lock = 0; + +#define BL_LIST_MAX 1048576 +static struct block_list *bl_list[BL_LIST_MAX]; +static int bl_list_count = 0; + +struct map_data map[MAX_MAP_PER_SERVER]; +int map_num = 0; +int map_port=0; + +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int minsave_interval = 100; +int save_settings = 0xFFFF; +int agit_flag = 0; +int agit2_flag = 0; +int night_flag = 0; // 0=day, 1=night [Yor] + +struct charid_request { + struct charid_request* next; + int charid;// who want to be notified of the nick +}; +struct charid2nick { + char nick[NAME_LENGTH]; + struct charid_request* requests;// requests of notification on this nick +}; + +// This is the main header found at the very beginning of the map cache +struct map_cache_main_header { + uint32 file_size; + uint16 map_count; +}; + +// This is the header appended before every compressed map cells info in the map cache +struct map_cache_map_info { + char name[MAP_NAME_LENGTH]; + int16 xs; + int16 ys; + int32 len; +}; + +char db_path[256] = "db"; +char motd_txt[256] = "conf/motd.txt"; +char help_txt[256] = "conf/help.txt"; +char help2_txt[256] = "conf/help2.txt"; +char charhelp_txt[256] = "conf/charhelp.txt"; + +char wisp_server_name[NAME_LENGTH] = "Server"; // can be modified in char-server configuration file + +int console = 0; +int enable_spy = 0; //To enable/disable @spy commands, which consume too much cpu time when sending packets. [Skotlex] +int enable_grf = 0; //To enable/disable reading maps from GRF files, bypassing mapcache [blackhole89] + +/*========================================== + * server player count (of all mapservers) + *------------------------------------------*/ +void map_setusers(int users) +{ + map_users = users; +} + +int map_getusers(void) +{ + return map_users; +} + +/*========================================== + * server player count (this mapserver only) + *------------------------------------------*/ +int map_usercount(void) +{ + return pc_db->size(pc_db); +} + + +/*========================================== + * Attempt to free a map blocklist + *------------------------------------------*/ +int map_freeblock (struct block_list *bl) +{ + nullpo_retr(block_free_lock, bl); + if (block_free_lock == 0 || block_free_count >= block_free_max) + { + aFree(bl); + bl = NULL; + if (block_free_count >= block_free_max) + ShowWarning("map_freeblock: too many free block! %d %d\n", block_free_count, block_free_lock); + } else + block_free[block_free_count++] = bl; + + return block_free_lock; +} +/*========================================== + * Lock blocklist, (prevent map_freeblock usage) + *------------------------------------------*/ +int map_freeblock_lock (void) +{ + return ++block_free_lock; +} + +/*========================================== + * Remove the lock on map_bl + *------------------------------------------*/ +int map_freeblock_unlock (void) +{ + if ((--block_free_lock) == 0) { + int i; + for (i = 0; i < block_free_count; i++) + { + aFree(block_free[i]); + block_free[i] = NULL; + } + block_free_count = 0; + } else if (block_free_lock < 0) { + ShowError("map_freeblock_unlock: lock count < 0 !\n"); + block_free_lock = 0; + } + + return block_free_lock; +} + +// Timer function to check if there some remaining lock and remove them if so. +// Called each 1s +int map_freeblock_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + if (block_free_lock > 0) { + ShowError("map_freeblock_timer: block_free_lock(%d) is invalid.\n", block_free_lock); + block_free_lock = 1; + map_freeblock_unlock(); + } + + return 0; +} + +// +// blocklist +// +/*========================================== + * Handling of map_bl[] + * The adresse of bl_heal is set in bl->prev + *------------------------------------------*/ +static struct block_list bl_head; + +#ifdef CELL_NOSTACK +/*========================================== + * These pair of functions update the counter of how many objects + * lie on a tile. + *------------------------------------------*/ +static void map_addblcell(struct block_list *bl) +{ + if( bl->m<0 || bl->x<0 || bl->x>=map[bl->m].xs || bl->y<0 || bl->y>=map[bl->m].ys || !(bl->type&BL_CHAR) ) + return; + map[bl->m].cell[bl->x+bl->y*map[bl->m].xs].cell_bl++; + return; +} + +static void map_delblcell(struct block_list *bl) +{ + if( bl->m <0 || bl->x<0 || bl->x>=map[bl->m].xs || bl->y<0 || bl->y>=map[bl->m].ys || !(bl->type&BL_CHAR) ) + return; + map[bl->m].cell[bl->x+bl->y*map[bl->m].xs].cell_bl--; +} +#endif + +/*========================================== + * Adds a block to the map. + * Returns 0 on success, 1 on failure (illegal coordinates). + *------------------------------------------*/ +int map_addblock(struct block_list* bl) +{ + int16 m, x, y; + int pos; + + nullpo_ret(bl); + + if (bl->prev != NULL) { + ShowError("map_addblock: bl->prev != NULL\n"); + return 1; + } + + m = bl->m; + x = bl->x; + y = bl->y; + if( m < 0 || m >= map_num ) + { + ShowError("map_addblock: invalid map id (%d), only %d are loaded.\n", m, map_num); + return 1; + } + if( x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys ) + { + ShowError("map_addblock: out-of-bounds coordinates (\"%s\",%d,%d), map is %dx%d\n", map[m].name, x, y, map[m].xs, map[m].ys); + return 1; + } + + pos = x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs; + + if (bl->type == BL_MOB) { + bl->next = map[m].block_mob[pos]; + bl->prev = &bl_head; + if (bl->next) bl->next->prev = bl; + map[m].block_mob[pos] = bl; + } else { + bl->next = map[m].block[pos]; + bl->prev = &bl_head; + if (bl->next) bl->next->prev = bl; + map[m].block[pos] = bl; + } + +#ifdef CELL_NOSTACK + map_addblcell(bl); +#endif + + return 0; +} + +/*========================================== + * Removes a block from the map. + *------------------------------------------*/ +int map_delblock(struct block_list* bl) +{ + int pos; + nullpo_ret(bl); + + // blocklist (2ways chainlist) + if (bl->prev == NULL) { + if (bl->next != NULL) { + // can't delete block (already at the begining of the chain) + ShowError("map_delblock error : bl->next!=NULL\n"); + } + return 0; + } + +#ifdef CELL_NOSTACK + map_delblcell(bl); +#endif + + pos = bl->x/BLOCK_SIZE+(bl->y/BLOCK_SIZE)*map[bl->m].bxs; + + if (bl->next) + bl->next->prev = bl->prev; + if (bl->prev == &bl_head) { + //Since the head of the list, update the block_list map of [] + if (bl->type == BL_MOB) { + map[bl->m].block_mob[pos] = bl->next; + } else { + map[bl->m].block[pos] = bl->next; + } + } else { + bl->prev->next = bl->next; + } + bl->next = NULL; + bl->prev = NULL; + + return 0; +} + +/*========================================== + * Moves a block a x/y target position. [Skotlex] + * Pass flag as 1 to prevent doing skill_unit_move checks + * (which are executed by default on BL_CHAR types) + *------------------------------------------*/ +int map_moveblock(struct block_list *bl, int x1, int y1, unsigned int tick) +{ + int x0 = bl->x, y0 = bl->y; + struct status_change *sc = NULL; + int moveblock = ( x0/BLOCK_SIZE != x1/BLOCK_SIZE || y0/BLOCK_SIZE != y1/BLOCK_SIZE); + + if (!bl->prev) { + //Block not in map, just update coordinates, but do naught else. + bl->x = x1; + bl->y = y1; + return 0; + } + + //TODO: Perhaps some outs of bounds checking should be placed here? + if (bl->type&BL_CHAR) { + sc = status_get_sc(bl); + + skill_unit_move(bl,tick,2); + status_change_end(bl, SC_CLOSECONFINE, INVALID_TIMER); + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); +// status_change_end(bl, SC_BLADESTOP, INVALID_TIMER); //Won't stop when you are knocked away, go figure... + status_change_end(bl, SC_TATAMIGAESHI, INVALID_TIMER); + status_change_end(bl, SC_MAGICROD, INVALID_TIMER); + if (sc->data[SC_PROPERTYWALK] && + sc->data[SC_PROPERTYWALK]->val3 >= skill_get_maxcount(sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2) ) + status_change_end(bl,SC_PROPERTYWALK,INVALID_TIMER); + } else + if (bl->type == BL_NPC) + npc_unsetcells((TBL_NPC*)bl); + + if (moveblock) map_delblock(bl); +#ifdef CELL_NOSTACK + else map_delblcell(bl); +#endif + bl->x = x1; + bl->y = y1; + if (moveblock) map_addblock(bl); +#ifdef CELL_NOSTACK + else map_addblcell(bl); +#endif + + if (bl->type&BL_CHAR) { + + skill_unit_move(bl,tick,3); + + if( bl->type == BL_PC && ((TBL_PC*)bl)->shadowform_id ) {//Shadow Form Target Moving + struct block_list *d_bl; + if( (d_bl = map_id2bl(((TBL_PC*)bl)->shadowform_id)) == NULL || bl->m != d_bl->m || !check_distance_bl(bl,d_bl,10) ) { + if( d_bl ) + status_change_end(d_bl,SC__SHADOWFORM,INVALID_TIMER); + ((TBL_PC*)bl)->shadowform_id = 0; + } + } + + if (sc && sc->count) { + if (sc->data[SC_DANCING]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_DANCING]->val2), bl->m, x1-x0, y1-y0); + else { + if (sc->data[SC_CLOAKING]) + skill_check_cloaking(bl, sc->data[SC_CLOAKING]); + if (sc->data[SC_WARM]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_WARM]->val4), bl->m, x1-x0, y1-y0); + if (sc->data[SC_BANDING]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_BANDING]->val4), bl->m, x1-x0, y1-y0); + + if (sc->data[SC_NEUTRALBARRIER_MASTER]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_NEUTRALBARRIER_MASTER]->val2), bl->m, x1-x0, y1-y0); + else if (sc->data[SC_STEALTHFIELD_MASTER]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_STEALTHFIELD_MASTER]->val2), bl->m, x1-x0, y1-y0); + + if( sc->data[SC__SHADOWFORM] ) {//Shadow Form Caster Moving + struct block_list *d_bl; + if( (d_bl = map_id2bl(sc->data[SC__SHADOWFORM]->val2)) == NULL || bl->m != d_bl->m || !check_distance_bl(bl,d_bl,10) ) + status_change_end(bl,SC__SHADOWFORM,INVALID_TIMER); + } + + if (sc->data[SC_PROPERTYWALK] + && sc->data[SC_PROPERTYWALK]->val3 < skill_get_maxcount(sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2) + && map_find_skill_unit_oncell(bl,bl->x,bl->y,SO_ELECTRICWALK,NULL,0) == NULL + && map_find_skill_unit_oncell(bl,bl->x,bl->y,SO_FIREWALK,NULL,0) == NULL + && skill_unitsetting(bl,sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2,x0, y0,0)) { + sc->data[SC_PROPERTYWALK]->val3++; + } + + + } + /* Guild Aura Moving */ + if( bl->type == BL_PC && ((TBL_PC*)bl)->state.gmaster_flag ) { + if (sc->data[SC_LEADERSHIP]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_LEADERSHIP]->val4), bl->m, x1-x0, y1-y0); + if (sc->data[SC_GLORYWOUNDS]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_GLORYWOUNDS]->val4), bl->m, x1-x0, y1-y0); + if (sc->data[SC_SOULCOLD]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_SOULCOLD]->val4), bl->m, x1-x0, y1-y0); + if (sc->data[SC_HAWKEYES]) + skill_unit_move_unit_group(skill_id2group(sc->data[SC_HAWKEYES]->val4), bl->m, x1-x0, y1-y0); + } + } + } else + if (bl->type == BL_NPC) + npc_setcells((TBL_NPC*)bl); + + return 0; +} + +/*========================================== + * Counts specified number of objects on given cell. + *------------------------------------------*/ +int map_count_oncell(int16 m, int16 x, int16 y, int type) +{ + int bx,by; + struct block_list *bl; + int count = 0; + + if (x < 0 || y < 0 || (x >= map[m].xs) || (y >= map[m].ys)) + return 0; + + bx = x/BLOCK_SIZE; + by = y/BLOCK_SIZE; + + if (type&~BL_MOB) + for( bl = map[m].block[bx+by*map[m].bxs] ; bl != NULL ; bl = bl->next ) + if(bl->x == x && bl->y == y && bl->type&type) + count++; + + if (type&BL_MOB) + for( bl = map[m].block_mob[bx+by*map[m].bxs] ; bl != NULL ; bl = bl->next ) + if(bl->x == x && bl->y == y) + count++; + + return count; +} +/* + * Looks for a skill unit on a given cell + * flag&1: runs battle_check_target check based on unit->group->target_flag + */ +struct skill_unit* map_find_skill_unit_oncell(struct block_list* target,int16 x,int16 y,uint16 skill_id,struct skill_unit* out_unit, int flag) { + int16 m,bx,by; + struct block_list *bl; + struct skill_unit *unit; + m = target->m; + + if (x < 0 || y < 0 || (x >= map[m].xs) || (y >= map[m].ys)) + return NULL; + + bx = x/BLOCK_SIZE; + by = y/BLOCK_SIZE; + + for( bl = map[m].block[bx+by*map[m].bxs] ; bl != NULL ; bl = bl->next ) + { + if (bl->x != x || bl->y != y || bl->type != BL_SKILL) + continue; + + unit = (struct skill_unit *) bl; + if( unit == out_unit || !unit->alive || !unit->group || unit->group->skill_id != skill_id ) + continue; + if( !(flag&1) || battle_check_target(&unit->bl,target,unit->group->target_flag) > 0 ) + return unit; + } + return NULL; +} + +/*========================================== + * Adapted from foreachinarea for an easier invocation. [Skotlex] + *------------------------------------------*/ +int map_foreachinrange(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int type, ...) +{ + int bx, by, m; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + int x0, x1, y0, y1; + va_list ap; + + m = center->m; + x0 = max(center->x - range, 0); + y0 = max(center->y - range, 0); + x1 = min(center->x + range, map[ m ].xs - 1); + y1 = min(center->y + range, map[ m ].ys - 1); + + if ( type&~BL_MOB ) + for ( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + for( bl = map[m].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->type&type + && bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + + if( type&BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++) { + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinrange: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] +} + +/*========================================== + * Same as foreachinrange, but there must be a shoot-able range between center and target to be counted in. [Skotlex] + *------------------------------------------*/ +int map_foreachinshootrange(int (*func)(struct block_list*,va_list),struct block_list* center, int16 range, int type,...) +{ + int bx, by, m; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + int x0, x1, y0, y1; + va_list ap; + + m = center->m; + if ( m < 0 ) + return 0; + + x0 = max(center->x-range, 0); + y0 = max(center->y-range, 0); + x1 = min(center->x+range, map[m].xs-1); + y1 = min(center->y+range, map[m].ys-1); + + if ( type&~BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->type&type + && bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && path_search_long(NULL, center->m, center->x, center->y, bl->x, bl->y, CELL_CHKWALL) + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + if( type&BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx=x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && path_search_long(NULL, center->m, center->x, center->y, bl->x, bl->y, CELL_CHKWALL) + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinrange: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] +} + +/*========================================== + * range = map m (x0,y0)-(x1,y1) + * Apply *func with ... arguments for the range. + * @type = BL_PC/BL_MOB etc.. + *------------------------------------------*/ +int map_foreachinarea(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int type, ...) +{ + int bx, by; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + va_list ap; + + if ( m < 0 ) + return 0; + + if ( x1 < x0 ) + swap(x0, x1); + if ( y1 < y0 ) + swap(y0, y1); + + x0 = max(x0, 0); + y0 = max(y0, 0); + x1 = min(x1, map[ m ].xs - 1); + y1 = min(y1, map[ m ].ys - 1); + if ( type&~BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) + if( bl->type&type && bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( type&BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) + if( bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinarea: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] +} +/*========================================== + * Adapted from forcountinarea for an easier invocation. [pakpil] + *------------------------------------------*/ +int map_forcountinrange(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int count, int type, ...) +{ + int bx, by, m; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + int x0, x1, y0, y1; + va_list ap; + + m = center->m; + x0 = max(center->x - range, 0); + y0 = max(center->y - range, 0); + x1 = min(center->x + range, map[ m ].xs - 1); + y1 = min(center->y + range, map[ m ].ys - 1); + + if ( type&~BL_MOB ) + for ( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->type&type + && bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + if( type&BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ){ + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 +#ifdef CIRCULAR_AREA + && check_distance_bl(center, bl, range) +#endif + && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_forcountinrange: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + if( count && returnCount >= count ) + break; + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] +} +int map_forcountinarea(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int count, int type, ...) +{ + int bx, by; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + va_list ap; + + if ( m < 0 ) + return 0; + + if ( x1 < x0 ) + swap(x0, x1); + if ( y1 < y0 ) + swap(y0, y1); + + x0 = max(x0, 0); + y0 = max(y0, 0); + x1 = min(x1, map[ m ].xs - 1); + y1 = min(y1, map[ m ].ys - 1); + + if ( type&~BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) + if( bl->type&type && bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( type&BL_MOB ) + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) + if( bl->x >= x0 && bl->x <= x1 && bl->y >= y0 && bl->y <= y1 && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinarea: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if(bl_list[ i ]->prev) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + if( count && returnCount >= count ) + break; + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] +} + +/*========================================== + * For what I get + * Move bl and do func* with va_list while moving. + * Mouvement is set by dx dy wich are distance in x and y + *------------------------------------------*/ +int map_foreachinmovearea(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int16 dx, int16 dy, int type, ...) +{ + int bx, by, m; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + int x0, x1, y0, y1; + va_list ap; + + if ( !range ) return 0; + if ( !dx && !dy ) return 0; //No movement. + + m = center->m; + + x0 = center->x - range; + x1 = center->x + range; + y0 = center->y - range; + y1 = center->y + range; + + if ( x1 < x0 ) + swap(x0, x1); + if ( y1 < y0 ) + swap(y0, y1); + + if( dx == 0 || dy == 0 ) { + //Movement along one axis only. + if( dx == 0 ){ + if( dy < 0 ) //Moving south + y0 = y1 + dy + 1; + else //North + y1 = y0 + dy - 1; + } else { //dy == 0 + if( dx < 0 ) //West + x0 = x1 + dx + 1; + else //East + x1 = x0 + dx - 1; + } + + x0 = max(x0, 0); + y0 = max(y0, 0); + x1 = min(x1, map[ m ].xs - 1); + y1 = min(y1, map[ m ].ys - 1); + + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + if ( type&~BL_MOB ) { + for( bl = map[m].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->type&type && + bl->x >= x0 && bl->x <= x1 && + bl->y >= y0 && bl->y <= y1 && + bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + if ( type&BL_MOB ) { + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->x >= x0 && bl->x <= x1 && + bl->y >= y0 && bl->y <= y1 && + bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + } + } else { // Diagonal movement + + x0 = max(x0, 0); + y0 = max(y0, 0); + x1 = min(x1, map[ m ].xs - 1); + y1 = min(y1, map[ m ].ys - 1); + + for( by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++ ) { + for( bx = x0 / BLOCK_SIZE; bx <= x1 / BLOCK_SIZE; bx++ ) { + if ( type & ~BL_MOB ) { + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->type&type && + bl->x >= x0 && bl->x <= x1 && + bl->y >= y0 && bl->y <= y1 && + bl_list_count < BL_LIST_MAX ) + if( ( dx > 0 && bl->x < x0 + dx) || + ( dx < 0 && bl->x > x1 + dx) || + ( dy > 0 && bl->y < y0 + dy) || + ( dy < 0 && bl->y > y1 + dy) ) + bl_list[ bl_list_count++ ] = bl; + } + } + if ( type&BL_MOB ) { + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->x >= x0 && bl->x <= x1 && + bl->y >= y0 && bl->y <= y1 && + bl_list_count < BL_LIST_MAX) + if( ( dx > 0 && bl->x < x0 + dx) || + ( dx < 0 && bl->x > x1 + dx) || + ( dy > 0 && bl->y < y0 + dy) || + ( dy < 0 && bl->y > y1 + dy) ) + bl_list[ bl_list_count++ ] = bl; + } + } + } + } + + } + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinmovearea: block count too many!\n"); + + map_freeblock_lock(); // Prohibit the release from memory + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); // Allow Free + + bl_list_count = blockcount; + return returnCount; +} + +// -- moonsoul (added map_foreachincell which is a rework of map_foreachinarea but +// which only checks the exact single x/y passed to it rather than an +// area radius - may be more useful in some instances) +// +int map_foreachincell(int (*func)(struct block_list*,va_list), int16 m, int16 x, int16 y, int type, ...) +{ + int bx, by; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + va_list ap; + + if ( x < 0 || y < 0 || x >= map[ m ].xs || y >= map[ m ].ys ) return 0; + + by = y / BLOCK_SIZE; + bx = x / BLOCK_SIZE; + + if( type&~BL_MOB ) + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) + if( bl->type&type && bl->x == x && bl->y == y && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + if( type&BL_MOB ) + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs]; bl != NULL; bl = bl->next ) + if( bl->x == x && bl->y == y && bl_list_count < BL_LIST_MAX) + bl_list[ bl_list_count++ ] = bl; + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachincell: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; +} + +/*============================================================ +* For checking a path between two points (x0, y0) and (x1, y1) +*------------------------------------------------------------*/ +int map_foreachinpath(int (*func)(struct block_list*,va_list),int16 m,int16 x0,int16 y0,int16 x1,int16 y1,int16 range,int length, int type,...) +{ + int returnCount = 0; //total sum of returned values of func() [Skotlex] +////////////////////////////////////////////////////////////// +// +// sharp shooting 3 [Skotlex] +// +////////////////////////////////////////////////////////////// +// problem: +// Same as Sharp Shooting 1. Hits all targets within range of +// the line. +// (t1,t2 t3 and t4 get hit) +// +// target 1 +// x t4 +// t2 +// t3 x +// x +// S +////////////////////////////////////////////////////////////// +// Methodology: +// My trigonometrics and math are a little rusty... so the approach I am writing +// here is basicly do a double for to check for all targets in the square that +// contains the initial and final positions (area range increased to match the +// radius given), then for each object to test, calculate the distance to the +// path and include it if the range fits and the target is in the line (0<k<1, +// as they call it). +// The implementation I took as reference is found at +// http://astronomy.swin.edu.au/~pbourke/geometry/pointline/ +// (they have a link to a C implementation, too) +// This approach is a lot like #2 commented on this function, which I have no +// idea why it was commented. I won't use doubles/floats, but pure int math for +// speed purposes. The range considered is always the same no matter how +// close/far the target is because that's how SharpShooting works currently in +// kRO. + + //Generic map_foreach* variables. + int i, blockcount = bl_list_count; + struct block_list *bl; + int bx, by; + //method specific variables + int magnitude2, len_limit; //The square of the magnitude + int k, xi, yi, xu, yu; + int mx0 = x0, mx1 = x1, my0 = y0, my1 = y1; + va_list ap; + + //Avoid needless calculations by not getting the sqrt right away. + #define MAGNITUDE2(x0, y0, x1, y1) ( ( ( x1 ) - ( x0 ) ) * ( ( x1 ) - ( x0 ) ) + ( ( y1 ) - ( y0 ) ) * ( ( y1 ) - ( y0 ) ) ) + + if ( m < 0 ) + return 0; + + len_limit = magnitude2 = MAGNITUDE2(x0, y0, x1, y1); + if ( magnitude2 < 1 ) //Same begin and ending point, can't trace path. + return 0; + + if ( length ) { //Adjust final position to fit in the given area. + //TODO: Find an alternate method which does not requires a square root calculation. + k = (int)sqrt((float)magnitude2); + mx1 = x0 + (x1 - x0) * length / k; + my1 = y0 + (y1 - y0) * length / k; + len_limit = MAGNITUDE2(x0, y0, mx1, my1); + } + //Expand target area to cover range. + if ( mx0 > mx1 ) { + mx0 += range; + mx1 -= range; + } else { + mx0 -= range; + mx1 += range; + } + if (my0 > my1) { + my0 += range; + my1 -= range; + } else { + my0 -= range; + my1 += range; + } + + //The two fors assume mx0 < mx1 && my0 < my1 + if ( mx0 > mx1 ) + swap(mx0, mx1); + if ( my0 > my1 ) + swap(my0, my1); + + mx0 = max(mx0, 0); + my0 = max(my0, 0); + mx1 = min(mx1, map[ m ].xs - 1); + my1 = min(my1, map[ m ].ys - 1); + + range *= range << 8; //Values are shifted later on for higher precision using int math. + + if ( type&~BL_MOB ) + for ( by = my0 / BLOCK_SIZE; by <= my1 / BLOCK_SIZE; by++ ) { + for( bx = mx0 / BLOCK_SIZE; bx <= mx1 / BLOCK_SIZE; bx++ ) { + for( bl = map[ m ].block[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->prev && bl->type&type && bl_list_count < BL_LIST_MAX ) { + xi = bl->x; + yi = bl->y; + + k = ( xi - x0 ) * ( x1 - x0 ) + ( yi - y0 ) * ( y1 - y0 ); + + if ( k < 0 || k > len_limit ) //Since more skills use this, check for ending point as well. + continue; + + if ( k > magnitude2 && !path_search_long(NULL, m, x0, y0, xi, yi, CELL_CHKWALL) ) + continue; //Targets beyond the initial ending point need the wall check. + + //All these shifts are to increase the precision of the intersection point and distance considering how it's + //int math. + k = ( k << 4 ) / magnitude2; //k will be between 1~16 instead of 0~1 + xi <<= 4; + yi <<= 4; + xu = ( x0 << 4 ) + k * ( x1 - x0 ); + yu = ( y0 << 4 ) + k * ( y1 - y0 ); + k = MAGNITUDE2(xi, yi, xu, yu); + + //If all dot coordinates were <<4 the square of the magnitude is <<8 + if ( k > range ) + continue; + + bl_list[ bl_list_count++ ] = bl; + } + } + } + } + if( type&BL_MOB ) + for( by = my0 / BLOCK_SIZE; by <= my1 / BLOCK_SIZE; by++ ) { + for( bx = mx0 / BLOCK_SIZE; bx <= mx1 / BLOCK_SIZE; bx++ ) { + for( bl = map[ m ].block_mob[ bx + by * map[ m ].bxs ]; bl != NULL; bl = bl->next ) { + if( bl->prev && bl_list_count < BL_LIST_MAX ) { + xi = bl->x; + yi = bl->y; + k = ( xi - x0 ) * ( x1 - x0 ) + ( yi - y0 ) * ( y1 - y0 ); + + if ( k < 0 || k > len_limit ) + continue; + + if ( k > magnitude2 && !path_search_long(NULL, m, x0, y0, xi, yi, CELL_CHKWALL) ) + continue; //Targets beyond the initial ending point need the wall check. + + k = ( k << 4 ) / magnitude2; //k will be between 1~16 instead of 0~1 + xi <<= 4; + yi <<= 4; + xu = ( x0 << 4 ) + k * ( x1 - x0 ); + yu = ( y0 << 4 ) + k * ( y1 - y0 ); + k = MAGNITUDE2(xi, yi, xu, yu); + + //If all dot coordinates were <<4 the square of the magnitude is <<8 + if ( k > range ) + continue; + + bl_list[ bl_list_count++ ] = bl; + } + } + } + } + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinpath: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; //[Skotlex] + +} + +// Copy of map_foreachincell, but applied to the whole map. [Skotlex] +int map_foreachinmap(int (*func)(struct block_list*,va_list), int16 m, int type,...) +{ + int b, bsize; + int returnCount = 0; //total sum of returned values of func() [Skotlex] + struct block_list *bl; + int blockcount = bl_list_count, i; + va_list ap; + + bsize = map[ m ].bxs * map[ m ].bys; + + if( type&~BL_MOB ) + for( b = 0; b < bsize; b++ ) + for( bl = map[ m ].block[ b ]; bl != NULL; bl = bl->next ) + if( bl->type&type && bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( type&BL_MOB ) + for( b = 0; b < bsize; b++ ) + for( bl = map[ m ].block_mob[ b ]; bl != NULL; bl = bl->next ) + if( bl_list_count < BL_LIST_MAX ) + bl_list[ bl_list_count++ ] = bl; + + if( bl_list_count >= BL_LIST_MAX ) + ShowWarning("map_foreachinmap: block count too many!\n"); + + map_freeblock_lock(); + + for( i = blockcount; i < bl_list_count ; i++ ) + if( bl_list[ i ]->prev ) { //func() may delete this bl_list[] slot, checking for prev ensures it wasnt queued for deletion. + va_start(ap, type); + returnCount += func(bl_list[ i ], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + bl_list_count = blockcount; + return returnCount; +} + + +/// Generates a new flooritem object id from the interval [MIN_FLOORITEM, MAX_FLOORITEM). +/// Used for floor items, skill units and chatroom objects. +/// @return The new object id +int map_get_new_object_id(void) +{ + static int last_object_id = MIN_FLOORITEM - 1; + int i; + + // find a free id + i = last_object_id + 1; + while( i != last_object_id ) { + if( i == MAX_FLOORITEM ) + i = MIN_FLOORITEM; + + if( !idb_exists(id_db, i) ) + break; + + ++i; + } + + if( i == last_object_id ) { + ShowError("map_addobject: no free object id!\n"); + return 0; + } + + // update cursor + last_object_id = i; + + return i; +} + +/*========================================== + * Timered function to clear the floor (remove remaining item) + * Called each flooritem_lifetime ms + *------------------------------------------*/ +int map_clearflooritem_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct flooritem_data* fitem = (struct flooritem_data*)idb_get(id_db, id); + + if (fitem == NULL || fitem->bl.type != BL_ITEM || (fitem->cleartimer != tid)) { + ShowError("map_clearflooritem_timer : error\n"); + return 1; + } + + + if (search_petDB_index(fitem->item_data.nameid, PET_EGG) >= 0) + intif_delete_petdata(MakeDWord(fitem->item_data.card[1], fitem->item_data.card[2])); + + clif_clearflooritem(fitem, 0); + map_deliddb(&fitem->bl); + map_delblock(&fitem->bl); + map_freeblock(&fitem->bl); + return 0; +} + +/* + * clears a single bl item out of the bazooonga. + */ +void map_clearflooritem(struct block_list *bl) { + struct flooritem_data* fitem = (struct flooritem_data*)bl; + + if( fitem->cleartimer ) + delete_timer(fitem->cleartimer,map_clearflooritem_timer); + + clif_clearflooritem(fitem, 0); + map_deliddb(&fitem->bl); + map_delblock(&fitem->bl); + map_freeblock(&fitem->bl); +} + +/*========================================== + * (m,x,y) locates a random available free cell around the given coordinates + * to place an BL_ITEM object. Scan area is 9x9, returns 1 on success. + * x and y are modified with the target cell when successful. + *------------------------------------------*/ +int map_searchrandfreecell(int16 m,int16 *x,int16 *y,int stack) { + int free_cell,i,j; + int free_cells[9][2]; + + for(free_cell=0,i=-1;i<=1;i++){ + if(i+*y<0 || i+*y>=map[m].ys) + continue; + for(j=-1;j<=1;j++){ + if(j+*x<0 || j+*x>=map[m].xs) + continue; + if(map_getcell(m,j+*x,i+*y,CELL_CHKNOPASS) && !map_getcell(m,j+*x,i+*y,CELL_CHKICEWALL)) + continue; + //Avoid item stacking to prevent against exploits. [Skotlex] + if(stack && map_count_oncell(m,j+*x,i+*y, BL_ITEM) > stack) + continue; + free_cells[free_cell][0] = j+*x; + free_cells[free_cell++][1] = i+*y; + } + } + if(free_cell==0) + return 0; + free_cell = rnd()%free_cell; + *x = free_cells[free_cell][0]; + *y = free_cells[free_cell][1]; + return 1; +} + + +static int map_count_sub(struct block_list *bl,va_list ap) +{ + return 1; +} + +/*========================================== + * Locates a random spare cell around the object given, using range as max + * distance from that spot. Used for warping functions. Use range < 0 for + * whole map range. + * Returns 1 on success. when it fails and src is available, x/y are set to src's + * src can be null as long as flag&1 + * when ~flag&1, m is not needed. + * Flag values: + * &1 = random cell must be around given m,x,y, not around src + * &2 = the target should be able to walk to the target tile. + * &4 = there shouldn't be any players around the target tile (use the no_spawn_on_player setting) + *------------------------------------------*/ +int map_search_freecell(struct block_list *src, int16 m, int16 *x,int16 *y, int16 rx, int16 ry, int flag) +{ + int tries, spawn=0; + int bx, by; + int rx2 = 2*rx+1; + int ry2 = 2*ry+1; + + if( !src && (!(flag&1) || flag&2) ) + { + ShowDebug("map_search_freecell: Incorrect usage! When src is NULL, flag has to be &1 and can't have &2\n"); + return 0; + } + + if (flag&1) { + bx = *x; + by = *y; + } else { + bx = src->x; + by = src->y; + m = src->m; + } + if (!rx && !ry) { + //No range? Return the target cell then.... + *x = bx; + *y = by; + return map_getcell(m,*x,*y,CELL_CHKREACH); + } + + if (rx >= 0 && ry >= 0) { + tries = rx2*ry2; + if (tries > 100) tries = 100; + } else { + tries = map[m].xs*map[m].ys; + if (tries > 500) tries = 500; + } + + while(tries--) { + *x = (rx >= 0)?(rnd()%rx2-rx+bx):(rnd()%(map[m].xs-2)+1); + *y = (ry >= 0)?(rnd()%ry2-ry+by):(rnd()%(map[m].ys-2)+1); + + if (*x == bx && *y == by) + continue; //Avoid picking the same target tile. + + if (map_getcell(m,*x,*y,CELL_CHKREACH)) + { + if(flag&2 && !unit_can_reach_pos(src, *x, *y, 1)) + continue; + if(flag&4) { + if (spawn >= 100) return 0; //Limit of retries reached. + if (spawn++ < battle_config.no_spawn_on_player && + map_foreachinarea(map_count_sub, m, + *x-AREA_SIZE, *y-AREA_SIZE, + *x+AREA_SIZE, *y+AREA_SIZE, BL_PC) + ) + continue; + } + return 1; + } + } + *x = bx; + *y = by; + return 0; +} + +/*========================================== + * Add an item to location (m,x,y) + * Parameters + * @item_data item attributes + * @amount quantity + * @m, @x, @y mapid,x,y + * @first_charid, @second_charid, @third_charid, looting priority + * @flag: &1 MVP item. &2 do stacking check. + *------------------------------------------*/ +int map_addflooritem(struct item *item_data,int amount,int16 m,int16 x,int16 y,int first_charid,int second_charid,int third_charid,int flags) +{ + int r; + struct flooritem_data *fitem=NULL; + + nullpo_ret(item_data); + + if(!map_searchrandfreecell(m,&x,&y,flags&2?1:0)) + return 0; + r=rnd(); + + CREATE(fitem, struct flooritem_data, 1); + fitem->bl.type=BL_ITEM; + fitem->bl.prev = fitem->bl.next = NULL; + fitem->bl.m=m; + fitem->bl.x=x; + fitem->bl.y=y; + fitem->bl.id = map_get_new_object_id(); + if(fitem->bl.id==0){ + aFree(fitem); + return 0; + } + + fitem->first_get_charid = first_charid; + fitem->first_get_tick = gettick() + (flags&1 ? battle_config.mvp_item_first_get_time : battle_config.item_first_get_time); + fitem->second_get_charid = second_charid; + fitem->second_get_tick = fitem->first_get_tick + (flags&1 ? battle_config.mvp_item_second_get_time : battle_config.item_second_get_time); + fitem->third_get_charid = third_charid; + fitem->third_get_tick = fitem->second_get_tick + (flags&1 ? battle_config.mvp_item_third_get_time : battle_config.item_third_get_time); + + memcpy(&fitem->item_data,item_data,sizeof(*item_data)); + fitem->item_data.amount=amount; + fitem->subx=(r&3)*3+3; + fitem->suby=((r>>2)&3)*3+3; + fitem->cleartimer=add_timer(gettick()+battle_config.flooritem_lifetime,map_clearflooritem_timer,fitem->bl.id,0); + + map_addiddb(&fitem->bl); + map_addblock(&fitem->bl); + clif_dropflooritem(fitem); + + return fitem->bl.id; +} + +/** + * @see DBCreateData + */ +static DBData create_charid2nick(DBKey key, va_list args) +{ + struct charid2nick *p; + CREATE(p, struct charid2nick, 1); + return db_ptr2data(p); +} + +/// Adds(or replaces) the nick of charid to nick_db and fullfils pending requests. +/// Does nothing if the character is online. +void map_addnickdb(int charid, const char* nick) +{ + struct charid2nick* p; + struct charid_request* req; + struct map_session_data* sd; + + if( map_charid2sd(charid) ) + return;// already online + + p = idb_ensure(nick_db, charid, create_charid2nick); + safestrncpy(p->nick, nick, sizeof(p->nick)); + + while( p->requests ) + { + req = p->requests; + p->requests = req->next; + sd = map_charid2sd(req->charid); + if( sd ) + clif_solved_charname(sd->fd, charid, p->nick); + aFree(req); + } +} + +/// Removes the nick of charid from nick_db. +/// Sends name to all pending requests on charid. +void map_delnickdb(int charid, const char* name) +{ + struct charid2nick* p; + struct charid_request* req; + struct map_session_data* sd; + DBData data; + + if (!nick_db->remove(nick_db, db_i2key(charid), &data) || (p = db_data2ptr(&data)) == NULL) + return; + + while( p->requests ) + { + req = p->requests; + p->requests = req->next; + sd = map_charid2sd(req->charid); + if( sd ) + clif_solved_charname(sd->fd, charid, name); + aFree(req); + } + aFree(p); +} + +/// Notifies sd of the nick of charid. +/// Uses the name in the character if online. +/// Uses the name in nick_db if offline. +void map_reqnickdb(struct map_session_data * sd, int charid) +{ + struct charid2nick* p; + struct charid_request* req; + struct map_session_data* tsd; + + nullpo_retv(sd); + + tsd = map_charid2sd(charid); + if( tsd ) + { + clif_solved_charname(sd->fd, charid, tsd->status.name); + return; + } + + p = idb_ensure(nick_db, charid, create_charid2nick); + if( *p->nick ) + { + clif_solved_charname(sd->fd, charid, p->nick); + return; + } + // not in cache, request it + CREATE(req, struct charid_request, 1); + req->next = p->requests; + p->requests = req; + chrif_searchcharid(charid); +} + +/*========================================== + * add bl to id_db + *------------------------------------------*/ +void map_addiddb(struct block_list *bl) +{ + nullpo_retv(bl); + + if( bl->type == BL_PC ) + { + TBL_PC* sd = (TBL_PC*)bl; + idb_put(pc_db,sd->bl.id,sd); + idb_put(charid_db,sd->status.char_id,sd); + } + else if( bl->type == BL_MOB ) + { + TBL_MOB* md = (TBL_MOB*)bl; + idb_put(mobid_db,bl->id,bl); + + if( md->state.boss ) + idb_put(bossid_db, bl->id, bl); + } + + if( bl->type & BL_REGEN ) + idb_put(regen_db, bl->id, bl); + + idb_put(id_db,bl->id,bl); +} + +/*========================================== + * remove bl from id_db + *------------------------------------------*/ +void map_deliddb(struct block_list *bl) +{ + nullpo_retv(bl); + + if( bl->type == BL_PC ) + { + TBL_PC* sd = (TBL_PC*)bl; + idb_remove(pc_db,sd->bl.id); + idb_remove(charid_db,sd->status.char_id); + } + else if( bl->type == BL_MOB ) + { + idb_remove(mobid_db,bl->id); + idb_remove(bossid_db,bl->id); + } + + if( bl->type & BL_REGEN ) + idb_remove(regen_db,bl->id); + + idb_remove(id_db,bl->id); +} + +/*========================================== + * Standard call when a player connection is closed. + *------------------------------------------*/ +int map_quit(struct map_session_data *sd) { + int i; + + if(!sd->state.active) { //Removing a player that is not active. + struct auth_node *node = chrif_search(sd->status.account_id); + if (node && node->char_id == sd->status.char_id && + node->state != ST_LOGOUT) + //Except when logging out, clear the auth-connect data immediately. + chrif_auth_delete(node->account_id, node->char_id, node->state); + //Non-active players should not have loaded any data yet (or it was cleared already) so no additional cleanups are needed. + return 0; + } + + if (sd->npc_timer_id != INVALID_TIMER) //Cancel the event timer. + npc_timerevent_quit(sd); + + if (sd->npc_id) + npc_event_dequeue(sd); + + if( sd->bg_id ) + bg_team_leave(sd,1); + + pc_itemcd_do(sd,false); + + npc_script_event(sd, NPCE_LOGOUT); + + //Unit_free handles clearing the player related data, + //map_quit handles extra specific data which is related to quitting normally + //(changing map-servers invokes unit_free but bypasses map_quit) + if( sd->sc.count ) { + //Status that are not saved... + status_change_end(&sd->bl, SC_BOSSMAPINFO, INVALID_TIMER); + status_change_end(&sd->bl, SC_AUTOTRADE, INVALID_TIMER); + status_change_end(&sd->bl, SC_SPURT, INVALID_TIMER); + status_change_end(&sd->bl, SC_BERSERK, INVALID_TIMER); + status_change_end(&sd->bl, SC__BLOODYLUST, INVALID_TIMER); + status_change_end(&sd->bl, SC_TRICKDEAD, INVALID_TIMER); + status_change_end(&sd->bl, SC_LEADERSHIP, INVALID_TIMER); + status_change_end(&sd->bl, SC_GLORYWOUNDS, INVALID_TIMER); + status_change_end(&sd->bl, SC_SOULCOLD, INVALID_TIMER); + status_change_end(&sd->bl, SC_HAWKEYES, INVALID_TIMER); + if(sd->sc.data[SC_ENDURE] && sd->sc.data[SC_ENDURE]->val4) + status_change_end(&sd->bl, SC_ENDURE, INVALID_TIMER); //No need to save infinite endure. + status_change_end(&sd->bl, SC_WEIGHT50, INVALID_TIMER); + status_change_end(&sd->bl, SC_WEIGHT90, INVALID_TIMER); + status_change_end(&sd->bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + status_change_end(&sd->bl, SC_KYOUGAKU, INVALID_TIMER); + if (battle_config.debuff_on_logout&1) { + status_change_end(&sd->bl, SC_ORCISH, INVALID_TIMER); + status_change_end(&sd->bl, SC_STRIPWEAPON, INVALID_TIMER); + status_change_end(&sd->bl, SC_STRIPARMOR, INVALID_TIMER); + status_change_end(&sd->bl, SC_STRIPSHIELD, INVALID_TIMER); + status_change_end(&sd->bl, SC_STRIPHELM, INVALID_TIMER); + status_change_end(&sd->bl, SC_EXTREMITYFIST, INVALID_TIMER); + status_change_end(&sd->bl, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + if(sd->sc.data[SC_REGENERATION] && sd->sc.data[SC_REGENERATION]->val4) + status_change_end(&sd->bl, SC_REGENERATION, INVALID_TIMER); + //TO-DO Probably there are way more NPC_type negative status that are removed + status_change_end(&sd->bl, SC_CHANGEUNDEAD, INVALID_TIMER); + // Both these statuses are removed on logout. [L0ne_W0lf] + status_change_end(&sd->bl, SC_SLOWCAST, INVALID_TIMER); + status_change_end(&sd->bl, SC_CRITICALWOUND, INVALID_TIMER); + } + if (battle_config.debuff_on_logout&2) { + status_change_end(&sd->bl, SC_MAXIMIZEPOWER, INVALID_TIMER); + status_change_end(&sd->bl, SC_MAXOVERTHRUST, INVALID_TIMER); + status_change_end(&sd->bl, SC_STEELBODY, INVALID_TIMER); + status_change_end(&sd->bl, SC_PRESERVE, INVALID_TIMER); + status_change_end(&sd->bl, SC_KAAHI, INVALID_TIMER); + status_change_end(&sd->bl, SC_SPIRIT, INVALID_TIMER); + } + } + + for( i = 0; i < EQI_MAX; i++ ) { + if( sd->equip_index[ i ] >= 0 ) + if( !pc_isequip( sd , sd->equip_index[ i ] ) ) + pc_unequipitem( sd , sd->equip_index[ i ] , 2 ); + } + + // Return loot to owner + if( sd->pd ) pet_lootitem_drop(sd->pd, sd); + + if( sd->state.storage_flag == 1 ) sd->state.storage_flag = 0; // No need to Double Save Storage on Quit. + + if( sd->ed ) { + elemental_clean_effect(sd->ed); + unit_remove_map(&sd->ed->bl,CLR_TELEPORT); + } + + unit_remove_map_pc(sd,CLR_TELEPORT); + + if( map[sd->bl.m].instance_id ) + { // Avoid map conflicts and warnings on next login + int16 m; + struct point *pt; + if( map[sd->bl.m].save.map ) + pt = &map[sd->bl.m].save; + else + pt = &sd->status.save_point; + + if( (m=map_mapindex2mapid(pt->map)) >= 0 ) + { + sd->bl.m = m; + sd->bl.x = pt->x; + sd->bl.y = pt->y; + sd->mapindex = pt->map; + } + } + + party_booking_delete(sd); // Party Booking [Spiria] + pc_makesavestatus(sd); + pc_clean_skilltree(sd); + chrif_save(sd,1); + unit_free_pc(sd); + return 0; +} + +/*========================================== + * Lookup, id to session (player,mob,npc,homon,merc..) + *------------------------------------------*/ +struct map_session_data * map_id2sd(int id) +{ + if (id <= 0) return NULL; + return (struct map_session_data*)idb_get(pc_db,id); +} + +struct mob_data * map_id2md(int id) +{ + if (id <= 0) return NULL; + return (struct mob_data*)idb_get(mobid_db,id); +} + +struct npc_data * map_id2nd(int id) +{// just a id2bl lookup because there's no npc_db + struct block_list* bl = map_id2bl(id); + + return BL_CAST(BL_NPC, bl); +} + +struct homun_data* map_id2hd(int id) +{ + struct block_list* bl = map_id2bl(id); + + return BL_CAST(BL_HOM, bl); +} + +struct mercenary_data* map_id2mc(int id) +{ + struct block_list* bl = map_id2bl(id); + + return BL_CAST(BL_MER, bl); +} + +struct chat_data* map_id2cd(int id) +{ + struct block_list* bl = map_id2bl(id); + + return BL_CAST(BL_CHAT, bl); +} + +/// Returns the nick of the target charid or NULL if unknown (requests the nick to the char server). +const char* map_charid2nick(int charid) +{ + struct charid2nick *p; + struct map_session_data* sd; + + sd = map_charid2sd(charid); + if( sd ) + return sd->status.name;// character is online, return it's name + + p = idb_ensure(nick_db, charid, create_charid2nick); + if( *p->nick ) + return p->nick;// name in nick_db + + chrif_searchcharid(charid);// request the name + return NULL; +} + +/// Returns the struct map_session_data of the charid or NULL if the char is not online. +struct map_session_data* map_charid2sd(int charid) +{ + return (struct map_session_data*)idb_get(charid_db, charid); +} + +/*========================================== + * Search session data from a nick name + * (without sensitive case if necessary) + * return map_session_data pointer or NULL + *------------------------------------------*/ +struct map_session_data * map_nick2sd(const char *nick) +{ + struct map_session_data* sd; + struct map_session_data* found_sd; + struct s_mapiterator* iter; + size_t nicklen; + int qty = 0; + + if( nick == NULL ) + return NULL; + + nicklen = strlen(nick); + iter = mapit_getallusers(); + + found_sd = NULL; + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if( battle_config.partial_name_scan ) + {// partial name search + if( strnicmp(sd->status.name, nick, nicklen) == 0 ) + { + found_sd = sd; + + if( strcmp(sd->status.name, nick) == 0 ) + {// Perfect Match + qty = 1; + break; + } + + qty++; + } + } + else if( strcasecmp(sd->status.name, nick) == 0 ) + {// exact search only + found_sd = sd; + break; + } + } + mapit_free(iter); + + if( battle_config.partial_name_scan && qty != 1 ) + found_sd = NULL; + + return found_sd; +} + +/*========================================== + * Looksup id_db DBMap and returns BL pointer of 'id' or NULL if not found + *------------------------------------------*/ +struct block_list * map_id2bl(int id) { + return (struct block_list*)idb_get(id_db,id); +} + +/** + * Same as map_id2bl except it only checks for its existence + **/ +bool map_blid_exists( int id ) { + return (idb_exists(id_db,id)); +} + +/*========================================== + * Convext Mirror + *------------------------------------------*/ +struct mob_data * map_getmob_boss(int16 m) +{ + DBIterator* iter; + struct mob_data *md = NULL; + bool found = false; + + iter = db_iterator(bossid_db); + for( md = (struct mob_data*)dbi_first(iter); dbi_exists(iter); md = (struct mob_data*)dbi_next(iter) ) + { + if( md->bl.m == m ) + { + found = true; + break; + } + } + dbi_destroy(iter); + + return (found)? md : NULL; +} + +struct mob_data * map_id2boss(int id) +{ + if (id <= 0) return NULL; + return (struct mob_data*)idb_get(bossid_db,id); +} + +/// Applies func to all the players in the db. +/// Stops iterating if func returns -1. +void map_foreachpc(int (*func)(struct map_session_data* sd, va_list args), ...) +{ + DBIterator* iter; + struct map_session_data* sd; + + iter = db_iterator(pc_db); + for( sd = dbi_first(iter); dbi_exists(iter); sd = dbi_next(iter) ) + { + va_list args; + int ret; + + va_start(args, func); + ret = func(sd, args); + va_end(args); + if( ret == -1 ) + break;// stop iterating + } + dbi_destroy(iter); +} + +/// Applies func to all the mobs in the db. +/// Stops iterating if func returns -1. +void map_foreachmob(int (*func)(struct mob_data* md, va_list args), ...) +{ + DBIterator* iter; + struct mob_data* md; + + iter = db_iterator(mobid_db); + for( md = (struct mob_data*)dbi_first(iter); dbi_exists(iter); md = (struct mob_data*)dbi_next(iter) ) + { + va_list args; + int ret; + + va_start(args, func); + ret = func(md, args); + va_end(args); + if( ret == -1 ) + break;// stop iterating + } + dbi_destroy(iter); +} + +/// Applies func to all the npcs in the db. +/// Stops iterating if func returns -1. +void map_foreachnpc(int (*func)(struct npc_data* nd, va_list args), ...) +{ + DBIterator* iter; + struct block_list* bl; + + iter = db_iterator(id_db); + for( bl = (struct block_list*)dbi_first(iter); dbi_exists(iter); bl = (struct block_list*)dbi_next(iter) ) + { + if( bl->type == BL_NPC ) + { + struct npc_data* nd = (struct npc_data*)bl; + va_list args; + int ret; + + va_start(args, func); + ret = func(nd, args); + va_end(args); + if( ret == -1 ) + break;// stop iterating + } + } + dbi_destroy(iter); +} + +/// Applies func to everything in the db. +/// Stops iteratin gif func returns -1. +void map_foreachregen(int (*func)(struct block_list* bl, va_list args), ...) +{ + DBIterator* iter; + struct block_list* bl; + + iter = db_iterator(regen_db); + for( bl = (struct block_list*)dbi_first(iter); dbi_exists(iter); bl = (struct block_list*)dbi_next(iter) ) + { + va_list args; + int ret; + + va_start(args, func); + ret = func(bl, args); + va_end(args); + if( ret == -1 ) + break;// stop iterating + } + dbi_destroy(iter); +} + +/// Applies func to everything in the db. +/// Stops iterating if func returns -1. +void map_foreachiddb(int (*func)(struct block_list* bl, va_list args), ...) +{ + DBIterator* iter; + struct block_list* bl; + + iter = db_iterator(id_db); + for( bl = (struct block_list*)dbi_first(iter); dbi_exists(iter); bl = (struct block_list*)dbi_next(iter) ) + { + va_list args; + int ret; + + va_start(args, func); + ret = func(bl, args); + va_end(args); + if( ret == -1 ) + break;// stop iterating + } + dbi_destroy(iter); +} + +/// Iterator. +/// Can filter by bl type. +struct s_mapiterator +{ + enum e_mapitflags flags;// flags for special behaviour + enum bl_type types;// what bl types to return + DBIterator* dbi;// database iterator +}; + +/// Returns true if the block_list matches the description in the iterator. +/// +/// @param _mapit_ Iterator +/// @param _bl_ block_list +/// @return true if it matches +#define MAPIT_MATCHES(_mapit_,_bl_) \ + ( \ + ( (_bl_)->type & (_mapit_)->types /* type matches */ ) \ + ) + +/// Allocates a new iterator. +/// Returns the new iterator. +/// types can represent several BL's as a bit field. +/// TODO should this be expanded to allow filtering of map/guild/party/chat/cell/area/...? +/// +/// @param flags Flags of the iterator +/// @param type Target types +/// @return Iterator +struct s_mapiterator* mapit_alloc(enum e_mapitflags flags, enum bl_type types) +{ + struct s_mapiterator* mapit; + + CREATE(mapit, struct s_mapiterator, 1); + mapit->flags = flags; + mapit->types = types; + if( types == BL_PC ) mapit->dbi = db_iterator(pc_db); + else if( types == BL_MOB ) mapit->dbi = db_iterator(mobid_db); + else mapit->dbi = db_iterator(id_db); + return mapit; +} + +/// Frees the iterator. +/// +/// @param mapit Iterator +void mapit_free(struct s_mapiterator* mapit) +{ + nullpo_retv(mapit); + + dbi_destroy(mapit->dbi); + aFree(mapit); +} + +/// Returns the first block_list that matches the description. +/// Returns NULL if not found. +/// +/// @param mapit Iterator +/// @return first block_list or NULL +struct block_list* mapit_first(struct s_mapiterator* mapit) +{ + struct block_list* bl; + + nullpo_retr(NULL,mapit); + + for( bl = (struct block_list*)dbi_first(mapit->dbi); bl != NULL; bl = (struct block_list*)dbi_next(mapit->dbi) ) + { + if( MAPIT_MATCHES(mapit,bl) ) + break;// found match + } + return bl; +} + +/// Returns the last block_list that matches the description. +/// Returns NULL if not found. +/// +/// @param mapit Iterator +/// @return last block_list or NULL +struct block_list* mapit_last(struct s_mapiterator* mapit) +{ + struct block_list* bl; + + nullpo_retr(NULL,mapit); + + for( bl = (struct block_list*)dbi_last(mapit->dbi); bl != NULL; bl = (struct block_list*)dbi_prev(mapit->dbi) ) + { + if( MAPIT_MATCHES(mapit,bl) ) + break;// found match + } + return bl; +} + +/// Returns the next block_list that matches the description. +/// Returns NULL if not found. +/// +/// @param mapit Iterator +/// @return next block_list or NULL +struct block_list* mapit_next(struct s_mapiterator* mapit) +{ + struct block_list* bl; + + nullpo_retr(NULL,mapit); + + for( ; ; ) + { + bl = (struct block_list*)dbi_next(mapit->dbi); + if( bl == NULL ) + break;// end + if( MAPIT_MATCHES(mapit,bl) ) + break;// found a match + // try next + } + return bl; +} + +/// Returns the previous block_list that matches the description. +/// Returns NULL if not found. +/// +/// @param mapit Iterator +/// @return previous block_list or NULL +struct block_list* mapit_prev(struct s_mapiterator* mapit) +{ + struct block_list* bl; + + nullpo_retr(NULL,mapit); + + for( ; ; ) + { + bl = (struct block_list*)dbi_prev(mapit->dbi); + if( bl == NULL ) + break;// end + if( MAPIT_MATCHES(mapit,bl) ) + break;// found a match + // try prev + } + return bl; +} + +/// Returns true if the current block_list exists in the database. +/// +/// @param mapit Iterator +/// @return true if it exists +bool mapit_exists(struct s_mapiterator* mapit) +{ + nullpo_retr(false,mapit); + + return dbi_exists(mapit->dbi); +} + +/*========================================== + * Add npc-bl to id_db, basically register npc to map + *------------------------------------------*/ +bool map_addnpc(int16 m,struct npc_data *nd) +{ + nullpo_ret(nd); + + if( m < 0 || m >= map_num ) + return false; + + if( map[m].npc_num == MAX_NPC_PER_MAP ) + { + ShowWarning("too many NPCs in one map %s\n",map[m].name); + return false; + } + + map[m].npc[map[m].npc_num]=nd; + map[m].npc_num++; + idb_put(id_db,nd->bl.id,nd); + return true; +} + +/*========================================= + * Dynamic Mobs [Wizputer] + *-----------------------------------------*/ +// Stores the spawn data entry in the mob list. +// Returns the index of successful, or -1 if the list was full. +int map_addmobtolist(unsigned short m, struct spawn_data *spawn) +{ + size_t i; + ARR_FIND( 0, MAX_MOB_LIST_PER_MAP, i, map[m].moblist[i] == NULL ); + if( i < MAX_MOB_LIST_PER_MAP ) + { + map[m].moblist[i] = spawn; + return i; + } + return -1; +} + +void map_spawnmobs(int16 m) +{ + int i, k=0; + if (map[m].mob_delete_timer != INVALID_TIMER) + { //Mobs have not been removed yet [Skotlex] + delete_timer(map[m].mob_delete_timer, map_removemobs_timer); + map[m].mob_delete_timer = INVALID_TIMER; + return; + } + for(i=0; i<MAX_MOB_LIST_PER_MAP; i++) + if(map[m].moblist[i]!=NULL) + { + k+=map[m].moblist[i]->num; + npc_parse_mob2(map[m].moblist[i]); + } + + if (battle_config.etc_log && k > 0) + { + ShowStatus("Map %s: Spawned '"CL_WHITE"%d"CL_RESET"' mobs.\n",map[m].name, k); + } +} + +int map_removemobs_sub(struct block_list *bl, va_list ap) +{ + struct mob_data *md = (struct mob_data *)bl; + nullpo_ret(md); + + //When not to remove mob: + // doesn't respawn and is not a slave + if( !md->spawn && !md->master_id ) + return 0; + // respawn data is not in cache + if( md->spawn && !md->spawn->state.dynamic ) + return 0; + // hasn't spawned yet + if( md->spawn_timer != INVALID_TIMER ) + return 0; + // is damaged and mob_remove_damaged is off + if( !battle_config.mob_remove_damaged && md->status.hp < md->status.max_hp ) + return 0; + // is a mvp + if( md->db->mexp > 0 ) + return 0; + + unit_free(&md->bl,CLR_OUTSIGHT); + + return 1; +} + +int map_removemobs_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + int count; + const int16 m = id; + + if (m < 0 || m >= MAX_MAP_PER_SERVER) + { //Incorrect map id! + ShowError("map_removemobs_timer error: timer %d points to invalid map %d\n",tid, m); + return 0; + } + if (map[m].mob_delete_timer != tid) + { //Incorrect timer call! + ShowError("map_removemobs_timer mismatch: %d != %d (map %s)\n",map[m].mob_delete_timer, tid, map[m].name); + return 0; + } + map[m].mob_delete_timer = INVALID_TIMER; + if (map[m].users > 0) //Map not empty! + return 1; + + count = map_foreachinmap(map_removemobs_sub, m, BL_MOB); + + if (battle_config.etc_log && count > 0) + ShowStatus("Map %s: Removed '"CL_WHITE"%d"CL_RESET"' mobs.\n",map[m].name, count); + + return 1; +} + +void map_removemobs(int16 m) +{ + if (map[m].mob_delete_timer != INVALID_TIMER) // should never happen + return; //Mobs are already scheduled for removal + + map[m].mob_delete_timer = add_timer(gettick()+battle_config.mob_remove_delay, map_removemobs_timer, m, 0); +} + +/*========================================== + * Hookup, get map_id from map_name + *------------------------------------------*/ +int16 map_mapname2mapid(const char* name) +{ + unsigned short map_index; + map_index = mapindex_name2id(name); + if (!map_index) + return -1; + return map_mapindex2mapid(map_index); +} + +/*========================================== + * Returns the map of the given mapindex. [Skotlex] + *------------------------------------------*/ +int16 map_mapindex2mapid(unsigned short mapindex) +{ + struct map_data *md=NULL; + + if (!mapindex) + return -1; + + md = (struct map_data*)uidb_get(map_db,(unsigned int)mapindex); + if(md==NULL || md->cell==NULL) + return -1; + return md->m; +} + +/*========================================== + * Switching Ip, port ? (like changing map_server) get ip/port from map_name + *------------------------------------------*/ +int map_mapname2ipport(unsigned short name, uint32* ip, uint16* port) +{ + struct map_data_other_server *mdos=NULL; + + mdos = (struct map_data_other_server*)uidb_get(map_db,(unsigned int)name); + if(mdos==NULL || mdos->cell) //If gat isn't null, this is a local map. + return -1; + *ip=mdos->ip; + *port=mdos->port; + return 0; +} + +/*========================================== + * Checks if both dirs point in the same direction. + *------------------------------------------*/ +int map_check_dir(int s_dir,int t_dir) +{ + if(s_dir == t_dir) + return 0; + switch(s_dir) { + case 0: if(t_dir == 7 || t_dir == 1 || t_dir == 0) return 0; break; + case 1: if(t_dir == 0 || t_dir == 2 || t_dir == 1) return 0; break; + case 2: if(t_dir == 1 || t_dir == 3 || t_dir == 2) return 0; break; + case 3: if(t_dir == 2 || t_dir == 4 || t_dir == 3) return 0; break; + case 4: if(t_dir == 3 || t_dir == 5 || t_dir == 4) return 0; break; + case 5: if(t_dir == 4 || t_dir == 6 || t_dir == 5) return 0; break; + case 6: if(t_dir == 5 || t_dir == 7 || t_dir == 6) return 0; break; + case 7: if(t_dir == 6 || t_dir == 0 || t_dir == 7) return 0; break; + } + return 1; +} + +/*========================================== + * Returns the direction of the given cell, relative to 'src' + *------------------------------------------*/ +uint8 map_calc_dir(struct block_list* src, int16 x, int16 y) +{ + uint8 dir = 0; + int dx, dy; + + nullpo_ret(src); + + dx = x-src->x; + dy = y-src->y; + if( dx == 0 && dy == 0 ) + { // both are standing on the same spot + //dir = 6; // aegis-style, makes knockback default to the left + dir = unit_getdir(src); // athena-style, makes knockback default to behind 'src' + } + else if( dx >= 0 && dy >=0 ) + { // upper-right + if( dx*2 <= dy ) dir = 0; // up + else if( dx > dy*2 ) dir = 6; // right + else dir = 7; // up-right + } + else if( dx >= 0 && dy <= 0 ) + { // lower-right + if( dx*2 <= -dy ) dir = 4; // down + else if( dx > -dy*2 ) dir = 6; // right + else dir = 5; // down-right + } + else if( dx <= 0 && dy <= 0 ) + { // lower-left + if( dx*2 >= dy ) dir = 4; // down + else if( dx < dy*2 ) dir = 2; // left + else dir = 3; // down-left + } + else + { // upper-left + if( -dx*2 <= dy ) dir = 0; // up + else if( -dx > dy*2 ) dir = 2; // left + else dir = 1; // up-left + + } + return dir; +} + +/*========================================== + * Randomizes target cell x,y to a random walkable cell that + * has the same distance from object as given coordinates do. [Skotlex] + *------------------------------------------*/ +int map_random_dir(struct block_list *bl, int16 *x, int16 *y) +{ + short xi = *x-bl->x; + short yi = *y-bl->y; + short i=0, j; + int dist2 = xi*xi + yi*yi; + short dist = (short)sqrt((float)dist2); + short segment; + + if (dist < 1) dist =1; + + do { + j = 1 + 2*(rnd()%4); //Pick a random diagonal direction + segment = 1+(rnd()%dist); //Pick a random interval from the whole vector in that direction + xi = bl->x + segment*dirx[j]; + segment = (short)sqrt((float)(dist2 - segment*segment)); //The complement of the previously picked segment + yi = bl->y + segment*diry[j]; + } while ( + (map_getcell(bl->m,xi,yi,CELL_CHKNOPASS) || !path_search(NULL,bl->m,bl->x,bl->y,xi,yi,1,CELL_CHKNOREACH)) + && (++i)<100 ); + + if (i < 100) { + *x = xi; + *y = yi; + return 1; + } + return 0; +} + +// gat system +inline static struct mapcell map_gat2cell(int gat) { + struct mapcell cell; + + memset(&cell,0,sizeof(struct mapcell)); + + switch( gat ) { + case 0: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // walkable ground + case 1: cell.walkable = 0; cell.shootable = 0; cell.water = 0; break; // non-walkable ground + case 2: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ??? + case 3: cell.walkable = 1; cell.shootable = 1; cell.water = 1; break; // walkable water + case 4: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ??? + case 5: cell.walkable = 0; cell.shootable = 1; cell.water = 0; break; // gap (snipable) + case 6: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ??? + default: + ShowWarning("map_gat2cell: unrecognized gat type '%d'\n", gat); + break; + } + + return cell; +} + +static int map_cell2gat(struct mapcell cell) +{ + if( cell.walkable == 1 && cell.shootable == 1 && cell.water == 0 ) return 0; + if( cell.walkable == 0 && cell.shootable == 0 && cell.water == 0 ) return 1; + if( cell.walkable == 1 && cell.shootable == 1 && cell.water == 1 ) return 3; + if( cell.walkable == 0 && cell.shootable == 1 && cell.water == 0 ) return 5; + + ShowWarning("map_cell2gat: cell has no matching gat type\n"); + return 1; // default to 'wall' +} + +/*========================================== + * Confirm if celltype in (m,x,y) match the one given in cellchk + *------------------------------------------*/ +int map_getcell(int16 m,int16 x,int16 y,cell_chk cellchk) +{ + return (m < 0 || m >= MAX_MAP_PER_SERVER) ? 0 : map_getcellp(&map[m],x,y,cellchk); +} + +int map_getcellp(struct map_data* m,int16 x,int16 y,cell_chk cellchk) +{ + struct mapcell cell; + + nullpo_ret(m); + + //NOTE: this intentionally overrides the last row and column + if(x<0 || x>=m->xs-1 || y<0 || y>=m->ys-1) + return( cellchk == CELL_CHKNOPASS ); + + cell = m->cell[x + y*m->xs]; + + switch(cellchk) + { + // gat type retrieval + case CELL_GETTYPE: + return map_cell2gat(cell); + + // base gat type checks + case CELL_CHKWALL: + return (!cell.walkable && !cell.shootable); + + case CELL_CHKWATER: + return (cell.water); + + case CELL_CHKCLIFF: + return (!cell.walkable && cell.shootable); + + + // base cell type checks + case CELL_CHKNPC: + return (cell.npc); + case CELL_CHKBASILICA: + return (cell.basilica); + case CELL_CHKLANDPROTECTOR: + return (cell.landprotector); + case CELL_CHKNOVENDING: + return (cell.novending); + case CELL_CHKNOCHAT: + return (cell.nochat); + case CELL_CHKMAELSTROM: + return (cell.maelstrom); + case CELL_CHKICEWALL: + return (cell.icewall); + + // special checks + case CELL_CHKPASS: +#ifdef CELL_NOSTACK + if (cell.cell_bl >= battle_config.cell_stack_limit) return 0; +#endif + case CELL_CHKREACH: + return (cell.walkable); + + case CELL_CHKNOPASS: +#ifdef CELL_NOSTACK + if (cell.cell_bl >= battle_config.cell_stack_limit) return 1; +#endif + case CELL_CHKNOREACH: + return (!cell.walkable); + + case CELL_CHKSTACK: +#ifdef CELL_NOSTACK + return (cell.cell_bl >= battle_config.cell_stack_limit); +#else + return 0; +#endif + + default: + return 0; + } +} + +/*========================================== + * Change the type/flags of a map cell + * 'cell' - which flag to modify + * 'flag' - true = on, false = off + *------------------------------------------*/ +void map_setcell(int16 m, int16 x, int16 y, cell_t cell, bool flag) +{ + int j; + + if( m < 0 || m >= map_num || x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys ) + return; + + j = x + y*map[m].xs; + + switch( cell ) { + case CELL_WALKABLE: map[m].cell[j].walkable = flag; break; + case CELL_SHOOTABLE: map[m].cell[j].shootable = flag; break; + case CELL_WATER: map[m].cell[j].water = flag; break; + + case CELL_NPC: map[m].cell[j].npc = flag; break; + case CELL_BASILICA: map[m].cell[j].basilica = flag; break; + case CELL_LANDPROTECTOR: map[m].cell[j].landprotector = flag; break; + case CELL_NOVENDING: map[m].cell[j].novending = flag; break; + case CELL_NOCHAT: map[m].cell[j].nochat = flag; break; + case CELL_MAELSTROM: map[m].cell[j].maelstrom = flag; break; + case CELL_ICEWALL: map[m].cell[j].icewall = flag; break; + default: + ShowWarning("map_setcell: invalid cell type '%d'\n", (int)cell); + break; + } +} + +void map_setgatcell(int16 m, int16 x, int16 y, int gat) +{ + int j; + struct mapcell cell; + + if( m < 0 || m >= map_num || x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys ) + return; + + j = x + y*map[m].xs; + + cell = map_gat2cell(gat); + map[m].cell[j].walkable = cell.walkable; + map[m].cell[j].shootable = cell.shootable; + map[m].cell[j].water = cell.water; +} + +/*========================================== + * Invisible Walls + *------------------------------------------*/ +static DBMap* iwall_db; + +void map_iwall_nextxy(int16 x, int16 y, int8 dir, int pos, int16 *x1, int16 *y1) +{ + if( dir == 0 || dir == 4 ) + *x1 = x; // Keep X + else if( dir > 0 && dir < 4 ) + *x1 = x - pos; // Going left + else + *x1 = x + pos; // Going right + + if( dir == 2 || dir == 6 ) + *y1 = y; + else if( dir > 2 && dir < 6 ) + *y1 = y - pos; + else + *y1 = y + pos; +} + +bool map_iwall_set(int16 m, int16 x, int16 y, int size, int8 dir, bool shootable, const char* wall_name) +{ + struct iwall_data *iwall; + int i; + int16 x1 = 0, y1 = 0; + + if( size < 1 || !wall_name ) + return false; + + if( (iwall = (struct iwall_data *)strdb_get(iwall_db, wall_name)) != NULL ) + return false; // Already Exists + + if( map_getcell(m, x, y, CELL_CHKNOREACH) ) + return false; // Starting cell problem + + CREATE(iwall, struct iwall_data, 1); + iwall->m = m; + iwall->x = x; + iwall->y = y; + iwall->size = size; + iwall->dir = dir; + iwall->shootable = shootable; + safestrncpy(iwall->wall_name, wall_name, sizeof(iwall->wall_name)); + + for( i = 0; i < size; i++ ) + { + map_iwall_nextxy(x, y, dir, i, &x1, &y1); + + if( map_getcell(m, x1, y1, CELL_CHKNOREACH) ) + break; // Collision + + map_setcell(m, x1, y1, CELL_WALKABLE, false); + map_setcell(m, x1, y1, CELL_SHOOTABLE, shootable); + + clif_changemapcell(0, m, x1, y1, map_getcell(m, x1, y1, CELL_GETTYPE), ALL_SAMEMAP); + } + + iwall->size = i; + + strdb_put(iwall_db, iwall->wall_name, iwall); + map[m].iwall_num++; + + return true; +} + +void map_iwall_get(struct map_session_data *sd) +{ + struct iwall_data *iwall; + DBIterator* iter; + int16 x1, y1; + int i; + + if( map[sd->bl.m].iwall_num < 1 ) + return; + + iter = db_iterator(iwall_db); + for( iwall = dbi_first(iter); dbi_exists(iter); iwall = dbi_next(iter) ) + { + if( iwall->m != sd->bl.m ) + continue; + + for( i = 0; i < iwall->size; i++ ) + { + map_iwall_nextxy(iwall->x, iwall->y, iwall->dir, i, &x1, &y1); + clif_changemapcell(sd->fd, iwall->m, x1, y1, map_getcell(iwall->m, x1, y1, CELL_GETTYPE), SELF); + } + } + dbi_destroy(iter); +} + +void map_iwall_remove(const char *wall_name) +{ + struct iwall_data *iwall; + int16 i, x1, y1; + + if( (iwall = (struct iwall_data *)strdb_get(iwall_db, wall_name)) == NULL ) + return; // Nothing to do + + for( i = 0; i < iwall->size; i++ ) + { + map_iwall_nextxy(iwall->x, iwall->y, iwall->dir, i, &x1, &y1); + + map_setcell(iwall->m, x1, y1, CELL_SHOOTABLE, true); + map_setcell(iwall->m, x1, y1, CELL_WALKABLE, true); + + clif_changemapcell(0, iwall->m, x1, y1, map_getcell(iwall->m, x1, y1, CELL_GETTYPE), ALL_SAMEMAP); + } + + map[iwall->m].iwall_num--; + strdb_remove(iwall_db, iwall->wall_name); +} + +/** + * @see DBCreateData + */ +static DBData create_map_data_other_server(DBKey key, va_list args) +{ + struct map_data_other_server *mdos; + unsigned short mapindex = (unsigned short)key.ui; + mdos=(struct map_data_other_server *)aCalloc(1,sizeof(struct map_data_other_server)); + mdos->index = mapindex; + memcpy(mdos->name, mapindex_id2name(mapindex), MAP_NAME_LENGTH); + return db_ptr2data(mdos); +} + +/*========================================== + * Add mapindex to db of another map server + *------------------------------------------*/ +int map_setipport(unsigned short mapindex, uint32 ip, uint16 port) +{ + struct map_data_other_server *mdos=NULL; + + mdos= uidb_ensure(map_db,(unsigned int)mapindex, create_map_data_other_server); + + if(mdos->cell) //Local map,Do nothing. Give priority to our own local maps over ones from another server. [Skotlex] + return 0; + if(ip == clif_getip() && port == clif_getport()) { + //That's odd, we received info that we are the ones with this map, but... we don't have it. + ShowFatalError("map_setipport : received info that this map-server SHOULD have map '%s', but it is not loaded.\n",mapindex_id2name(mapindex)); + exit(EXIT_FAILURE); + } + mdos->ip = ip; + mdos->port = port; + return 1; +} + +/** + * Delete all the other maps server management + * @see DBApply + */ +int map_eraseallipport_sub(DBKey key, DBData *data, va_list va) +{ + struct map_data_other_server *mdos = db_data2ptr(data); + if(mdos->cell == NULL) { + db_remove(map_db,key); + aFree(mdos); + } + return 0; +} + +int map_eraseallipport(void) +{ + map_db->foreach(map_db,map_eraseallipport_sub); + return 1; +} + +/*========================================== + * Delete mapindex from db of another map server + *------------------------------------------*/ +int map_eraseipport(unsigned short mapindex, uint32 ip, uint16 port) +{ + struct map_data_other_server *mdos; + + mdos = (struct map_data_other_server*)uidb_get(map_db,(unsigned int)mapindex); + if(!mdos || mdos->cell) //Map either does not exists or is a local map. + return 0; + + if(mdos->ip==ip && mdos->port == port) { + uidb_remove(map_db,(unsigned int)mapindex); + aFree(mdos); + return 1; + } + return 0; +} + +/*========================================== + * [Shinryo]: Init the mapcache + *------------------------------------------*/ +static char *map_init_mapcache(FILE *fp) +{ + size_t size = 0; + char *buffer; + + // No file open? Return.. + nullpo_ret(fp); + + // Get file size + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // Allocate enough space + CREATE(buffer, char, size); + + // No memory? Return.. + nullpo_ret(buffer); + + // Read file into buffer.. + if(fread(buffer, sizeof(char), size, fp) != size) { + ShowError("map_init_mapcache: Could not read entire mapcache file\n"); + return NULL; + } + + return buffer; +} + +/*========================================== + * Map cache reading + * [Shinryo]: Optimized some behaviour to speed this up + *==========================================*/ +int map_readfromcache(struct map_data *m, char *buffer, char *decode_buffer) +{ + int i; + struct map_cache_main_header *header = (struct map_cache_main_header *)buffer; + struct map_cache_map_info *info = NULL; + char *p = buffer + sizeof(struct map_cache_main_header); + + for(i = 0; i < header->map_count; i++) { + info = (struct map_cache_map_info *)p; + + if( strcmp(m->name, info->name) == 0 ) + break; // Map found + + // Jump to next entry.. + p += sizeof(struct map_cache_map_info) + info->len; + } + + if( info && i < header->map_count ) { + unsigned long size, xy; + + if( info->xs <= 0 || info->ys <= 0 ) + return 0;// Invalid + + m->xs = info->xs; + m->ys = info->ys; + size = (unsigned long)info->xs*(unsigned long)info->ys; + + if(size > MAX_MAP_SIZE) { + ShowWarning("map_readfromcache: %s exceeded MAX_MAP_SIZE of %d\n", info->name, MAX_MAP_SIZE); + return 0; // Say not found to remove it from list.. [Shinryo] + } + + // TO-DO: Maybe handle the scenario, if the decoded buffer isn't the same size as expected? [Shinryo] + decode_zip(decode_buffer, &size, p+sizeof(struct map_cache_map_info), info->len); + + CREATE(m->cell, struct mapcell, size); + + + for( xy = 0; xy < size; ++xy ) + m->cell[xy] = map_gat2cell(decode_buffer[xy]); + + return 1; + } + + return 0; // Not found +} + +int map_addmap(char* mapname) +{ + if( strcmpi(mapname,"clear")==0 ) + { + map_num = 0; + instance_start = 0; + return 0; + } + + if( map_num >= MAX_MAP_PER_SERVER - 1 ) + { + ShowError("Could not add map '"CL_WHITE"%s"CL_RESET"', the limit of maps has been reached.\n",mapname); + return 1; + } + + mapindex_getmapname(mapname, map[map_num].name); + map_num++; + return 0; +} + +static void map_delmapid(int id) +{ + ShowNotice("Removing map [ %s ] from maplist"CL_CLL"\n",map[id].name); + memmove(map+id, map+id+1, sizeof(map[0])*(map_num-id-1)); + map_num--; +} + +int map_delmap(char* mapname) +{ + int i; + char map_name[MAP_NAME_LENGTH]; + + if (strcmpi(mapname, "all") == 0) { + map_num = 0; + return 0; + } + + mapindex_getmapname(mapname, map_name); + for(i = 0; i < map_num; i++) { + if (strcmp(map[i].name, map_name) == 0) { + map_delmapid(i); + return 1; + } + } + return 0; +} + +/// Initializes map flags and adjusts them depending on configuration. +void map_flags_init(void) +{ + int i; + + for( i = 0; i < map_num; i++ ) + { + // mapflags + memset(&map[i].flag, 0, sizeof(map[i].flag)); + + // additional mapflag data + map[i].zone = 0; // restricted mapflag zone + map[i].nocommand = 0; // nocommand mapflag level + map[i].bexp = 100; // per map base exp multiplicator + map[i].jexp = 100; // per map job exp multiplicator + memset(map[i].drop_list, 0, sizeof(map[i].drop_list)); // pvp nightmare drop list + + // adjustments + if( battle_config.pk_mode ) + map[i].flag.pvp = 1; // make all maps pvp for pk_mode [Valaris] + } +} + +#define NO_WATER 1000000 + +/* + * Reads from the .rsw for each map + * Returns water height (or NO_WATER if file doesn't exist) or other error is encountered. + * Assumed path for file is data/mapname.rsw + * Credits to LittleWolf + */ +int map_waterheight(char* mapname) +{ + char fn[256]; + char *rsw, *found; + + //Look up for the rsw + sprintf(fn, "data\\%s.rsw", mapname); + + found = grfio_find_file(fn); + if (found) strcpy(fn, found); // replace with real name + + // read & convert fn + rsw = (char *) grfio_read (fn); + if (rsw) + { //Load water height from file + int wh = (int) *(float*)(rsw+166); + aFree(rsw); + return wh; + } + ShowWarning("Failed to find water level for (%s)\n", mapname, fn); + return NO_WATER; +} + +/*================================== + * .GAT format + *----------------------------------*/ +int map_readgat (struct map_data* m) +{ + char filename[256]; + uint8* gat; + int water_height; + size_t xy, off, num_cells; + + sprintf(filename, "data\\%s.gat", m->name); + + gat = (uint8 *) grfio_read(filename); + if (gat == NULL) + return 0; + + m->xs = *(int32*)(gat+6); + m->ys = *(int32*)(gat+10); + num_cells = m->xs * m->ys; + CREATE(m->cell, struct mapcell, num_cells); + + water_height = map_waterheight(m->name); + + // Set cell properties + off = 14; + for( xy = 0; xy < num_cells; ++xy ) + { + // read cell data + float height = *(float*)( gat + off ); + uint32 type = *(uint32*)( gat + off + 16 ); + off += 20; + + if( type == 0 && water_height != NO_WATER && height > water_height ) + type = 3; // Cell is 0 (walkable) but under water level, set to 3 (walkable water) + + m->cell[xy] = map_gat2cell(type); + } + + aFree(gat); + + return 1; +} + +/*====================================== + * Add/Remove map to the map_db + *--------------------------------------*/ +void map_addmap2db(struct map_data *m) +{ + uidb_put(map_db, (unsigned int)m->index, m); +} + +void map_removemapdb(struct map_data *m) +{ + uidb_remove(map_db, (unsigned int)m->index); +} + +/*====================================== + * Initiate maps loading stage + *--------------------------------------*/ +int map_readallmaps (void) +{ + int i; + FILE* fp=NULL; + int maps_removed = 0; + char *map_cache_buffer = NULL; // Has the uncompressed gat data of all maps, so just one allocation has to be made + char map_cache_decode_buffer[MAX_MAP_SIZE]; + + if( enable_grf ) + ShowStatus("Loading maps (using GRF files)...\n"); + else { + char mapcachefilepath[254]; + sprintf(mapcachefilepath,"%s/%s%s",db_path,DBPATH,"map_cache.dat"); + ShowStatus("Loading maps (using %s as map cache)...\n", mapcachefilepath); + if( (fp = fopen(mapcachefilepath, "rb")) == NULL ) { + ShowFatalError("Unable to open map cache file "CL_WHITE"%s"CL_RESET"\n", mapcachefilepath); + exit(EXIT_FAILURE); //No use launching server if maps can't be read. + } + + // Init mapcache data.. [Shinryo] + map_cache_buffer = map_init_mapcache(fp); + if(!map_cache_buffer) { + ShowFatalError("Failed to initialize mapcache data (%s)..\n", mapcachefilepath); + exit(EXIT_FAILURE); + } + } + + for(i = 0; i < map_num; i++) { + size_t size; + + // show progress + if(enable_grf) + ShowStatus("Loading maps [%i/%i]: %s"CL_CLL"\r", i, map_num, map[i].name); + + // try to load the map + if( ! + (enable_grf? + map_readgat(&map[i]) + :map_readfromcache(&map[i], map_cache_buffer, map_cache_decode_buffer)) + ) { + map_delmapid(i); + maps_removed++; + i--; + continue; + } + + map[i].index = mapindex_name2id(map[i].name); + + if (uidb_get(map_db,(unsigned int)map[i].index) != NULL) + { + ShowWarning("Map %s already loaded!"CL_CLL"\n", map[i].name); + if (map[i].cell) { + aFree(map[i].cell); + map[i].cell = NULL; + } + map_delmapid(i); + maps_removed++; + i--; + continue; + } + + map_addmap2db(&map[i]); + + map[i].m = i; + memset(map[i].moblist, 0, sizeof(map[i].moblist)); //Initialize moblist [Skotlex] + map[i].mob_delete_timer = INVALID_TIMER; //Initialize timer [Skotlex] + + map[i].bxs = (map[i].xs + BLOCK_SIZE - 1) / BLOCK_SIZE; + map[i].bys = (map[i].ys + BLOCK_SIZE - 1) / BLOCK_SIZE; + + size = map[i].bxs * map[i].bys * sizeof(struct block_list*); + map[i].block = (struct block_list**)aCalloc(size, 1); + map[i].block_mob = (struct block_list**)aCalloc(size, 1); + } + + // intialization and configuration-dependent adjustments of mapflags + map_flags_init(); + + if( !enable_grf ) { + fclose(fp); + + // The cache isn't needed anymore, so free it.. [Shinryo] + aFree(map_cache_buffer); + } + + // finished map loading + ShowInfo("Successfully loaded '"CL_WHITE"%d"CL_RESET"' maps."CL_CLL"\n",map_num); + instance_start = map_num; // Next Map Index will be instances + + if (maps_removed) + ShowNotice("Maps removed: '"CL_WHITE"%d"CL_RESET"'\n",maps_removed); + + return 0; +} + +//////////////////////////////////////////////////////////////////////// +static int map_ip_set = 0; +static int char_ip_set = 0; + +/*========================================== + * Console Command Parser [Wizputer] + *------------------------------------------*/ +int parse_console(const char* buf) +{ + char type[64]; + char command[64]; + char map[64]; + int16 x = 0; + int16 y = 0; + int16 m; + int n; + struct map_session_data sd; + + memset(&sd, 0, sizeof(struct map_session_data)); + strcpy(sd.status.name, "console"); + + if( ( n = sscanf(buf, "%63[^:]:%63[^:]:%63s %hd %hd[^\n]", type, command, map, &x, &y) ) < 5 ) + { + if( ( n = sscanf(buf, "%63[^:]:%63[^\n]", type, command) ) < 2 ) + { + n = sscanf(buf, "%63[^\n]", type); + } + } + + if( n == 5 ) + { + m = map_mapname2mapid(map); + if( m < 0 ) + { + ShowWarning("Console: Unknown map.\n"); + return 0; + } + sd.bl.m = m; + map_search_freecell(&sd.bl, m, &sd.bl.x, &sd.bl.y, -1, -1, 0); + if( x > 0 ) + sd.bl.x = x; + if( y > 0 ) + sd.bl.y = y; + } + else + { + map[0] = '\0'; + if( n < 2 ) + command[0] = '\0'; + if( n < 1 ) + type[0] = '\0'; + } + + ShowNotice("Type of command: '%s' || Command: '%s' || Map: '%s' Coords: %d %d\n", type, command, map, x, y); + + if( n == 5 && strcmpi("admin",type) == 0 ) + { + if( !is_atcommand(sd.fd, &sd, command, 0) ) + ShowInfo("Console: not atcommand\n"); + } + else if( n == 2 && strcmpi("server", type) == 0 ) + { + if( strcmpi("shutdown", command) == 0 || strcmpi("exit", command) == 0 || strcmpi("quit", command) == 0 ) + { + runflag = 0; + } + } + else if( strcmpi("help", type) == 0 ) + { + ShowInfo("To use GM commands:\n"); + ShowInfo(" admin:<gm command>:<map of \"gm\"> <x> <y>\n"); + ShowInfo("You can use any GM command that doesn't require the GM.\n"); + ShowInfo("No using @item or @warp however you can use @charwarp\n"); + ShowInfo("The <map of \"gm\"> <x> <y> is for commands that need coords of the GM\n"); + ShowInfo("IE: @spawn\n"); + ShowInfo("To shutdown the server:\n"); + ShowInfo(" server:shutdown\n"); + } + + return 0; +} + +/*========================================== + * Read map server configuration files (conf/map_athena.conf...) + *------------------------------------------*/ +int map_config_read(char *cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName,"r"); + if( fp == NULL ) + { + ShowError("Map configuration file not found at: %s\n", cfgName); + return 1; + } + + while( fgets(line, sizeof(line), fp) ) + { + char* ptr; + + if( line[0] == '/' && line[1] == '/' ) + continue; + if( (ptr = strstr(line, "//")) != NULL ) + *ptr = '\n'; //Strip comments + if( sscanf(line, "%[^:]: %[^\t\r\n]", w1, w2) < 2 ) + continue; + + //Strip trailing spaces + ptr = w2 + strlen(w2); + while (--ptr >= w2 && *ptr == ' '); + ptr++; + *ptr = '\0'; + + if(strcmpi(w1,"timestamp_format")==0) + strncpy(timestamp_format, w2, 20); + else if(strcmpi(w1,"stdout_with_ansisequence")==0) + stdout_with_ansisequence = config_switch(w2); + else if(strcmpi(w1,"console_silent")==0) { + msg_silent = atoi(w2); + if( msg_silent ) // only bother if its actually enabled + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + } else if (strcmpi(w1, "userid")==0) + chrif_setuserid(w2); + else if (strcmpi(w1, "passwd") == 0) + chrif_setpasswd(w2); + else if (strcmpi(w1, "char_ip") == 0) + char_ip_set = chrif_setip(w2); + else if (strcmpi(w1, "char_port") == 0) + chrif_setport(atoi(w2)); + else if (strcmpi(w1, "map_ip") == 0) + map_ip_set = clif_setip(w2); + else if (strcmpi(w1, "bind_ip") == 0) + clif_setbindip(w2); + else if (strcmpi(w1, "map_port") == 0) { + clif_setport(atoi(w2)); + map_port = (atoi(w2)); + } else if (strcmpi(w1, "map") == 0) + map_addmap(w2); + else if (strcmpi(w1, "delmap") == 0) + map_delmap(w2); + else if (strcmpi(w1, "npc") == 0) + npc_addsrcfile(w2); + else if (strcmpi(w1, "delnpc") == 0) + npc_delsrcfile(w2); + else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2); + if (autosave_interval < 1) //Revert to default saving. + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + else + autosave_interval *= 1000; //Pass from sec to ms + } else if (strcmpi(w1, "minsave_time") == 0) { + minsave_interval= atoi(w2); + if (minsave_interval < 1) + minsave_interval = 1; + } else if (strcmpi(w1, "save_settings") == 0) + save_settings = atoi(w2); + else if (strcmpi(w1, "motd_txt") == 0) + strcpy(motd_txt, w2); + else if (strcmpi(w1, "help_txt") == 0) + strcpy(help_txt, w2); + else if (strcmpi(w1, "help2_txt") == 0) + strcpy(help2_txt, w2); + else if (strcmpi(w1, "charhelp_txt") == 0) + strcpy(charhelp_txt, w2); + else if(strcmpi(w1,"db_path") == 0) + strncpy(db_path,w2,255); + else if (strcmpi(w1, "console") == 0) { + console = config_switch(w2); + if (console) + ShowNotice("Console Commands are enabled.\n"); + } else if (strcmpi(w1, "enable_spy") == 0) + enable_spy = config_switch(w2); + else if (strcmpi(w1, "use_grf") == 0) + enable_grf = config_switch(w2); + else if (strcmpi(w1, "console_msg_log") == 0) + console_msg_log = atoi(w2);//[Ind] + else if (strcmpi(w1, "import") == 0) + map_config_read(w2); + else + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + + fclose(fp); + return 0; +} + +void map_reloadnpc_sub(char *cfgName) +{ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName,"r"); + if( fp == NULL ) + { + ShowError("Map configuration file not found at: %s\n", cfgName); + return; + } + + while( fgets(line, sizeof(line), fp) ) + { + char* ptr; + + if( line[0] == '/' && line[1] == '/' ) + continue; + if( (ptr = strstr(line, "//")) != NULL ) + *ptr = '\n'; //Strip comments + if( sscanf(line, "%[^:]: %[^\t\r\n]", w1, w2) < 2 ) + continue; + + //Strip trailing spaces + ptr = w2 + strlen(w2); + while (--ptr >= w2 && *ptr == ' '); + ptr++; + *ptr = '\0'; + + if (strcmpi(w1, "npc") == 0) + npc_addsrcfile(w2); + else if (strcmpi(w1, "import") == 0) + map_reloadnpc_sub(w2); + else + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + + fclose(fp); +} + +void map_reloadnpc(bool clear) +{ + if (clear) + npc_addsrcfile("clear"); // this will clear the current script list + +#ifdef RENEWAL + map_reloadnpc_sub("npc/re/scripts_main.conf"); +#else + map_reloadnpc_sub("npc/pre-re/scripts_main.conf"); +#endif +} + +int inter_config_read(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, "main_chat_nick")==0) + safestrncpy(main_chat_nick, w2, sizeof(main_chat_nick)); + else + if(strcmpi(w1,"item_db_db")==0) + strcpy(item_db_db,w2); + else + if(strcmpi(w1,"mob_db_db")==0) + strcpy(mob_db_db,w2); + else + if(strcmpi(w1,"item_db2_db")==0) + strcpy(item_db2_db,w2); + else + if(strcmpi(w1,"item_db_re_db")==0) + strcpy(item_db_re_db,w2); + else + if(strcmpi(w1,"mob_db2_db")==0) + strcpy(mob_db2_db,w2); + else + //Map Server SQL DB + if(strcmpi(w1,"map_server_ip")==0) + strcpy(map_server_ip, w2); + else + if(strcmpi(w1,"map_server_port")==0) + map_server_port=atoi(w2); + else + if(strcmpi(w1,"map_server_id")==0) + strcpy(map_server_id, w2); + else + if(strcmpi(w1,"map_server_pw")==0) + strcpy(map_server_pw, w2); + else + if(strcmpi(w1,"map_server_db")==0) + strcpy(map_server_db, w2); + else + if(strcmpi(w1,"default_codepage")==0) + strcpy(default_codepage, w2); + else + if(strcmpi(w1,"use_sql_db")==0) { + db_use_sqldbs = config_switch(w2); + ShowStatus ("Using SQL dbs: %s\n",w2); + } else + if(strcmpi(w1,"log_db_ip")==0) + strcpy(log_db_ip, w2); + else + if(strcmpi(w1,"log_db_id")==0) + strcpy(log_db_id, w2); + else + if(strcmpi(w1,"log_db_pw")==0) + strcpy(log_db_pw, w2); + else + if(strcmpi(w1,"log_db_port")==0) + log_db_port = atoi(w2); + else + if(strcmpi(w1,"log_db_db")==0) + strcpy(log_db_db, w2); + else + if( mapreg_config_read(w1,w2) ) + continue; + //support the import command, just like any other config + else + if(strcmpi(w1,"import")==0) + inter_config_read(w2); + } + fclose(fp); + + return 0; +} + +/*======================================= + * MySQL Init + *---------------------------------------*/ +int map_sql_init(void) +{ + // main db connection + mmysql_handle = Sql_Malloc(); + + ShowInfo("Connecting to the Map DB Server....\n"); + if( SQL_ERROR == Sql_Connect(mmysql_handle, map_server_id, map_server_pw, map_server_ip, map_server_port, map_server_db) ) + exit(EXIT_FAILURE); + ShowStatus("connect success! (Map Server Connection)\n"); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(mmysql_handle, default_codepage) ) + Sql_ShowDebug(mmysql_handle); + + return 0; +} + +int map_sql_close(void) +{ + ShowStatus("Close Map DB Connection....\n"); + Sql_Free(mmysql_handle); + mmysql_handle = NULL; +#ifndef BETA_THREAD_TEST + if (log_config.sql_logs) + { + ShowStatus("Close Log DB Connection....\n"); + Sql_Free(logmysql_handle); + logmysql_handle = NULL; + } +#endif + return 0; +} + +int log_sql_init(void) +{ +#ifndef BETA_THREAD_TEST + // log db connection + logmysql_handle = Sql_Malloc(); + + ShowInfo(""CL_WHITE"[SQL]"CL_RESET": Connecting to the Log Database "CL_WHITE"%s"CL_RESET" At "CL_WHITE"%s"CL_RESET"...\n",log_db_db,log_db_ip); + if ( SQL_ERROR == Sql_Connect(logmysql_handle, log_db_id, log_db_pw, log_db_ip, log_db_port, log_db_db) ) + exit(EXIT_FAILURE); + ShowStatus(""CL_WHITE"[SQL]"CL_RESET": Successfully '"CL_GREEN"connected"CL_RESET"' to Database '"CL_WHITE"%s"CL_RESET"'.\n", log_db_db); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(logmysql_handle, default_codepage) ) + Sql_ShowDebug(logmysql_handle); +#endif + return 0; +} + +/** + * @see DBApply + */ +int map_db_final(DBKey key, DBData *data, va_list ap) +{ + struct map_data_other_server *mdos = db_data2ptr(data); + if(mdos && mdos->cell == NULL) + aFree(mdos); + return 0; +} + +/** + * @see DBApply + */ +int nick_db_final(DBKey key, DBData *data, va_list args) +{ + struct charid2nick* p = db_data2ptr(data); + struct charid_request* req; + + if( p == NULL ) + return 0; + while( p->requests ) + { + req = p->requests; + p->requests = req->next; + aFree(req); + } + aFree(p); + return 0; +} + +int cleanup_sub(struct block_list *bl, va_list ap) +{ + nullpo_ret(bl); + + switch(bl->type) { + case BL_PC: + map_quit((struct map_session_data *) bl); + break; + case BL_NPC: + npc_unload((struct npc_data *)bl,false); + break; + case BL_MOB: + unit_free(bl,CLR_OUTSIGHT); + break; + case BL_PET: + //There is no need for this, the pet is removed together with the player. [Skotlex] + break; + case BL_ITEM: + map_clearflooritem(bl); + break; + case BL_SKILL: + skill_delunit((struct skill_unit *) bl); + break; + } + + return 1; +} + +/** + * @see DBApply + */ +static int cleanup_db_sub(DBKey key, DBData *data, va_list va) +{ + return cleanup_sub(db_data2ptr(data), va); +} + +/*========================================== + * map destructor + *------------------------------------------*/ +void do_final(void) +{ + int i, j; + struct map_session_data* sd; + struct s_mapiterator* iter; + + ShowStatus("Terminating...\n"); + + //Ladies and babies first. + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + map_quit(sd); + mapit_free(iter); + + /* prepares npcs for a faster shutdown process */ + do_clear_npc(); + + // remove all objects on maps + for (i = 0; i < map_num; i++) { + ShowStatus("Cleaning up maps [%d/%d]: %s..."CL_CLL"\r", i+1, map_num, map[i].name); + if (map[i].m >= 0) + map_foreachinmap(cleanup_sub, i, BL_ALL); + } + ShowStatus("Cleaned up %d maps."CL_CLL"\n", map_num); + + id_db->foreach(id_db,cleanup_db_sub); + chrif_char_reset_offline(); + chrif_flush_fifo(); + + do_final_atcommand(); + do_final_battle(); + do_final_chrif(); + do_final_clif(); + do_final_npc(); + do_final_script(); + do_final_instance(); + do_final_itemdb(); + do_final_storage(); + do_final_guild(); + do_final_party(); + do_final_pc(); + do_final_pet(); + do_final_mob(); + do_final_msg(); + do_final_skill(); + do_final_status(); + do_final_unit(); + do_final_battleground(); + do_final_duel(); + do_final_elemental(); + + map_db->destroy(map_db, map_db_final); + + for (i=0; i<map_num; i++) { + if(map[i].cell) aFree(map[i].cell); + if(map[i].block) aFree(map[i].block); + if(map[i].block_mob) aFree(map[i].block_mob); + if(battle_config.dynamic_mobs) { //Dynamic mobs flag by [random] + if(map[i].mob_delete_timer != INVALID_TIMER) + delete_timer(map[i].mob_delete_timer, map_removemobs_timer); + for (j=0; j<MAX_MOB_LIST_PER_MAP; j++) + if (map[i].moblist[j]) aFree(map[i].moblist[j]); + } + } + + mapindex_final(); + if(enable_grf) + grfio_final(); + + id_db->destroy(id_db, NULL); + pc_db->destroy(pc_db, NULL); + mobid_db->destroy(mobid_db, NULL); + bossid_db->destroy(bossid_db, NULL); + nick_db->destroy(nick_db, nick_db_final); + charid_db->destroy(charid_db, NULL); + iwall_db->destroy(iwall_db, NULL); + regen_db->destroy(regen_db, NULL); + + map_sql_close(); + + ShowStatus("Finished.\n"); +} + +static int map_abort_sub(struct map_session_data* sd, va_list ap) +{ + chrif_save(sd,1); + return 1; +} + + +//------------------------------ +// Function called when the server +// has received a crash signal. +//------------------------------ +void do_abort(void) +{ + static int run = 0; + //Save all characters and then flush the inter-connection. + if (run) { + ShowFatalError("Server has crashed while trying to save characters. Character data can't be saved!\n"); + return; + } + run = 1; + if (!chrif_isconnected()) + { + if (pc_db->size(pc_db)) + ShowFatalError("Server has crashed without a connection to the char-server, %u characters can't be saved!\n", pc_db->size(pc_db)); + return; + } + ShowError("Server received crash signal! Attempting to save all online characters!\n"); + map_foreachpc(map_abort_sub); + chrif_flush_fifo(); +} + +/*====================================================== + * Map-Server Version Screen [MC Cameri] + *------------------------------------------------------*/ +static void map_helpscreen(bool do_exit) +{ + ShowInfo("Usage: %s [options]\n", SERVER_NAME); + ShowInfo("\n"); + ShowInfo("Options:\n"); + ShowInfo(" -?, -h [--help]\t\tDisplays this help screen.\n"); + ShowInfo(" -v [--version]\t\tDisplays the server's version.\n"); + ShowInfo(" --run-once\t\t\tCloses server after loading (testing).\n"); + ShowInfo(" --map-config <file>\t\tAlternative map-server configuration.\n"); + ShowInfo(" --battle-config <file>\tAlternative battle configuration.\n"); + ShowInfo(" --atcommand-config <file>\tAlternative atcommand configuration.\n"); + ShowInfo(" --script-config <file>\tAlternative script configuration.\n"); + ShowInfo(" --msg-config <file>\t\tAlternative message configuration.\n"); + ShowInfo(" --grf-path <file>\t\tAlternative GRF path configuration.\n"); + ShowInfo(" --inter-config <file>\t\tAlternative inter-server configuration.\n"); + ShowInfo(" --log-config <file>\t\tAlternative logging configuration.\n"); + if( do_exit ) + exit(EXIT_SUCCESS); +} + +/*====================================================== + * Map-Server Version Screen [MC Cameri] + *------------------------------------------------------*/ +static void map_versionscreen(bool do_exit) +{ + ShowInfo(CL_WHITE"rAthena SVN version: %s" CL_RESET"\n", get_svn_revision()); + ShowInfo(CL_GREEN"Website/Forum:"CL_RESET"\thttp://rathena.org/\n"); + ShowInfo(CL_GREEN"IRC Channel:"CL_RESET"\tirc://irc.rathena.net/#rathena\n"); + ShowInfo("Open "CL_WHITE"readme.txt"CL_RESET" for more information.\n"); + if( do_exit ) + exit(EXIT_SUCCESS); +} + +/*====================================================== + * Map-Server Init and Command-line Arguments [Valaris] + *------------------------------------------------------*/ +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_MAP; +} + + +/// Called when a terminate signal is received. +void do_shutdown(void) +{ + if( runflag != MAPSERVER_ST_SHUTDOWN ) + { + runflag = MAPSERVER_ST_SHUTDOWN; + ShowStatus("Shutting down...\n"); + { + struct map_session_data* sd; + struct s_mapiterator* iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + clif_GM_kick(NULL, sd); + mapit_free(iter); + flush_fifos(); + } + chrif_check_shutdown(); + } +} + +static bool map_arg_next_value(const char* option, int i, int argc) +{ + if( i >= argc-1 ) + { + ShowWarning("Missing value for option '%s'.\n", option); + return false; + } + + return true; +} + +int do_init(int argc, char *argv[]) +{ + int i; + +#ifdef GCOLLECT + GC_enable_incremental(); +#endif + + INTER_CONF_NAME="conf/inter_athena.conf"; + LOG_CONF_NAME="conf/log_athena.conf"; + MAP_CONF_NAME = "conf/map_athena.conf"; + BATTLE_CONF_FILENAME = "conf/battle_athena.conf"; + ATCOMMAND_CONF_FILENAME = "conf/atcommand_athena.conf"; + SCRIPT_CONF_NAME = "conf/script_athena.conf"; + MSG_CONF_NAME = "conf/msg_athena.conf"; + GRF_PATH_FILENAME = "conf/grf-files.txt"; + + rnd_init(); + + for( i = 1; i < argc ; i++ ) + { + const char* arg = argv[i]; + + if( arg[0] != '-' && ( arg[0] != '/' || arg[1] == '-' ) ) + {// -, -- and / + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } + else if( (++arg)[0] == '-' ) + {// long option + arg++; + + if( strcmp(arg, "help") == 0 ) + { + map_helpscreen(true); + } + else if( strcmp(arg, "version") == 0 ) + { + map_versionscreen(true); + } + else if( strcmp(arg, "map-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + MAP_CONF_NAME = argv[++i]; + } + else if( strcmp(arg, "battle-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + BATTLE_CONF_FILENAME = argv[++i]; + } + else if( strcmp(arg, "atcommand-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + ATCOMMAND_CONF_FILENAME = argv[++i]; + } + else if( strcmp(arg, "script-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + SCRIPT_CONF_NAME = argv[++i]; + } + else if( strcmp(arg, "msg-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + MSG_CONF_NAME = argv[++i]; + } + else if( strcmp(arg, "grf-path-file") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + GRF_PATH_FILENAME = argv[++i]; + } + else if( strcmp(arg, "inter-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + INTER_CONF_NAME = argv[++i]; + } + else if( strcmp(arg, "log-config") == 0 ) + { + if( map_arg_next_value(arg, i, argc) ) + LOG_CONF_NAME = argv[++i]; + } + else if( strcmp(arg, "run-once") == 0 ) // close the map-server as soon as its done.. for testing [Celest] + { + runflag = CORE_ST_STOP; + } + else + { + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } + } + else switch( arg[0] ) + {// short option + case '?': + case 'h': + map_helpscreen(true); + break; + case 'v': + map_versionscreen(true); + break; + default: + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } + } + + map_config_read(MAP_CONF_NAME); + /* only temporary until sirius's datapack patch is complete */ + + // loads npcs + map_reloadnpc(false); + + chrif_checkdefaultlogin(); + + if (!map_ip_set || !char_ip_set) { + char ip_str[16]; + ip2str(addr_[0], ip_str); + + ShowWarning("Not all IP addresses in map_athena.conf configured, autodetecting...\n"); + + if (naddr_ == 0) + ShowError("Unable to determine your IP address...\n"); + else if (naddr_ > 1) + ShowNotice("Multiple interfaces detected...\n"); + + ShowInfo("Defaulting to %s as our IP address\n", ip_str); + + if (!map_ip_set) + clif_setip(ip_str); + if (!char_ip_set) + chrif_setip(ip_str); + } + + battle_config_read(BATTLE_CONF_FILENAME); + msg_config_read(MSG_CONF_NAME); + script_config_read(SCRIPT_CONF_NAME); + inter_config_read(INTER_CONF_NAME); + log_config_read(LOG_CONF_NAME); + + id_db = idb_alloc(DB_OPT_BASE); + pc_db = idb_alloc(DB_OPT_BASE); //Added for reliable map_id2sd() use. [Skotlex] + mobid_db = idb_alloc(DB_OPT_BASE); //Added to lower the load of the lazy mob ai. [Skotlex] + bossid_db = idb_alloc(DB_OPT_BASE); // Used for Convex Mirror quick MVP search + map_db = uidb_alloc(DB_OPT_BASE); + nick_db = idb_alloc(DB_OPT_BASE); + charid_db = idb_alloc(DB_OPT_BASE); + regen_db = idb_alloc(DB_OPT_BASE); // efficient status_natural_heal processing + + iwall_db = strdb_alloc(DB_OPT_RELEASE_DATA,2*NAME_LENGTH+2+1); // [Zephyrus] Invisible Walls + + map_sql_init(); + if (log_config.sql_logs) + log_sql_init(); + + mapindex_init(); + if(enable_grf) + grfio_init(GRF_PATH_FILENAME); + + map_readallmaps(); + + add_timer_func_list(map_freeblock_timer, "map_freeblock_timer"); + add_timer_func_list(map_clearflooritem_timer, "map_clearflooritem_timer"); + add_timer_func_list(map_removemobs_timer, "map_removemobs_timer"); + add_timer_interval(gettick()+1000, map_freeblock_timer, 0, 0, 60*1000); + + do_init_atcommand(); + do_init_battle(); + do_init_instance(); + do_init_chrif(); + do_init_clif(); + do_init_script(); + do_init_itemdb(); + do_init_skill(); + do_init_mob(); + do_init_pc(); + do_init_status(); + do_init_party(); + do_init_guild(); + do_init_storage(); + do_init_pet(); + do_init_merc(); + do_init_mercenary(); + do_init_elemental(); + do_init_quest(); + do_init_npc(); + do_init_unit(); + do_init_battleground(); + do_init_duel(); + + npc_event_do_oninit(); // Init npcs (OnInit) + + if( console ) + { + //##TODO invoke a CONSOLE_START plugin event + } + + if (battle_config.pk_mode) + ShowNotice("Server is running on '"CL_WHITE"PK Mode"CL_RESET"'.\n"); + + ShowStatus("Server is '"CL_GREEN"ready"CL_RESET"' and listening on port '"CL_WHITE"%d"CL_RESET"'.\n\n", map_port); + + if( runflag != CORE_ST_STOP ) + { + shutdown_callback = do_shutdown; + runflag = MAPSERVER_ST_RUNNING; + } +#if defined(BUILDBOT) + if( buildbotflag ) + exit(EXIT_FAILURE); +#endif + + return 0; +} diff --git a/src/map/map.h b/src/map/map.h new file mode 100644 index 000000000..86d936972 --- /dev/null +++ b/src/map/map.h @@ -0,0 +1,803 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MAP_H_ +#define _MAP_H_ + +#include "../common/cbasetypes.h" +#include "../common/core.h" // CORE_ST_LAST +#include "../common/mmo.h" +#include "../common/mapindex.h" +#include "../common/db.h" + +/** + * [rAthena.org] + **/ +#include "../config/core.h" + +#include <stdarg.h> + +struct npc_data; +struct item_data; + +enum E_MAPSERVER_ST +{ + MAPSERVER_ST_RUNNING = CORE_ST_LAST, + MAPSERVER_ST_SHUTDOWN, + MAPSERVER_ST_LAST +}; + + +#define MAX_NPC_PER_MAP 512 +#define AREA_SIZE battle_config.area_size +#define DAMAGELOG_SIZE 30 +#define LOOTITEM_SIZE 10 +#define MAX_MOBSKILL 50 //Max 128, see mob skill_idx type if need this higher +#define MAX_MOB_LIST_PER_MAP 128 +#define MAX_EVENTQUEUE 2 +#define MAX_EVENTTIMER 32 +#define NATURAL_HEAL_INTERVAL 500 +#define MIN_FLOORITEM 2 +#define MAX_FLOORITEM START_ACCOUNT_NUM +#define MAX_LEVEL 150 +#define MAX_DROP_PER_MAP 48 +#define MAX_IGNORE_LIST 20 // official is 14 +#define MAX_VENDING 12 +#define MAX_MAP_SIZE 512*512 // Wasn't there something like this already? Can't find it.. [Shinryo] + +// Added definitions for WoESE objects. [L0ne_W0lf] +enum MOBID { + MOBID_EMPERIUM = 1288, + MOBID_TREAS01 = 1324, + MOBID_TREAS40 = 1363, + MOBID_BARRICADE1 = 1905, + MOBID_BARRICADE2, + MOBID_GUARIDAN_STONE1, + MOBID_GUARIDAN_STONE2, + MOBID_FOOD_STOR, + MOBID_BLUE_CRYST = 1914, + MOBID_PINK_CRYST, + MOBID_TREAS41 = 1938, + MOBID_TREAS49 = 1946, + MOBID_SILVERSNIPER = 2042, + MOBID_MAGICDECOY_WIND = 2046, +}; + +//The following system marks a different job ID system used by the map server, +//which makes a lot more sense than the normal one. [Skotlex] +// +//These marks the "level" of the job. +#define JOBL_2_1 0x100 //256 +#define JOBL_2_2 0x200 //512 +#define JOBL_2 0x300 + +#define JOBL_UPPER 0x1000 //4096 +#define JOBL_BABY 0x2000 //8192 +#define JOBL_THIRD 0x4000 //16384 + +//for filtering and quick checking. +#define MAPID_BASEMASK 0x00ff +#define MAPID_UPPERMASK 0x0fff +#define MAPID_THIRDMASK (JOBL_THIRD|MAPID_UPPERMASK) +//First Jobs +//Note the oddity of the novice: +//Super Novices are considered the 2-1 version of the novice! Novices are considered a first class type, too... +enum { +//Novice And 1-1 Jobs + MAPID_NOVICE = 0x0, + MAPID_SWORDMAN, + MAPID_MAGE, + MAPID_ARCHER, + MAPID_ACOLYTE, + MAPID_MERCHANT, + MAPID_THIEF, + MAPID_TAEKWON, + MAPID_WEDDING, + MAPID_GUNSLINGER, + MAPID_NINJA, + MAPID_XMAS, + MAPID_SUMMER, + MAPID_GANGSI, +//2-1 Jobs + MAPID_SUPER_NOVICE = JOBL_2_1|0x0, + MAPID_KNIGHT, + MAPID_WIZARD, + MAPID_HUNTER, + MAPID_PRIEST, + MAPID_BLACKSMITH, + MAPID_ASSASSIN, + MAPID_STAR_GLADIATOR, + MAPID_KAGEROUOBORO = JOBL_2_1|0x0A, + MAPID_DEATH_KNIGHT = JOBL_2_1|0x0D, +//2-2 Jobs + MAPID_CRUSADER = JOBL_2_2|0x1, + MAPID_SAGE, + MAPID_BARDDANCER, + MAPID_MONK, + MAPID_ALCHEMIST, + MAPID_ROGUE, + MAPID_SOUL_LINKER, + MAPID_DARK_COLLECTOR = JOBL_2_2|0x0D, +//Trans Novice And Trans 1-1 Jobs + MAPID_NOVICE_HIGH = JOBL_UPPER|0x0, + MAPID_SWORDMAN_HIGH, + MAPID_MAGE_HIGH, + MAPID_ARCHER_HIGH, + MAPID_ACOLYTE_HIGH, + MAPID_MERCHANT_HIGH, + MAPID_THIEF_HIGH, +//Trans 2-1 Jobs + MAPID_LORD_KNIGHT = JOBL_UPPER|JOBL_2_1|0x1, + MAPID_HIGH_WIZARD, + MAPID_SNIPER, + MAPID_HIGH_PRIEST, + MAPID_WHITESMITH, + MAPID_ASSASSIN_CROSS, +//Trans 2-2 Jobs + MAPID_PALADIN = JOBL_UPPER|JOBL_2_2|0x1, + MAPID_PROFESSOR, + MAPID_CLOWNGYPSY, + MAPID_CHAMPION, + MAPID_CREATOR, + MAPID_STALKER, +//Baby Novice And Baby 1-1 Jobs + MAPID_BABY = JOBL_BABY|0x0, + MAPID_BABY_SWORDMAN, + MAPID_BABY_MAGE, + MAPID_BABY_ARCHER, + MAPID_BABY_ACOLYTE, + MAPID_BABY_MERCHANT, + MAPID_BABY_THIEF, +//Baby 2-1 Jobs + MAPID_SUPER_BABY = JOBL_BABY|JOBL_2_1|0x0, + MAPID_BABY_KNIGHT, + MAPID_BABY_WIZARD, + MAPID_BABY_HUNTER, + MAPID_BABY_PRIEST, + MAPID_BABY_BLACKSMITH, + MAPID_BABY_ASSASSIN, +//Baby 2-2 Jobs + MAPID_BABY_CRUSADER = JOBL_BABY|JOBL_2_2|0x1, + MAPID_BABY_SAGE, + MAPID_BABY_BARDDANCER, + MAPID_BABY_MONK, + MAPID_BABY_ALCHEMIST, + MAPID_BABY_ROGUE, +//3-1 Jobs + MAPID_SUPER_NOVICE_E = JOBL_THIRD|JOBL_2_1|0x0, + MAPID_RUNE_KNIGHT, + MAPID_WARLOCK, + MAPID_RANGER, + MAPID_ARCH_BISHOP, + MAPID_MECHANIC, + MAPID_GUILLOTINE_CROSS, +//3-2 Jobs + MAPID_ROYAL_GUARD = JOBL_THIRD|JOBL_2_2|0x1, + MAPID_SORCERER, + MAPID_MINSTRELWANDERER, + MAPID_SURA, + MAPID_GENETIC, + MAPID_SHADOW_CHASER, +//Trans 3-1 Jobs + MAPID_RUNE_KNIGHT_T = JOBL_THIRD|JOBL_UPPER|JOBL_2_1|0x1, + MAPID_WARLOCK_T, + MAPID_RANGER_T, + MAPID_ARCH_BISHOP_T, + MAPID_MECHANIC_T, + MAPID_GUILLOTINE_CROSS_T, +//Trans 3-2 Jobs + MAPID_ROYAL_GUARD_T = JOBL_THIRD|JOBL_UPPER|JOBL_2_2|0x1, + MAPID_SORCERER_T, + MAPID_MINSTRELWANDERER_T, + MAPID_SURA_T, + MAPID_GENETIC_T, + MAPID_SHADOW_CHASER_T, +//Baby 3-1 Jobs + MAPID_SUPER_BABY_E = JOBL_THIRD|JOBL_BABY|JOBL_2_1|0x0, + MAPID_BABY_RUNE, + MAPID_BABY_WARLOCK, + MAPID_BABY_RANGER, + MAPID_BABY_BISHOP, + MAPID_BABY_MECHANIC, + MAPID_BABY_CROSS, +//Baby 3-2 Jobs + MAPID_BABY_GUARD = JOBL_THIRD|JOBL_BABY|JOBL_2_2|0x1, + MAPID_BABY_SORCERER, + MAPID_BABY_MINSTRELWANDERER, + MAPID_BABY_SURA, + MAPID_BABY_GENETIC, + MAPID_BABY_CHASER, +}; + +//Max size for inputs to Graffiti, Talkie Box and Vending text prompts +#define MESSAGE_SIZE (79 + 1) +//String length you can write in the 'talking box' +#define CHATBOX_SIZE (70 + 1) +//Chatroom-related string sizes +#define CHATROOM_TITLE_SIZE (36 + 1) +#define CHATROOM_PASS_SIZE (8 + 1) +//Max allowed chat text length +#define CHAT_SIZE_MAX (255 + 1) +//24 for npc name + 24 for label + 2 for a "::" and 1 for EOS +#define EVENT_NAME_LENGTH ( NAME_LENGTH * 2 + 3 ) + +#define DEFAULT_AUTOSAVE_INTERVAL 5*60*1000 + +//Specifies maps where players may hit each other +#define map_flag_vs(m) (map[m].flag.pvp || map[m].flag.gvg_dungeon || map[m].flag.gvg || ((agit_flag || agit2_flag) && map[m].flag.gvg_castle) || map[m].flag.battleground) +//Specifies maps that have special GvG/WoE restrictions +#define map_flag_gvg(m) (map[m].flag.gvg || ((agit_flag || agit2_flag) && map[m].flag.gvg_castle)) +//Specifies if the map is tagged as GvG/WoE (regardless of agit_flag status) +#define map_flag_gvg2(m) (map[m].flag.gvg || map[m].flag.gvg_castle) +// No Kill Steal Protection +#define map_flag_ks(m) (map[m].flag.town || map[m].flag.pvp || map[m].flag.gvg || map[m].flag.battleground) + +//This stackable implementation does not means a BL can be more than one type at a time, but it's +//meant to make it easier to check for multiple types at a time on invocations such as map_foreach* calls [Skotlex] +enum bl_type { + BL_NUL = 0x000, + BL_PC = 0x001, + BL_MOB = 0x002, + BL_PET = 0x004, + BL_HOM = 0x008, + BL_MER = 0x010, + BL_ITEM = 0x020, + BL_SKILL = 0x040, + BL_NPC = 0x080, + BL_CHAT = 0x100, + BL_ELEM = 0x200, + + BL_ALL = 0xFFF, +}; + +//For common mapforeach calls. Since pets cannot be affected, they aren't included here yet. +#define BL_CHAR (BL_PC|BL_MOB|BL_HOM|BL_MER|BL_ELEM) + +enum npc_subtype { WARP, SHOP, SCRIPT, CASHSHOP, TOMB }; + +enum { + RC_FORMLESS=0, + RC_UNDEAD, + RC_BRUTE, + RC_PLANT, + RC_INSECT, + RC_FISH, + RC_DEMON, + RC_DEMIHUMAN, + RC_ANGEL, + RC_DRAGON, + RC_BOSS, + RC_NONBOSS, + RC_NONDEMIHUMAN, + RC_MAX +}; + +enum { + RC2_NONE = 0, + RC2_GOBLIN, + RC2_KOBOLD, + RC2_ORC, + RC2_GOLEM, + RC2_GUARDIAN, + RC2_NINJA, + RC2_MAX +}; + +enum { + ELE_NEUTRAL=0, + ELE_WATER, + ELE_EARTH, + ELE_FIRE, + ELE_WIND, + ELE_POISON, + ELE_HOLY, + ELE_DARK, + ELE_GHOST, + ELE_UNDEAD, + ELE_MAX +}; + +enum auto_trigger_flag { + ATF_SELF=0x01, + ATF_TARGET=0x02, + ATF_SHORT=0x04, + ATF_LONG=0x08, + ATF_WEAPON=0x10, + ATF_MAGIC=0x20, + ATF_MISC=0x40, +}; + +struct block_list { + struct block_list *next,*prev; + int id; + int16 m,x,y; + enum bl_type type; +}; + + +// Mob List Held in memory for Dynamic Mobs [Wizputer] +// Expanded to specify all mob-related spawn data by [Skotlex] +struct spawn_data { + short class_; //Class, used because a mob can change it's class + unsigned short m, x, y; //Spawn information (map, point, spawn-area around point) + signed short xs, ys; + unsigned short num; //Number of mobs using this structure + unsigned short active;//Number of mobs that are already spawned (for mob_remove_damaged: no) + unsigned int delay1, delay2; //Spawn delay (fixed base + random variance) + unsigned int level; + struct { + unsigned int size : 2; //Holds if mob has to be tiny/large + unsigned int ai : 4; //Special ai for summoned monsters. + //0: Normal mob | 1: Standard summon, attacks mobs + //2: Alchemist Marine Sphere | 3: Alchemist Summon Flora | 4: Summon Zanzou + unsigned int dynamic : 1; //Whether this data is indexed by a map's dynamic mob list + unsigned int boss : 1; //0: Non-boss monster | 1: Boss monster + } state; + char name[NAME_LENGTH], eventname[EVENT_NAME_LENGTH]; //Name/event +}; + +struct flooritem_data { + struct block_list bl; + unsigned char subx,suby; + int cleartimer; + int first_get_charid,second_get_charid,third_get_charid; + unsigned int first_get_tick,second_get_tick,third_get_tick; + struct item item_data; +}; + +enum _sp { + SP_SPEED,SP_BASEEXP,SP_JOBEXP,SP_KARMA,SP_MANNER,SP_HP,SP_MAXHP,SP_SP, // 0-7 + SP_MAXSP,SP_STATUSPOINT,SP_0a,SP_BASELEVEL,SP_SKILLPOINT,SP_STR,SP_AGI,SP_VIT, // 8-15 + SP_INT,SP_DEX,SP_LUK,SP_CLASS,SP_ZENY,SP_SEX,SP_NEXTBASEEXP,SP_NEXTJOBEXP, // 16-23 + SP_WEIGHT,SP_MAXWEIGHT,SP_1a,SP_1b,SP_1c,SP_1d,SP_1e,SP_1f, // 24-31 + SP_USTR,SP_UAGI,SP_UVIT,SP_UINT,SP_UDEX,SP_ULUK,SP_26,SP_27, // 32-39 + SP_28,SP_ATK1,SP_ATK2,SP_MATK1,SP_MATK2,SP_DEF1,SP_DEF2,SP_MDEF1, // 40-47 + SP_MDEF2,SP_HIT,SP_FLEE1,SP_FLEE2,SP_CRITICAL,SP_ASPD,SP_36,SP_JOBLEVEL, // 48-55 + SP_UPPER,SP_PARTNER,SP_CART,SP_FAME,SP_UNBREAKABLE, //56-60 + SP_CARTINFO=99, // 99 + + SP_BASEJOB=119, // 100+19 - celest + SP_BASECLASS=120, //Hmm.. why 100+19? I just use the next one... [Skotlex] + SP_KILLERRID=121, + SP_KILLEDRID=122, + + // Mercenaries + SP_MERCFLEE=165, SP_MERCKILLS=189, SP_MERCFAITH=190, + + // original 1000- + SP_ATTACKRANGE=1000, SP_ATKELE,SP_DEFELE, // 1000-1002 + SP_CASTRATE, SP_MAXHPRATE, SP_MAXSPRATE, SP_SPRATE, // 1003-1006 + SP_ADDELE, SP_ADDRACE, SP_ADDSIZE, SP_SUBELE, SP_SUBRACE, // 1007-1011 + SP_ADDEFF, SP_RESEFF, // 1012-1013 + SP_BASE_ATK,SP_ASPD_RATE,SP_HP_RECOV_RATE,SP_SP_RECOV_RATE,SP_SPEED_RATE, // 1014-1018 + SP_CRITICAL_DEF,SP_NEAR_ATK_DEF,SP_LONG_ATK_DEF, // 1019-1021 + SP_DOUBLE_RATE, SP_DOUBLE_ADD_RATE, SP_SKILL_HEAL, SP_MATK_RATE, // 1022-1025 + SP_IGNORE_DEF_ELE,SP_IGNORE_DEF_RACE, // 1026-1027 + SP_ATK_RATE,SP_SPEED_ADDRATE,SP_SP_REGEN_RATE, // 1028-1030 + SP_MAGIC_ATK_DEF,SP_MISC_ATK_DEF, // 1031-1032 + SP_IGNORE_MDEF_ELE,SP_IGNORE_MDEF_RACE, // 1033-1034 + SP_MAGIC_ADDELE,SP_MAGIC_ADDRACE,SP_MAGIC_ADDSIZE, // 1035-1037 + SP_PERFECT_HIT_RATE,SP_PERFECT_HIT_ADD_RATE,SP_CRITICAL_RATE,SP_GET_ZENY_NUM,SP_ADD_GET_ZENY_NUM, // 1038-1042 + SP_ADD_DAMAGE_CLASS,SP_ADD_MAGIC_DAMAGE_CLASS,SP_ADD_DEF_CLASS,SP_ADD_MDEF_CLASS, // 1043-1046 + SP_ADD_MONSTER_DROP_ITEM,SP_DEF_RATIO_ATK_ELE,SP_DEF_RATIO_ATK_RACE,SP_UNBREAKABLE_GARMENT, // 1047-1050 + SP_HIT_RATE,SP_FLEE_RATE,SP_FLEE2_RATE,SP_DEF_RATE,SP_DEF2_RATE,SP_MDEF_RATE,SP_MDEF2_RATE, // 1051-1057 + SP_SPLASH_RANGE,SP_SPLASH_ADD_RANGE,SP_AUTOSPELL,SP_HP_DRAIN_RATE,SP_SP_DRAIN_RATE, // 1058-1062 + SP_SHORT_WEAPON_DAMAGE_RETURN,SP_LONG_WEAPON_DAMAGE_RETURN,SP_WEAPON_COMA_ELE,SP_WEAPON_COMA_RACE, // 1063-1066 + SP_ADDEFF2,SP_BREAK_WEAPON_RATE,SP_BREAK_ARMOR_RATE,SP_ADD_STEAL_RATE, // 1067-1070 + SP_MAGIC_DAMAGE_RETURN,SP_ALL_STATS=1073,SP_AGI_VIT,SP_AGI_DEX_STR,SP_PERFECT_HIDE, // 1071-1076 + SP_NO_KNOCKBACK,SP_CLASSCHANGE, // 1077-1078 + SP_HP_DRAIN_VALUE,SP_SP_DRAIN_VALUE, // 1079-1080 + SP_WEAPON_ATK,SP_WEAPON_ATK_RATE, // 1081-1082 + SP_DELAYRATE,SP_HP_DRAIN_RATE_RACE,SP_SP_DRAIN_RATE_RACE, // 1083-1085 + SP_IGNORE_MDEF_RATE,SP_IGNORE_DEF_RATE,SP_SKILL_HEAL2,SP_ADDEFF_ONSKILL, //1086-1089 + SP_ADD_HEAL_RATE,SP_ADD_HEAL2_RATE, //1090-1091 + + SP_RESTART_FULL_RECOVER=2000,SP_NO_CASTCANCEL,SP_NO_SIZEFIX,SP_NO_MAGIC_DAMAGE,SP_NO_WEAPON_DAMAGE,SP_NO_GEMSTONE, // 2000-2005 + SP_NO_CASTCANCEL2,SP_NO_MISC_DAMAGE,SP_UNBREAKABLE_WEAPON,SP_UNBREAKABLE_ARMOR, SP_UNBREAKABLE_HELM, // 2006-2010 + SP_UNBREAKABLE_SHIELD, SP_LONG_ATK_RATE, // 2011-2012 + + SP_CRIT_ATK_RATE, SP_CRITICAL_ADDRACE, SP_NO_REGEN, SP_ADDEFF_WHENHIT, SP_AUTOSPELL_WHENHIT, // 2013-2017 + SP_SKILL_ATK, SP_UNSTRIPABLE, SP_AUTOSPELL_ONSKILL, // 2018-2020 + SP_SP_GAIN_VALUE, SP_HP_REGEN_RATE, SP_HP_LOSS_RATE, SP_ADDRACE2, SP_HP_GAIN_VALUE, // 2021-2025 + SP_SUBSIZE, SP_HP_DRAIN_VALUE_RACE, SP_ADD_ITEM_HEAL_RATE, SP_SP_DRAIN_VALUE_RACE, SP_EXP_ADDRACE, // 2026-2030 + SP_SP_GAIN_RACE, SP_SUBRACE2, SP_UNBREAKABLE_SHOES, // 2031-2033 + SP_UNSTRIPABLE_WEAPON,SP_UNSTRIPABLE_ARMOR,SP_UNSTRIPABLE_HELM,SP_UNSTRIPABLE_SHIELD, // 2034-2037 + SP_INTRAVISION, SP_ADD_MONSTER_DROP_ITEMGROUP, SP_SP_LOSS_RATE, // 2038-2040 + SP_ADD_SKILL_BLOW, SP_SP_VANISH_RATE, SP_MAGIC_SP_GAIN_VALUE, SP_MAGIC_HP_GAIN_VALUE, SP_ADD_CLASS_DROP_ITEM, //2041-2045 + SP_EMATK, SP_SP_GAIN_RACE_ATTACK, SP_HP_GAIN_RACE_ATTACK, SP_SKILL_USE_SP_RATE, //2046-2049 + SP_SKILL_COOLDOWN,SP_SKILL_FIXEDCAST, SP_SKILL_VARIABLECAST, SP_FIXCASTRATE, SP_VARCASTRATE, //2050-2054 + SP_SKILL_USE_SP,SP_MAGIC_ATK_ELE //2055-2056 +}; + +enum _look { + LOOK_BASE, + LOOK_HAIR, + LOOK_WEAPON, + LOOK_HEAD_BOTTOM, + LOOK_HEAD_TOP, + LOOK_HEAD_MID, + LOOK_HAIR_COLOR, + LOOK_CLOTHES_COLOR, + LOOK_SHIELD, + LOOK_SHOES, + LOOK_BODY, + LOOK_FLOOR, + LOOK_ROBE, +}; + +// used by map_setcell() +typedef enum { + CELL_WALKABLE, + CELL_SHOOTABLE, + CELL_WATER, + + CELL_NPC, + CELL_BASILICA, + CELL_LANDPROTECTOR, + CELL_NOVENDING, + CELL_NOCHAT, + CELL_MAELSTROM, + CELL_ICEWALL, + +} cell_t; + +// used by map_getcell() +typedef enum { + CELL_GETTYPE, // retrieves a cell's 'gat' type + + CELL_CHKWALL, // wall (gat type 1) + CELL_CHKWATER, // water (gat type 3) + CELL_CHKCLIFF, // cliff/gap (gat type 5) + + CELL_CHKPASS, // passable cell (gat type non-1/5) + CELL_CHKREACH, // Same as PASS, but ignores the cell-stacking mod. + CELL_CHKNOPASS, // non-passable cell (gat types 1 and 5) + CELL_CHKNOREACH, // Same as NOPASS, but ignores the cell-stacking mod. + CELL_CHKSTACK, // whether cell is full (reached cell stacking limit) + + CELL_CHKNPC, + CELL_CHKBASILICA, + CELL_CHKLANDPROTECTOR, + CELL_CHKNOVENDING, + CELL_CHKNOCHAT, + CELL_CHKMAELSTROM, + CELL_CHKICEWALL, + +} cell_chk; + +struct mapcell +{ + // terrain flags + unsigned char + walkable : 1, + shootable : 1, + water : 1; + + // dynamic flags + unsigned char + npc : 1, + basilica : 1, + landprotector : 1, + novending : 1, + nochat : 1, + maelstrom : 1, + icewall : 1; + +#ifdef CELL_NOSTACK + unsigned char cell_bl; //Holds amount of bls in this cell. +#endif +}; + +struct iwall_data { + char wall_name[50]; + short m, x, y, size; + int8 dir; + bool shootable; +}; + +struct map_data { + char name[MAP_NAME_LENGTH]; + uint16 index; // The map index used by the mapindex* functions. + struct mapcell* cell; // Holds the information of each map cell (NULL if the map is not on this map-server). + struct block_list **block; + struct block_list **block_mob; + int16 m; + int16 xs,ys; // map dimensions (in cells) + int16 bxs,bys; // map dimensions (in blocks) + int16 bgscore_lion, bgscore_eagle; // Battleground ScoreBoard + int npc_num; + int users; + int users_pvp; + int iwall_num; // Total of invisible walls in this map + struct map_flag { + unsigned town : 1; // [Suggestion to protect Mail System] + unsigned autotrade : 1; + unsigned allowks : 1; // [Kill Steal Protection] + unsigned nomemo : 1; + unsigned noteleport : 1; + unsigned noreturn : 1; + unsigned monster_noteleport : 1; + unsigned nosave : 1; + unsigned nobranch : 1; + unsigned noexppenalty : 1; + unsigned pvp : 1; + unsigned pvp_noparty : 1; + unsigned pvp_noguild : 1; + unsigned pvp_nightmaredrop :1; + unsigned pvp_nocalcrank : 1; + unsigned gvg_castle : 1; + unsigned gvg : 1; // Now it identifies gvg versus maps that are active 24/7 + unsigned gvg_dungeon : 1; // Celest + unsigned gvg_noparty : 1; + unsigned battleground : 2; // [BattleGround System] + unsigned nozenypenalty : 1; + unsigned notrade : 1; + unsigned noskill : 1; + unsigned nowarp : 1; + unsigned nowarpto : 1; + unsigned noicewall : 1; // [Valaris] + unsigned snow : 1; // [Valaris] + unsigned clouds : 1; + unsigned clouds2 : 1; // [Valaris] + unsigned fog : 1; // [Valaris] + unsigned fireworks : 1; + unsigned sakura : 1; // [Valaris] + unsigned leaves : 1; // [Valaris] + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //unsigned rain : 1; // [Valaris] + unsigned nogo : 1; // [Valaris] + unsigned nobaseexp : 1; // [Lorky] added by Lupus + unsigned nojobexp : 1; // [Lorky] + unsigned nomobloot : 1; // [Lorky] + unsigned nomvploot : 1; // [Lorky] + unsigned nightenabled :1; //For night display. [Skotlex] + unsigned restricted : 1; // [Komurka] + unsigned nodrop : 1; + unsigned novending : 1; + unsigned loadevent : 1; + unsigned nochat :1; + unsigned partylock :1; + unsigned guildlock :1; + unsigned src4instance : 1; // To flag this map when it's used as a src map for instances + unsigned reset :1; // [Daegaladh] + } flag; + struct point save; + struct npc_data *npc[MAX_NPC_PER_MAP]; + struct { + int drop_id; + int drop_type; + int drop_per; + } drop_list[MAX_DROP_PER_MAP]; + + struct spawn_data *moblist[MAX_MOB_LIST_PER_MAP]; // [Wizputer] + int mob_delete_timer; // [Skotlex] + int zone; // zone number (for item/skill restrictions) + int jexp; // map experience multiplicator + int bexp; // map experience multiplicator + int nocommand; //Blocks @/# commands for non-gms. [Skotlex] + /** + * Ice wall reference counter for bugreport:3574 + * - since there are a thounsand mobs out there in a lot of maps checking on, + * - every targetting for icewall on attack path would just be a waste, so, + * - this counter allows icewall checking be only run when there is a actual ice wall on the map + **/ + int icewall_num; + // Instance Variables + int instance_id; + int instance_src_map; +}; + +/// Stores information about a remote map (for multi-mapserver setups). +/// Beginning of data structure matches 'map_data', to allow typecasting. +struct map_data_other_server { + char name[MAP_NAME_LENGTH]; + unsigned short index; //Index is the map index used by the mapindex* functions. + struct mapcell* cell; // If this is NULL, the map is not on this map-server + uint32 ip; + uint16 port; +}; + +int map_getcell(int16 m,int16 x,int16 y,cell_chk cellchk); +int map_getcellp(struct map_data* m,int16 x,int16 y,cell_chk cellchk); +void map_setcell(int16 m, int16 x, int16 y, cell_t cell, bool flag); +void map_setgatcell(int16 m, int16 x, int16 y, int gat); + +extern struct map_data map[]; +extern int map_num; + +extern int autosave_interval; +extern int minsave_interval; +extern int save_settings; +extern int agit_flag; +extern int agit2_flag; +extern int night_flag; // 0=day, 1=night [Yor] +extern int enable_spy; //Determines if @spy commands are active. +extern char db_path[256]; + +extern char motd_txt[]; +extern char help_txt[]; +extern char help2_txt[]; +extern char charhelp_txt[]; + +extern char wisp_server_name[]; + +// users +void map_setusers(int); +int map_getusers(void); +int map_usercount(void); + +// blocklist lock +int map_freeblock(struct block_list *bl); +int map_freeblock_lock(void); +int map_freeblock_unlock(void); +// blocklist manipulation +int map_addblock(struct block_list* bl); +int map_delblock(struct block_list* bl); +int map_moveblock(struct block_list *, int, int, unsigned int); +int map_foreachinrange(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int type, ...); +int map_foreachinshootrange(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int type, ...); +int map_foreachinarea(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int type, ...); +int map_forcountinrange(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int count, int type, ...); +int map_forcountinarea(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int count, int type, ...); +int map_foreachinmovearea(int (*func)(struct block_list*,va_list), struct block_list* center, int16 range, int16 dx, int16 dy, int type, ...); +int map_foreachincell(int (*func)(struct block_list*,va_list), int16 m, int16 x, int16 y, int type, ...); +int map_foreachinpath(int (*func)(struct block_list*,va_list), int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int16 range, int length, int type, ...); +int map_foreachinmap(int (*func)(struct block_list*,va_list), int16 m, int type, ...); +//blocklist nb in one cell +int map_count_oncell(int16 m,int16 x,int16 y,int type); +struct skill_unit *map_find_skill_unit_oncell(struct block_list *,int16 x,int16 y,uint16 skill_id,struct skill_unit *, int flag); +// search and creation +int map_get_new_object_id(void); +int map_search_freecell(struct block_list *src, int16 m, int16 *x, int16 *y, int16 rx, int16 ry, int flag); +// +int map_quit(struct map_session_data *); +// npc +bool map_addnpc(int16 m,struct npc_data *); + +// map item +int map_clearflooritem_timer(int tid, unsigned int tick, int id, intptr_t data); +int map_removemobs_timer(int tid, unsigned int tick, int id, intptr_t data); +void map_clearflooritem(struct block_list* bl); +int map_addflooritem(struct item *item_data,int amount,int16 m,int16 x,int16 y,int first_charid,int second_charid,int third_charid,int flags); + +// player to map session +void map_addnickdb(int charid, const char* nick); +void map_delnickdb(int charid, const char* nick); +void map_reqnickdb(struct map_session_data* sd,int charid); +const char* map_charid2nick(int charid); +struct map_session_data* map_charid2sd(int charid); + +struct map_session_data * map_id2sd(int id); +struct mob_data * map_id2md(int id); +struct npc_data * map_id2nd(int id); +struct homun_data* map_id2hd(int id); +struct mercenary_data* map_id2mc(int id); +struct chat_data* map_id2cd(int id); +struct block_list * map_id2bl(int id); +bool map_blid_exists( int id ); + +#define map_id2index(id) map[(id)].index +int16 map_mapindex2mapid(unsigned short mapindex); +int16 map_mapname2mapid(const char* name); +int map_mapname2ipport(unsigned short name, uint32* ip, uint16* port); +int map_setipport(unsigned short map, uint32 ip, uint16 port); +int map_eraseipport(unsigned short map, uint32 ip, uint16 port); +int map_eraseallipport(void); +void map_addiddb(struct block_list *); +void map_deliddb(struct block_list *bl); +void map_foreachpc(int (*func)(struct map_session_data* sd, va_list args), ...); +void map_foreachmob(int (*func)(struct mob_data* md, va_list args), ...); +void map_foreachnpc(int (*func)(struct npc_data* nd, va_list args), ...); +void map_foreachregen(int (*func)(struct block_list* bl, va_list args), ...); +void map_foreachiddb(int (*func)(struct block_list* bl, va_list args), ...); +struct map_session_data * map_nick2sd(const char*); +struct mob_data * map_getmob_boss(int16 m); +struct mob_data * map_id2boss(int id); + +// reload config file looking only for npcs +void map_reloadnpc(bool clear); + +/// Bitfield of flags for the iterator. +enum e_mapitflags +{ + MAPIT_NORMAL = 0, +// MAPIT_PCISPLAYING = 1,// Unneeded as pc_db/id_db will only hold auth'ed, active players. +}; +struct s_mapiterator; +struct s_mapiterator* mapit_alloc(enum e_mapitflags flags, enum bl_type types); +void mapit_free(struct s_mapiterator* mapit); +struct block_list* mapit_first(struct s_mapiterator* mapit); +struct block_list* mapit_last(struct s_mapiterator* mapit); +struct block_list* mapit_next(struct s_mapiterator* mapit); +struct block_list* mapit_prev(struct s_mapiterator* mapit); +bool mapit_exists(struct s_mapiterator* mapit); +#define mapit_getallusers() mapit_alloc(MAPIT_NORMAL,BL_PC) +#define mapit_geteachpc() mapit_alloc(MAPIT_NORMAL,BL_PC) +#define mapit_geteachmob() mapit_alloc(MAPIT_NORMAL,BL_MOB) +#define mapit_geteachnpc() mapit_alloc(MAPIT_NORMAL,BL_NPC) +#define mapit_geteachiddb() mapit_alloc(MAPIT_NORMAL,BL_ALL) + +int map_check_dir(int s_dir,int t_dir); +uint8 map_calc_dir( struct block_list *src,int16 x,int16 y); +int map_random_dir(struct block_list *bl, short *x, short *y); // [Skotlex] + +int cleanup_sub(struct block_list *bl, va_list ap); + +int map_delmap(char* mapname); +void map_flags_init(void); + +bool map_iwall_set(int16 m, int16 x, int16 y, int size, int8 dir, bool shootable, const char* wall_name); +void map_iwall_get(struct map_session_data *sd); +void map_iwall_remove(const char *wall_name); + +int map_addmobtolist(unsigned short m, struct spawn_data *spawn); // [Wizputer] +void map_spawnmobs(int16 m); // [Wizputer] +void map_removemobs(int16 m); // [Wizputer] +void do_reconnect_map(void); //Invoked on map-char reconnection [Skotlex] +void map_addmap2db(struct map_data *m); +void map_removemapdb(struct map_data *m); + +extern char *INTER_CONF_NAME; +extern char *LOG_CONF_NAME; +extern char *MAP_CONF_NAME; +extern char *BATTLE_CONF_FILENAME; +extern char *ATCOMMAND_CONF_FILENAME; +extern char *SCRIPT_CONF_NAME; +extern char *MSG_CONF_NAME; +extern char *GRF_PATH_FILENAME; + +//Useful typedefs from jA [Skotlex] +typedef struct map_session_data TBL_PC; +typedef struct npc_data TBL_NPC; +typedef struct mob_data TBL_MOB; +typedef struct flooritem_data TBL_ITEM; +typedef struct chat_data TBL_CHAT; +typedef struct skill_unit TBL_SKILL; +typedef struct pet_data TBL_PET; +typedef struct homun_data TBL_HOM; +typedef struct mercenary_data TBL_MER; +typedef struct elemental_data TBL_ELEM; + +#define BL_CAST(type_, bl) \ + ( ((bl) == (struct block_list*)NULL || (bl)->type != (type_)) ? (T ## type_ *)NULL : (T ## type_ *)(bl) ) + + +extern char main_chat_nick[16]; + +#ifdef BETA_THREAD_TEST + +extern char default_codepage[32]; +extern int map_server_port; +extern char map_server_ip[32]; +extern char map_server_id[32]; +extern char map_server_pw[32]; +extern char map_server_db[32]; + +extern char log_db_ip[32]; +extern int log_db_port; +extern char log_db_id[32]; +extern char log_db_pw[32]; +extern char log_db_db[32]; + +#endif + +#include "../common/sql.h" + +extern int db_use_sqldbs; + +extern Sql* mmysql_handle; +extern Sql* logmysql_handle; + +extern char item_db_db[32]; +extern char item_db2_db[32]; +extern char item_db_re_db[32]; +extern char mob_db_db[32]; +extern char mob_db2_db[32]; +extern char mob_skill_db_db[32]; +extern char mob_skill_db2_db[32]; + +void do_shutdown(void); + +#endif /* _MAP_H_ */ diff --git a/src/map/mapreg.h b/src/map/mapreg.h new file mode 100644 index 000000000..d5fadafc5 --- /dev/null +++ b/src/map/mapreg.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MAPREG_H_ +#define _MAPREG_H_ + +void mapreg_reload(void); +void mapreg_final(void); +void mapreg_init(void); +bool mapreg_config_read(const char* w1, const char* w2); + +int mapreg_readreg(int uid); +char* mapreg_readregstr(int uid); +bool mapreg_setreg(int uid, int val); +bool mapreg_setregstr(int uid, const char* str); + +#endif /* _MAPREG_H_ */ diff --git a/src/map/mapreg_sql.c b/src/map/mapreg_sql.c new file mode 100644 index 000000000..7782f7f02 --- /dev/null +++ b/src/map/mapreg_sql.c @@ -0,0 +1,235 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/sql.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "map.h" // mmysql_handle +#include "script.h" +#include <stdlib.h> +#include <string.h> + +static DBMap* mapreg_db = NULL; // int var_id -> int value +static DBMap* mapregstr_db = NULL; // int var_id -> char* value + +static char mapreg_table[32] = "mapreg"; +static bool mapreg_dirty = false; +#define MAPREG_AUTOSAVE_INTERVAL (300*1000) + + +/// Looks up the value of an integer variable using its uid. +int mapreg_readreg(int uid) +{ + return idb_iget(mapreg_db, uid); +} + +/// Looks up the value of a string variable using its uid. +char* mapreg_readregstr(int uid) +{ + return idb_get(mapregstr_db, uid); +} + +/// Modifies the value of an integer variable. +bool mapreg_setreg(int uid, int val) +{ + int num = (uid & 0x00ffffff); + int i = (uid & 0xff000000) >> 24; + const char* name = get_str(num); + + if( val != 0 ) + { + if( idb_iput(mapreg_db,uid,val) ) + mapreg_dirty = true; // already exists, delay write + else if(name[1] != '@') + {// write new variable to database + char tmp_str[32*2+1]; + Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32)); + if( SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%d')", mapreg_table, tmp_str, i, val) ) + Sql_ShowDebug(mmysql_handle); + } + } + else // val == 0 + { + idb_remove(mapreg_db,uid); + + if( name[1] != '@' ) + {// Remove from database because it is unused. + if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i) ) + Sql_ShowDebug(mmysql_handle); + } + } + + return true; +} + +/// Modifies the value of a string variable. +bool mapreg_setregstr(int uid, const char* str) +{ + int num = (uid & 0x00ffffff); + int i = (uid & 0xff000000) >> 24; + const char* name = get_str(num); + + if( str == NULL || *str == 0 ) + { + if(name[1] != '@') { + if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%d'", mapreg_table, name, i) ) + Sql_ShowDebug(mmysql_handle); + } + idb_remove(mapregstr_db,uid); + } + else + { + if (idb_put(mapregstr_db,uid, aStrdup(str))) + mapreg_dirty = true; + else if(name[1] != '@') { //put returned null, so we must insert. + // Someone is causing a database size infinite increase here without name[1] != '@' [Lance] + char tmp_str[32*2+1]; + char tmp_str2[255*2+1]; + Sql_EscapeStringLen(mmysql_handle, tmp_str, name, strnlen(name, 32)); + Sql_EscapeStringLen(mmysql_handle, tmp_str2, str, strnlen(str, 255)); + if( SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%d','%s')", mapreg_table, tmp_str, i, tmp_str2) ) + Sql_ShowDebug(mmysql_handle); + } + } + + return true; +} + +/// Loads permanent variables from database +static void script_load_mapreg(void) +{ + /* + 0 1 2 + +-------------------------+ + | varname | index | value | + +-------------------------+ + */ + SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle); + char varname[32+1]; + int index; + char value[255+1]; + uint32 length; + + if ( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `varname`, `index`, `value` FROM `%s`", mapreg_table) + || SQL_ERROR == SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); + return; + } + + SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &varname[0], sizeof(varname), &length, NULL); + SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &index, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 2, SQLDT_STRING, &value[0], sizeof(value), NULL, NULL); + + while ( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) + { + int s = add_str(varname); + int i = index; + + if( varname[length-1] == '$' ) + idb_put(mapregstr_db, (i<<24)|s, aStrdup(value)); + else + idb_iput(mapreg_db, (i<<24)|s, atoi(value)); + } + + SqlStmt_Free(stmt); + + mapreg_dirty = false; +} + +/// Saves permanent variables to database +static void script_save_mapreg(void) +{ + DBIterator* iter; + DBData *data; + DBKey key; + + iter = db_iterator(mapreg_db); + for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) ) + { + int num = (key.i & 0x00ffffff); + int i = (key.i & 0xff000000) >> 24; + const char* name = get_str(num); + + if( name[1] == '@' ) + continue; + + if( SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%d' WHERE `varname`='%s' AND `index`='%d'", mapreg_table, db_data2i(data), name, i) ) + Sql_ShowDebug(mmysql_handle); + } + dbi_destroy(iter); + + iter = db_iterator(mapregstr_db); + for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) ) + { + int num = (key.i & 0x00ffffff); + int i = (key.i & 0xff000000) >> 24; + const char* name = get_str(num); + char tmp_str2[2*255+1]; + + if( name[1] == '@' ) + continue; + + Sql_EscapeStringLen(mmysql_handle, tmp_str2, db_data2ptr(data), safestrnlen(db_data2ptr(data), 255)); + if( SQL_ERROR == Sql_Query(mmysql_handle, "UPDATE `%s` SET `value`='%s' WHERE `varname`='%s' AND `index`='%d'", mapreg_table, tmp_str2, name, i) ) + Sql_ShowDebug(mmysql_handle); + } + dbi_destroy(iter); + + mapreg_dirty = false; +} + +static int script_autosave_mapreg(int tid, unsigned int tick, int id, intptr_t data) +{ + if( mapreg_dirty ) + script_save_mapreg(); + + return 0; +} + + +void mapreg_reload(void) +{ + if( mapreg_dirty ) + script_save_mapreg(); + + db_clear(mapreg_db); + db_clear(mapregstr_db); + + script_load_mapreg(); +} + +void mapreg_final(void) +{ + if( mapreg_dirty ) + script_save_mapreg(); + + db_destroy(mapreg_db); + db_destroy(mapregstr_db); +} + +void mapreg_init(void) +{ + mapreg_db = idb_alloc(DB_OPT_BASE); + mapregstr_db = idb_alloc(DB_OPT_RELEASE_DATA); + + script_load_mapreg(); + + add_timer_func_list(script_autosave_mapreg, "script_autosave_mapreg"); + add_timer_interval(gettick() + MAPREG_AUTOSAVE_INTERVAL, script_autosave_mapreg, 0, 0, MAPREG_AUTOSAVE_INTERVAL); +} + +bool mapreg_config_read(const char* w1, const char* w2) +{ + if(!strcmpi(w1, "mapreg_db")) + safestrncpy(mapreg_table, w2, sizeof(mapreg_table)); + else + return false; + + return true; +} diff --git a/src/map/mercenary.c b/src/map/mercenary.c new file mode 100644 index 000000000..973dac33e --- /dev/null +++ b/src/map/mercenary.c @@ -0,0 +1,510 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/mmo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "log.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" +#include "unit.h" +#include "mercenary.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +struct s_mercenary_db mercenary_db[MAX_MERCENARY_CLASS]; // Mercenary Database + +int merc_search_index(int class_) +{ + int i; + ARR_FIND(0, MAX_MERCENARY_CLASS, i, mercenary_db[i].class_ == class_); + return (i == MAX_MERCENARY_CLASS)?-1:i; +} + +bool merc_class(int class_) +{ + return (bool)(merc_search_index(class_) > -1); +} + +struct view_data * merc_get_viewdata(int class_) +{ + int i = merc_search_index(class_); + if( i < 0 ) + return 0; + + return &mercenary_db[i].vd; +} + +int merc_create(struct map_session_data *sd, int class_, unsigned int lifetime) +{ + struct s_mercenary merc; + struct s_mercenary_db *db; + int i; + nullpo_retr(0,sd); + + if( (i = merc_search_index(class_)) < 0 ) + return 0; + + db = &mercenary_db[i]; + memset(&merc,0,sizeof(struct s_mercenary)); + + merc.char_id = sd->status.char_id; + merc.class_ = class_; + merc.hp = db->status.max_hp; + merc.sp = db->status.max_sp; + merc.life_time = lifetime; + + // Request Char Server to create this mercenary + intif_mercenary_create(&merc); + + return 1; +} + +int mercenary_get_lifetime(struct mercenary_data *md) +{ + const struct TimerData * td; + if( md == NULL || md->contract_timer == INVALID_TIMER ) + return 0; + + td = get_timer(md->contract_timer); + return (td != NULL) ? DIFF_TICK(td->tick, gettick()) : 0; +} + +int mercenary_get_guild(struct mercenary_data *md) +{ + int class_; + + if( md == NULL || md->db == NULL ) + return -1; + + class_ = md->db->class_; + + if( class_ >= 6017 && class_ <= 6026 ) + return ARCH_MERC_GUILD; + if( class_ >= 6027 && class_ <= 6036 ) + return SPEAR_MERC_GUILD; + if( class_ >= 6037 && class_ <= 6046 ) + return SWORD_MERC_GUILD; + + return -1; +} + +int mercenary_get_faith(struct mercenary_data *md) +{ + struct map_session_data *sd; + int class_; + + if( md == NULL || md->db == NULL || (sd = md->master) == NULL ) + return 0; + + class_ = md->db->class_; + + if( class_ >= 6017 && class_ <= 6026 ) + return sd->status.arch_faith; + if( class_ >= 6027 && class_ <= 6036 ) + return sd->status.spear_faith; + if( class_ >= 6037 && class_ <= 6046 ) + return sd->status.sword_faith; + + return 0; +} + +int mercenary_set_faith(struct mercenary_data *md, int value) +{ + struct map_session_data *sd; + int class_, *faith; + + if( md == NULL || md->db == NULL || (sd = md->master) == NULL ) + return 0; + + class_ = md->db->class_; + + if( class_ >= 6017 && class_ <= 6026 ) + faith = &sd->status.arch_faith; + else if( class_ >= 6027 && class_ <= 6036 ) + faith = &sd->status.spear_faith; + else if( class_ >= 6037 && class_ <= 6046 ) + faith = &sd->status.sword_faith; + else + return 0; + + *faith += value; + *faith = cap_value(*faith, 0, SHRT_MAX); + clif_mercenary_updatestatus(sd, SP_MERCFAITH); + + return 0; +} + +int mercenary_get_calls(struct mercenary_data *md) +{ + struct map_session_data *sd; + int class_; + + if( md == NULL || md->db == NULL || (sd = md->master) == NULL ) + return 0; + + class_ = md->db->class_; + + if( class_ >= 6017 && class_ <= 6026 ) + return sd->status.arch_calls; + if( class_ >= 6027 && class_ <= 6036 ) + return sd->status.spear_calls; + if( class_ >= 6037 && class_ <= 6046 ) + return sd->status.sword_calls; + + return 0; +} + +int mercenary_set_calls(struct mercenary_data *md, int value) +{ + struct map_session_data *sd; + int class_, *calls; + + if( md == NULL || md->db == NULL || (sd = md->master) == NULL ) + return 0; + + class_ = md->db->class_; + + if( class_ >= 6017 && class_ <= 6026 ) + calls = &sd->status.arch_calls; + else if( class_ >= 6027 && class_ <= 6036 ) + calls = &sd->status.spear_calls; + else if( class_ >= 6037 && class_ <= 6046 ) + calls = &sd->status.sword_calls; + else + return 0; + + *calls += value; + *calls = cap_value(*calls, 0, INT_MAX); + + return 0; +} + +int mercenary_save(struct mercenary_data *md) +{ + md->mercenary.hp = md->battle_status.hp; + md->mercenary.sp = md->battle_status.sp; + md->mercenary.life_time = mercenary_get_lifetime(md); + + intif_mercenary_save(&md->mercenary); + return 1; +} + +static int merc_contract_end(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + struct mercenary_data *md; + + if( (sd = map_id2sd(id)) == NULL ) + return 1; + if( (md = sd->md) == NULL ) + return 1; + + if( md->contract_timer != tid ) + { + ShowError("merc_contract_end %d != %d.\n", md->contract_timer, tid); + return 0; + } + + md->contract_timer = INVALID_TIMER; + merc_delete(md, 0); // Mercenary soldier's duty hour is over. + + return 0; +} + +int merc_delete(struct mercenary_data *md, int reply) +{ + struct map_session_data *sd = md->master; + md->mercenary.life_time = 0; + + merc_contract_stop(md); + + if( !sd ) + return unit_free(&md->bl, CLR_OUTSIGHT); + + if( md->devotion_flag ) + { + md->devotion_flag = 0; + status_change_end(&sd->bl, SC_DEVOTION, INVALID_TIMER); + } + + switch( reply ) + { + case 0: mercenary_set_faith(md, 1); break; // +1 Loyalty on Contract ends. + case 1: mercenary_set_faith(md, -1); break; // -1 Loyalty on Mercenary killed + } + + clif_mercenary_message(sd, reply); + return unit_remove_map(&md->bl, CLR_OUTSIGHT); +} + +void merc_contract_stop(struct mercenary_data *md) +{ + nullpo_retv(md); + if( md->contract_timer != INVALID_TIMER ) + delete_timer(md->contract_timer, merc_contract_end); + md->contract_timer = INVALID_TIMER; +} + +void merc_contract_init(struct mercenary_data *md) +{ + if( md->contract_timer == INVALID_TIMER ) + md->contract_timer = add_timer(gettick() + md->mercenary.life_time, merc_contract_end, md->master->bl.id, 0); + + md->regen.state.block = 0; +} + +int merc_data_received(struct s_mercenary *merc, bool flag) +{ + struct map_session_data *sd; + struct mercenary_data *md; + struct s_mercenary_db *db; + int i = merc_search_index(merc->class_); + + if( (sd = map_charid2sd(merc->char_id)) == NULL ) + return 0; + if( !flag || i < 0 ) + { // Not created - loaded - DB info + sd->status.mer_id = 0; + return 0; + } + + db = &mercenary_db[i]; + if( !sd->md ) + { + sd->md = md = (struct mercenary_data*)aCalloc(1,sizeof(struct mercenary_data)); + md->bl.type = BL_MER; + md->bl.id = npc_get_new_npc_id(); + md->devotion_flag = 0; + + md->master = sd; + md->db = db; + memcpy(&md->mercenary, merc, sizeof(struct s_mercenary)); + status_set_viewdata(&md->bl, md->mercenary.class_); + status_change_init(&md->bl); + unit_dataset(&md->bl); + md->ud.dir = sd->ud.dir; + + md->bl.m = sd->bl.m; + md->bl.x = sd->bl.x; + md->bl.y = sd->bl.y; + unit_calc_pos(&md->bl, sd->bl.x, sd->bl.y, sd->ud.dir); + md->bl.x = md->ud.to_x; + md->bl.y = md->ud.to_y; + + map_addiddb(&md->bl); + status_calc_mercenary(md,1); + md->contract_timer = INVALID_TIMER; + merc_contract_init(md); + } + else + { + memcpy(&sd->md->mercenary, merc, sizeof(struct s_mercenary)); + md = sd->md; + } + + if( sd->status.mer_id == 0 ) + mercenary_set_calls(md, 1); + sd->status.mer_id = merc->mercenary_id; + + if( md && md->bl.prev == NULL && sd->bl.prev != NULL ) + { + map_addblock(&md->bl); + clif_spawn(&md->bl); + clif_mercenary_info(sd); + clif_mercenary_skillblock(sd); + } + + return 1; +} + +void mercenary_heal(struct mercenary_data *md, int hp, int sp) +{ + if( hp ) + clif_mercenary_updatestatus(md->master, SP_HP); + if( sp ) + clif_mercenary_updatestatus(md->master, SP_SP); +} + +int mercenary_dead(struct mercenary_data *md) +{ + merc_delete(md, 1); + return 0; +} + +int mercenary_killbonus(struct mercenary_data *md) +{ + const enum sc_type scs[] = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP }; + int index = rnd() % ARRAYLENGTH(scs); + + sc_start(&md->bl, scs[index], 100, rnd() % 5, 600000); + return 0; +} + +int mercenary_kills(struct mercenary_data *md) +{ + md->mercenary.kill_count++; + md->mercenary.kill_count = cap_value(md->mercenary.kill_count, 0, INT_MAX); + + if( (md->mercenary.kill_count % 50) == 0 ) + { + mercenary_set_faith(md, 1); + mercenary_killbonus(md); + } + + if( md->master ) + clif_mercenary_updatestatus(md->master, SP_MERCKILLS); + + return 0; +} + +int mercenary_checkskill(struct mercenary_data *md, uint16 skill_id) +{ + int i = skill_id - MC_SKILLBASE; + + if( !md || !md->db ) + return 0; + if( md->db->skill[i].id == skill_id ) + return md->db->skill[i].lv; + + return 0; +} + +static bool read_mercenarydb_sub(char* str[], int columns, int current) +{ + int ele; + struct s_mercenary_db *db; + struct status_data *status; + + db = &mercenary_db[current]; + db->class_ = atoi(str[0]); + strncpy(db->sprite, str[1], NAME_LENGTH); + strncpy(db->name, str[2], NAME_LENGTH); + db->lv = atoi(str[3]); + + status = &db->status; + db->vd.class_ = db->class_; + + status->max_hp = atoi(str[4]); + status->max_sp = atoi(str[5]); + status->rhw.range = atoi(str[6]); + status->rhw.atk = atoi(str[7]); + status->rhw.atk2 = status->rhw.atk + atoi(str[8]); + status->def = atoi(str[9]); + status->mdef = atoi(str[10]); + status->str = atoi(str[11]); + status->agi = atoi(str[12]); + status->vit = atoi(str[13]); + status->int_ = atoi(str[14]); + status->dex = atoi(str[15]); + status->luk = atoi(str[16]); + db->range2 = atoi(str[17]); + db->range3 = atoi(str[18]); + status->size = atoi(str[19]); + status->race = atoi(str[20]); + + ele = atoi(str[21]); + status->def_ele = ele%10; + status->ele_lv = ele/20; + if( status->def_ele >= ELE_MAX ) + { + ShowWarning("Mercenary %d has invalid element type %d (max element is %d)\n", db->class_, status->def_ele, ELE_MAX - 1); + status->def_ele = ELE_NEUTRAL; + } + if( status->ele_lv < 1 || status->ele_lv > 4 ) + { + ShowWarning("Mercenary %d has invalid element level %d (max is 4)\n", db->class_, status->ele_lv); + status->ele_lv = 1; + } + + status->aspd_rate = 1000; + status->speed = atoi(str[22]); + status->adelay = atoi(str[23]); + status->amotion = atoi(str[24]); + status->dmotion = atoi(str[25]); + + return true; +} + +int read_mercenarydb(void) +{ + memset(mercenary_db,0,sizeof(mercenary_db)); + sv_readdb(db_path, "mercenary_db.txt", ',', 26, 26, MAX_MERCENARY_CLASS, &read_mercenarydb_sub); + + return 0; +} + +static bool read_mercenary_skilldb_sub(char* str[], int columns, int current) +{// <merc id>,<skill id>,<skill level> + struct s_mercenary_db *db; + int i, class_; + uint16 skill_id, skill_lv; + + class_ = atoi(str[0]); + ARR_FIND(0, MAX_MERCENARY_CLASS, i, class_ == mercenary_db[i].class_); + if( i == MAX_MERCENARY_CLASS ) + { + ShowError("read_mercenary_skilldb : Class %d not found in mercenary_db for skill entry.\n", class_); + return false; + } + + skill_id = atoi(str[1]); + if( skill_id < MC_SKILLBASE || skill_id >= MC_SKILLBASE + MAX_MERCSKILL ) + { + ShowError("read_mercenary_skilldb : Skill %d out of range.\n", skill_id); + return false; + } + + db = &mercenary_db[i]; + skill_lv = atoi(str[2]); + + i = skill_id - MC_SKILLBASE; + db->skill[i].id = skill_id; + db->skill[i].lv = skill_lv; + + return true; +} + +int read_mercenary_skilldb(void) +{ + sv_readdb(db_path, "mercenary_skill_db.txt", ',', 3, 3, -1, &read_mercenary_skilldb_sub); + + return 0; +} + +int do_init_mercenary(void) +{ + read_mercenarydb(); + read_mercenary_skilldb(); + + //add_timer_func_list(mercenary_contract, "mercenary_contract"); + return 0; +} + +int do_final_mercenary(void); diff --git a/src/map/mercenary.h b/src/map/mercenary.h new file mode 100644 index 000000000..994c7aaa4 --- /dev/null +++ b/src/map/mercenary.h @@ -0,0 +1,83 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MERCENARY_H_ +#define _MERCENARY_H_ + +#include "status.h" // struct status_data, struct status_change +#include "unit.h" // struct unit_data + +// number of cells that a mercenary can walk to from it's master before being warped +#define MAX_MER_DISTANCE 15 + +enum { + ARCH_MERC_GUILD, + SPEAR_MERC_GUILD, + SWORD_MERC_GUILD, +}; + +struct s_mercenary_db { + int class_; + char sprite[NAME_LENGTH], name[NAME_LENGTH]; + unsigned short lv; + short range2, range3; + struct status_data status; + struct view_data vd; + struct { + unsigned short id, lv; + } skill[MAX_MERCSKILL]; +}; + +extern struct s_mercenary_db mercenary_db[MAX_MERCENARY_CLASS]; + +struct mercenary_data { + struct block_list bl; + struct unit_data ud; + struct view_data *vd; + struct status_data base_status, battle_status; + struct status_change sc; + struct regen_data regen; + + struct s_mercenary_db *db; + struct s_mercenary mercenary; + char blockskill[MAX_SKILL]; + + struct map_session_data *master; + int contract_timer; + + unsigned devotion_flag : 1; + unsigned int masterteleport_timer; +}; + +bool merc_class(int class_); +struct view_data * merc_get_viewdata(int class_); + +int merc_create(struct map_session_data *sd, int class_, unsigned int lifetime); +int merc_data_received(struct s_mercenary *merc, bool flag); +int mercenary_save(struct mercenary_data *md); + +void mercenary_heal(struct mercenary_data *md, int hp, int sp); +int mercenary_dead(struct mercenary_data *md); + +int merc_delete(struct mercenary_data *md, int reply); +void merc_contract_stop(struct mercenary_data *md); + +int mercenary_get_lifetime(struct mercenary_data *md); +int mercenary_get_guild(struct mercenary_data *md); +int mercenary_get_faith(struct mercenary_data *md); +int mercenary_set_faith(struct mercenary_data *md, int value); +int mercenary_get_calls(struct mercenary_data *md); +int mercenary_set_calls(struct mercenary_data *md, int value); +int mercenary_kills(struct mercenary_data *md); + +int mercenary_checkskill(struct mercenary_data *md, uint16 skill_id); + +/** + * atcommand.c required + **/ +int read_mercenarydb(void); +int read_mercenary_skilldb(void); + +int do_init_mercenary(void); + +#endif /* _MERCENARY_H_ */ diff --git a/src/map/mob.c b/src/map/mob.c new file mode 100644 index 000000000..ac3c1dfe3 --- /dev/null +++ b/src/map/mob.c @@ -0,0 +1,4701 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/db.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.h" +#include "../common/random.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/socket.h" + +#include "map.h" +#include "path.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "pet.h" +#include "status.h" +#include "mob.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "guild.h" +#include "itemdb.h" +#include "skill.h" +#include "battle.h" +#include "party.h" +#include "npc.h" +#include "log.h" +#include "script.h" +#include "atcommand.h" +#include "date.h" +#include "quest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <math.h> + +#define ACTIVE_AI_RANGE 2 //Distance added on top of 'AREA_SIZE' at which mobs enter active AI mode. + +#define IDLE_SKILL_INTERVAL 10 //Active idle skills should be triggered every 1 second (1000/MIN_MOBTHINKTIME) + +#define MOB_LAZYSKILLPERC 0 // Probability for mobs far from players from doing their IDLE skill. (rate of 1000 minute) +// Move probability for mobs away from players (rate of 1000 minute) +// in Aegis, this is 100% for mobs that have been activated by players and none otherwise. +#define MOB_LAZYMOVEPERC(md) (md->state.spotted?1000:0) +#define MOB_MAX_DELAY (24*3600*1000) +#define MAX_MINCHASE 30 //Max minimum chase value to use for mobs. +#define RUDE_ATTACKED_COUNT 2 //After how many rude-attacks should the skill be used? +#define MAX_MOB_CHAT 250 //Max Skill's messages + +//Dynamic mob database, allows saving of memory when there's big gaps in the mob_db [Skotlex] +struct mob_db *mob_db_data[MAX_MOB_DB+1]; +struct mob_db *mob_dummy = NULL; //Dummy mob to be returned when a non-existant one is requested. + +struct mob_db *mob_db(int index) { if (index < 0 || index > MAX_MOB_DB || mob_db_data[index] == NULL) return mob_dummy; return mob_db_data[index]; } + +//Dynamic mob chat database +struct mob_chat *mob_chat_db[MAX_MOB_CHAT+1]; +struct mob_chat *mob_chat(short id) { if(id<=0 || id>MAX_MOB_CHAT || mob_chat_db[id]==NULL) return (struct mob_chat*)NULL; return mob_chat_db[id]; } + +//Dynamic item drop ratio database for per-item drop ratio modifiers overriding global drop ratios. +#define MAX_ITEMRATIO_MOBS 10 +struct item_drop_ratio { + int drop_ratio; + int mob_id[MAX_ITEMRATIO_MOBS]; +}; +static struct item_drop_ratio *item_drop_ratio_db[MAX_ITEMDB]; + +static struct eri *item_drop_ers; //For loot drops delay structures. +static struct eri *item_drop_list_ers; + +static struct { + int qty; + int class_[350]; +} summon[MAX_RANDOMMONSTER]; + +//Defines the Manuk/Splendide mob groups for the status reductions [Epoque] +const int mob_manuk[8] = { 1986, 1987, 1988, 1989, 1990, 1997, 1998, 1999 }; +const int mob_splendide[5] = { 1991, 1992, 1993, 1994, 1995 }; + +/*========================================== + * Local prototype declaration (only required thing) + *------------------------------------------*/ +static int mob_makedummymobdb(int); +static int mob_spawn_guardian_sub(int tid, unsigned int tick, int id, intptr_t data); +int mob_skill_id2skill_idx(int class_,uint16 skill_id); + +/*========================================== + * Mob is searched with a name. + *------------------------------------------*/ +int mobdb_searchname(const char *str) +{ + int i; + struct mob_db* mob; + for(i=0;i<=MAX_MOB_DB;i++){ + mob = mob_db(i); + if(mob == mob_dummy) //Skip dummy mobs. + continue; + if(strcmpi(mob->name,str)==0 || strcmpi(mob->jname,str)==0 || strcmpi(mob->sprite,str)==0) + return i; + } + + return 0; +} +static int mobdb_searchname_array_sub(struct mob_db* mob, const char *str) +{ + if (mob == mob_dummy) + return 1; //Invalid mob. + if(!mob->base_exp && !mob->job_exp) + return 1; //Discount slave-mobs (no exp) as requested by Playtester. [Skotlex] + if(stristr(mob->jname,str)) + return 0; + if(stristr(mob->name,str)) + return 0; + return strcmpi(mob->jname,str); +} + +/*========================================== + * MvP Tomb [GreenBox] + *------------------------------------------*/ +void mvptomb_create(struct mob_data *md, char *killer, time_t time) +{ + struct npc_data *nd; + + if ( md->tomb_nid ) + mvptomb_destroy(md); + + CREATE(nd, struct npc_data, 1); + + nd->bl.id = md->tomb_nid = npc_get_new_npc_id(); + + nd->ud.dir = md->ud.dir; + nd->bl.m = md->bl.m; + nd->bl.x = md->bl.x; + nd->bl.y = md->bl.y; + nd->bl.type = BL_NPC; + + safestrncpy(nd->name, msg_txt(656), sizeof(nd->name)); + + nd->class_ = 565; + nd->speed = 200; + nd->subtype = TOMB; + + nd->u.tomb.md = md; + nd->u.tomb.kill_time = time; + + if (killer) + safestrncpy(nd->u.tomb.killer_name, killer, NAME_LENGTH); + else + nd->u.tomb.killer_name[0] = '\0'; + + map_addnpc(nd->bl.m, nd); + map_addblock(&nd->bl); + status_set_viewdata(&nd->bl, nd->class_); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + clif_spawn(&nd->bl); + +} + +void mvptomb_destroy(struct mob_data *md) { + struct npc_data *nd; + + if ( (nd = map_id2nd(md->tomb_nid)) ) { + int16 m, i; + + m = nd->bl.m; + + clif_clearunit_area(&nd->bl,CLR_OUTSIGHT); + + map_delblock(&nd->bl); + + ARR_FIND( 0, map[m].npc_num, i, map[m].npc[i] == nd ); + if( !(i == map[m].npc_num) ) { + map[m].npc_num--; + map[m].npc[i] = map[m].npc[map[m].npc_num]; + map[m].npc[map[m].npc_num] = NULL; + } + + map_deliddb(&nd->bl); + + aFree(nd); + } + + md->tomb_nid = 0; +} + +/*========================================== + * Founds up to N matches. Returns number of matches [Skotlex] + *------------------------------------------*/ +int mobdb_searchname_array(struct mob_db** data, int size, const char *str) +{ + int count = 0, i; + struct mob_db* mob; + for(i=0;i<=MAX_MOB_DB;i++){ + mob = mob_db(i); + if (mob == mob_dummy || mob_is_clone(i) ) //keep clones out (or you leak player stats) + continue; + if (!mobdb_searchname_array_sub(mob, str)) { + if (count < size) + data[count] = mob; + count++; + } + } + return count; +} + +/*========================================== + * Id Mob is checked. + *------------------------------------------*/ +int mobdb_checkid(const int id) +{ + if (mob_db(id) == mob_dummy) + return 0; + if (mob_is_clone(id)) //checkid is used mostly for random ID based code, therefore clone mobs are out of the question. + return 0; + return id; +} + +/*========================================== + * Returns the view data associated to this mob class. + *------------------------------------------*/ +struct view_data * mob_get_viewdata(int class_) +{ + if (mob_db(class_) == mob_dummy) + return 0; + return &mob_db(class_)->vd; +} +/*========================================== + * Cleans up mob-spawn data to make it "valid" + *------------------------------------------*/ +int mob_parse_dataset(struct spawn_data *data) +{ + size_t len; + + if ((!mobdb_checkid(data->class_) && !mob_is_clone(data->class_)) || !data->num) + return 0; + + if( ( len = strlen(data->eventname) ) > 0 ) + { + if( data->eventname[len-1] == '"' ) + data->eventname[len-1] = '\0'; //Remove trailing quote. + if( data->eventname[0] == '"' ) //Strip leading quotes + memmove(data->eventname, data->eventname+1, len-1); + } + + if(strcmp(data->name,"--en--")==0) + safestrncpy(data->name, mob_db(data->class_)->name, sizeof(data->name)); + else if(strcmp(data->name,"--ja--")==0) + safestrncpy(data->name, mob_db(data->class_)->jname, sizeof(data->name)); + + return 1; +} +/*========================================== + * Generates the basic mob data using the spawn_data provided. + *------------------------------------------*/ +struct mob_data* mob_spawn_dataset(struct spawn_data *data) +{ + struct mob_data *md = (struct mob_data*)aCalloc(1, sizeof(struct mob_data)); + md->bl.id= npc_get_new_npc_id(); + md->bl.type = BL_MOB; + md->bl.m = data->m; + md->bl.x = data->x; + md->bl.y = data->y; + md->class_ = data->class_; + md->state.boss = data->state.boss; + md->db = mob_db(md->class_); + if (data->level > 0 && data->level <= MAX_LEVEL) + md->level = data->level; + memcpy(md->name, data->name, NAME_LENGTH); + if (data->state.ai) + md->special_state.ai = data->state.ai; + if (data->state.size) + md->special_state.size = data->state.size; + if (data->eventname[0] && strlen(data->eventname) >= 4) + memcpy(md->npc_event, data->eventname, 50); + if(md->db->status.mode&MD_LOOTER) + md->lootitem = (struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + md->spawn_timer = INVALID_TIMER; + md->deletetimer = INVALID_TIMER; + md->skill_idx = -1; + status_set_viewdata(&md->bl, md->class_); + status_change_init(&md->bl); + unit_dataset(&md->bl); + + map_addiddb(&md->bl); + return md; +} + +/*========================================== + * Fetches a random mob_id [Skotlex] + * type: Where to fetch from: + * 0: dead branch list + * 1: poring list + * 2: bloody branch list + * flag: + * &1: Apply the summon success chance found in the list (otherwise get any monster from the db) + * &2: Apply a monster check level. + * &4: Selected monster should not be a boss type + * &8: Selected monster must have normal spawn. + * lv: Mob level to check against + *------------------------------------------*/ +int mob_get_random_id(int type, int flag, int lv) +{ + struct mob_db *mob; + int i=0, class_; + if(type < 0 || type >= MAX_RANDOMMONSTER) { + ShowError("mob_get_random_id: Invalid type (%d) of random monster.\n", type); + return 0; + } + do { + if (type) + class_ = summon[type].class_[rnd()%summon[type].qty]; + else //Dead branch + class_ = rnd() % MAX_MOB_DB; + mob = mob_db(class_); + } while ((mob == mob_dummy || + mob_is_clone(class_) || + (flag&1 && mob->summonper[type] <= rnd() % 1000000) || + (flag&2 && lv < mob->lv) || + (flag&4 && mob->status.mode&MD_BOSS) || + (flag&8 && mob->spawn[0].qty < 1) + ) && (i++) < MAX_MOB_DB); + + if(i >= MAX_MOB_DB) // no suitable monster found, use fallback for given list + class_ = mob_db_data[0]->summonper[type]; + return class_; +} + +/*========================================== + * Kill Steal Protection [Zephyrus] + *------------------------------------------*/ +bool mob_ksprotected (struct block_list *src, struct block_list *target) +{ + struct block_list *s_bl, *t_bl; + struct map_session_data + *sd, // Source + *pl_sd, // Owner + *t_sd; // Mob Target + struct status_change_entry *sce; + struct mob_data *md; + unsigned int tick = gettick(); + char output[128]; + + if( !battle_config.ksprotection ) + return false; // KS Protection Disabled + + if( !(md = BL_CAST(BL_MOB,target)) ) + return false; // Tarjet is not MOB + + if( (s_bl = battle_get_master(src)) == NULL ) + s_bl = src; + + if( !(sd = BL_CAST(BL_PC,s_bl)) ) + return false; // Master is not PC + + t_bl = map_id2bl(md->target_id); + if( !t_bl || (s_bl = battle_get_master(t_bl)) == NULL ) + s_bl = t_bl; + + t_sd = BL_CAST(BL_PC,s_bl); + + do { + if( map[md->bl.m].flag.allowks || map_flag_ks(md->bl.m) ) + return false; // Ignores GVG, PVP and AllowKS map flags + + if( md->db->mexp || md->master_id ) + return false; // MVP, Slaves mobs ignores KS + + if( (sce = md->sc.data[SC_KSPROTECTED]) == NULL ) + break; // No KS Protected + + if( sd->bl.id == sce->val1 || // Same Owner + (sce->val2 == 2 && sd->status.party_id && sd->status.party_id == sce->val3) || // Party KS allowed + (sce->val2 == 3 && sd->status.guild_id && sd->status.guild_id == sce->val4) ) // Guild KS allowed + break; + + if( t_sd && ( + (sce->val2 == 1 && sce->val1 != t_sd->bl.id) || + (sce->val2 == 2 && sce->val3 && sce->val3 != t_sd->status.party_id) || + (sce->val2 == 3 && sce->val4 && sce->val4 != t_sd->status.guild_id)) ) + break; + + if( (pl_sd = map_id2sd(sce->val1)) == NULL || pl_sd->bl.m != md->bl.m ) + break; + + if( !pl_sd->state.noks ) + return false; // No KS Protected, but normal players should be protected too + + // Message to KS + if( DIFF_TICK(sd->ks_floodprotect_tick, tick) <= 0 ) + { + sprintf(output, "[KS Warning!! - Owner : %s]", pl_sd->status.name); + clif_disp_onlyself(sd, output, strlen(output)); + + sd->ks_floodprotect_tick = tick + 2000; + } + + // Message to Owner + if( DIFF_TICK(pl_sd->ks_floodprotect_tick, tick) <= 0 ) + { + sprintf(output, "[Watch out! %s is trying to KS you!]", sd->status.name); + clif_disp_onlyself(pl_sd, output, strlen(output)); + + pl_sd->ks_floodprotect_tick = tick + 2000; + } + + return true; + } while(0); + + status_change_start(target, SC_KSPROTECTED, 10000, sd->bl.id, sd->state.noks, sd->status.party_id, sd->status.guild_id, battle_config.ksprotection, 0); + + return false; +} + +struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai) +{ + struct spawn_data data; + + memset(&data, 0, sizeof(struct spawn_data)); + data.m = m; + data.num = 1; + data.class_ = class_; + data.state.size = size; + data.state.ai = ai; + + if (mobname) + safestrncpy(data.name, mobname, sizeof(data.name)); + else + if (battle_config.override_mob_names == 1) + strcpy(data.name, "--en--"); + else + strcpy(data.name, "--ja--"); + + if (event) + safestrncpy(data.eventname, event, sizeof(data.eventname)); + + // Locate spot next to player. + if (bl && (x < 0 || y < 0)) + map_search_freecell(bl, m, &x, &y, 1, 1, 0); + + // if none found, pick random position on map + if (x <= 0 || y <= 0 || map_getcell(m,x,y,CELL_CHKNOREACH)) + map_search_freecell(NULL, m, &x, &y, -1, -1, 1); + + data.x = x; + data.y = y; + + if (!mob_parse_dataset(&data)) + return NULL; + + return mob_spawn_dataset(&data); +} + +/*========================================== + * Spawn a single mob on the specified coordinates. + *------------------------------------------*/ +int mob_once_spawn(struct map_session_data* sd, int16 m, int16 x, int16 y, const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai) +{ + struct mob_data* md = NULL; + int count, lv; + + if (m < 0 || amount <= 0) + return 0; // invalid input + + lv = (sd) ? sd->status.base_level : 255; + + for (count = 0; count < amount; count++) + { + int c = (class_ >= 0) ? class_ : mob_get_random_id(-class_ - 1, (battle_config.random_monster_checklv) ? 3 : 1, lv); + md = mob_once_spawn_sub((sd) ? &sd->bl : NULL, m, x, y, mobname, c, event, size, ai); + + if (!md) + continue; + + if (class_ == MOBID_EMPERIUM) + { + struct guild_castle* gc = guild_mapindex2gc(map[m].index); + struct guild* g = (gc) ? guild_search(gc->guild_id) : NULL; + if (gc) + { + md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data)); + md->guardian_data->castle = gc; + md->guardian_data->number = MAX_GUARDIANS; + md->guardian_data->guild_id = gc->guild_id; + if (g) + { + md->guardian_data->emblem_id = g->emblem_id; + memcpy(md->guardian_data->guild_name, g->name, NAME_LENGTH); + } + else if (gc->guild_id) //Guild not yet available, retry in 5. + add_timer(gettick()+5000,mob_spawn_guardian_sub,md->bl.id,md->guardian_data->guild_id); + } + } // end addition [Valaris] + + mob_spawn(md); + + if (class_ < 0 && battle_config.dead_branch_active) + //Behold Aegis's masterful decisions yet again... + //"I understand the "Aggressive" part, but the "Can Move" and "Can Attack" is just stupid" - Poki#3 + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE|MD_CANATTACK|MD_CANMOVE|MD_ANGRY, 0, 60000); + } + + return (md) ? md->bl.id : 0; // id of last spawned mob +} + +/*========================================== + * Spawn mobs in the specified area. + *------------------------------------------*/ +int mob_once_spawn_area(struct map_session_data* sd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai) +{ + int i, max, id = 0; + int lx = -1, ly = -1; + + if (m < 0 || amount <= 0) + return 0; // invalid input + + // normalize x/y coordinates + if (x0 > x1) + swap(x0, x1); + if (y0 > y1) + swap(y0, y1); + + // choose a suitable max. number of attempts + max = (y1 - y0 + 1)*(x1 - x0 + 1)*3; + if (max > 1000) + max = 1000; + + // spawn mobs, one by one + for (i = 0; i < amount; i++) + { + int x, y; + int j = 0; + + // find a suitable map cell + do { + x = rnd()%(x1-x0+1)+x0; + y = rnd()%(y1-y0+1)+y0; + j++; + } while (map_getcell(m,x,y,CELL_CHKNOPASS) && j < max); + + if (j == max) + {// attempt to find an available cell failed + if (lx == -1 && ly == -1) + return 0; // total failure + + // fallback to last good x/y pair + x = lx; + y = ly; + } + + // record last successful coordinates + lx = x; + ly = y; + + id = mob_once_spawn(sd, m, x, y, mobname, class_, 1, event, size, ai); + } + + return id; // id of last spawned mob +} +/*========================================== + * Set a Guardian's guild data [Skotlex] + *------------------------------------------*/ +static int mob_spawn_guardian_sub(int tid, unsigned int tick, int id, intptr_t data) +{ //Needed because the guild_data may not be available at guardian spawn time. + struct block_list* bl = map_id2bl(id); + struct mob_data* md; + struct guild* g; + int guardup_lv; + + if (bl == NULL) //It is possible mob was already removed from map when the castle has no owner. [Skotlex] + return 0; + + if (bl->type != BL_MOB) + { + ShowError("mob_spawn_guardian_sub: Block error!\n"); + return 0; + } + + md = (struct mob_data*)bl; + nullpo_ret(md->guardian_data); + g = guild_search((int)data); + + if (g == NULL) + { //Liberate castle, if the guild is not found this is an error! [Skotlex] + ShowError("mob_spawn_guardian_sub: Couldn't load guild %d!\n", (int)data); + if (md->class_ == MOBID_EMPERIUM) + { //Not sure this is the best way, but otherwise we'd be invoking this for ALL guardians spawned later on. + md->guardian_data->guild_id = 0; + if (md->guardian_data->castle->guild_id) //Free castle up. + { + ShowNotice("Clearing ownership of castle %d (%s)\n", md->guardian_data->castle->castle_id, md->guardian_data->castle->castle_name); + guild_castledatasave(md->guardian_data->castle->castle_id, 1, 0); + } + } else { + if (md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS && md->guardian_data->castle->guardian[md->guardian_data->number].visible) + guild_castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number,0); + unit_free(&md->bl,CLR_OUTSIGHT); //Remove guardian. + } + return 0; + } + guardup_lv = guild_checkskill(g,GD_GUARDUP); + md->guardian_data->emblem_id = g->emblem_id; + memcpy(md->guardian_data->guild_name, g->name, NAME_LENGTH); + md->guardian_data->guardup_lv = guardup_lv; + if( guardup_lv ) + status_calc_mob(md, 0); //Give bonuses. + return 0; +} + +/*========================================== + * Summoning Guardians [Valaris] + *------------------------------------------*/ +int mob_spawn_guardian(const char* mapname, short x, short y, const char* mobname, int class_, const char* event, int guardian, bool has_index) +{ + struct mob_data *md=NULL; + struct spawn_data data; + struct guild *g=NULL; + struct guild_castle *gc; + int16 m; + memset(&data, 0, sizeof(struct spawn_data)); + data.num = 1; + + m=map_mapname2mapid(mapname); + + if(m<0) + { + ShowWarning("mob_spawn_guardian: Map [%s] not found.\n", mapname); + return 0; + } + data.m = m; + data.num = 1; + if(class_<=0) { + class_ = mob_get_random_id(-class_-1, 1, 99); + if (!class_) return 0; + } + + data.class_ = class_; + + if( !has_index ) + { + guardian = -1; + } + else if( guardian < 0 || guardian >= MAX_GUARDIANS ) + { + ShowError("mob_spawn_guardian: Invalid guardian index %d for guardian %d (castle map %s)\n", guardian, class_, map[m].name); + return 0; + } + + if((x<=0 || y<=0) && !map_search_freecell(NULL, m, &x, &y, -1,-1, 1)) + { + ShowWarning("mob_spawn_guardian: Couldn't locate a spawn cell for guardian class %d (index %d) at castle map %s\n",class_, guardian, map[m].name); + return 0; + } + data.x = x; + data.y = y; + safestrncpy(data.name, mobname, sizeof(data.name)); + safestrncpy(data.eventname, event, sizeof(data.eventname)); + if (!mob_parse_dataset(&data)) + return 0; + + gc=guild_mapname2gc(map[m].name); + if (gc == NULL) + { + ShowError("mob_spawn_guardian: No castle set at map %s\n", map[m].name); + return 0; + } + if (!gc->guild_id) + ShowWarning("mob_spawn_guardian: Spawning guardian %d on a castle with no guild (castle map %s)\n", class_, map[m].name); + else + g = guild_search(gc->guild_id); + + if( has_index && gc->guardian[guardian].id ) + { //Check if guardian already exists, refuse to spawn if so. + struct mob_data *md2 = (TBL_MOB*)map_id2bl(gc->guardian[guardian].id); + if (md2 && md2->bl.type == BL_MOB && + md2->guardian_data && md2->guardian_data->number == guardian) + { + ShowError("mob_spawn_guardian: Attempted to spawn guardian in position %d which already has a guardian (castle map %s)\n", guardian, map[m].name); + return 0; + } + } + + md = mob_spawn_dataset(&data); + md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data)); + md->guardian_data->number = guardian; + md->guardian_data->guild_id = gc->guild_id; + md->guardian_data->castle = gc; + if( has_index ) + {// permanent guardian + gc->guardian[guardian].id = md->bl.id; + } + else + {// temporary guardian + int i; + ARR_FIND(0, gc->temp_guardians_max, i, gc->temp_guardians[i] == 0); + if( i == gc->temp_guardians_max ) + { + ++(gc->temp_guardians_max); + RECREATE(gc->temp_guardians, int, gc->temp_guardians_max); + } + gc->temp_guardians[i] = md->bl.id; + } + if (g) + { + md->guardian_data->emblem_id = g->emblem_id; + memcpy (md->guardian_data->guild_name, g->name, NAME_LENGTH); + md->guardian_data->guardup_lv = guild_checkskill(g,GD_GUARDUP); + } else if (md->guardian_data->guild_id) + add_timer(gettick()+5000,mob_spawn_guardian_sub,md->bl.id,md->guardian_data->guild_id); + mob_spawn(md); + + return md->bl.id; +} + +/*========================================== + * Summoning BattleGround [Zephyrus] + *------------------------------------------*/ +int mob_spawn_bg(const char* mapname, short x, short y, const char* mobname, int class_, const char* event, unsigned int bg_id) +{ + struct mob_data *md = NULL; + struct spawn_data data; + int16 m; + + if( (m = map_mapname2mapid(mapname)) < 0 ) + { + ShowWarning("mob_spawn_bg: Map [%s] not found.\n", mapname); + return 0; + } + + memset(&data, 0, sizeof(struct spawn_data)); + data.m = m; + data.num = 1; + if( class_ <= 0 ) + { + class_ = mob_get_random_id(-class_-1,1,99); + if( !class_ ) return 0; + } + + data.class_ = class_; + if( (x <= 0 || y <= 0) && !map_search_freecell(NULL, m, &x, &y, -1,-1, 1) ) + { + ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %d) at map %s\n",class_, bg_id, map[m].name); + return 0; + } + + data.x = x; + data.y = y; + safestrncpy(data.name, mobname, sizeof(data.name)); + safestrncpy(data.eventname, event, sizeof(data.eventname)); + if( !mob_parse_dataset(&data) ) + return 0; + + md = mob_spawn_dataset(&data); + mob_spawn(md); + md->bg_id = bg_id; // BG Team ID + + return md->bl.id; +} + +/*========================================== + * Reachability to a Specification ID existence place + * state indicates type of 'seek' mob should do: + * - MSS_LOOT: Looking for item, path must be easy. + * - MSS_RUSH: Chasing attacking player, path is complex + * - MSS_FOLLOW: Initiative/support seek, path is complex + *------------------------------------------*/ +int mob_can_reach(struct mob_data *md,struct block_list *bl,int range, int state) +{ + int easy = 0; + + nullpo_ret(md); + nullpo_ret(bl); + switch (state) { + case MSS_RUSH: + case MSS_FOLLOW: + easy = 0; //(battle_config.mob_ai&0x1?0:1); + break; + case MSS_LOOT: + default: + easy = 1; + break; + } + return unit_can_reach_bl(&md->bl, bl, range, easy, NULL, NULL); +} + +/*========================================== + * Links nearby mobs (supportive mobs) + *------------------------------------------*/ +int mob_linksearch(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + int class_; + struct block_list *target; + unsigned int tick; + + nullpo_ret(bl); + md=(struct mob_data *)bl; + class_ = va_arg(ap, int); + target = va_arg(ap, struct block_list *); + tick=va_arg(ap, unsigned int); + + if (md->class_ == class_ && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME + && !md->target_id) + { + md->last_linktime = tick; + if( mob_can_reach(md,target,md->db->range2, MSS_FOLLOW) ){ // Reachability judging + md->target_id = target->id; + md->min_chase=md->db->range3; + return 1; + } + } + + return 0; +} + +/*========================================== + * mob spawn with delay (timer function) + *------------------------------------------*/ +int mob_delayspawn(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list* bl = map_id2bl(id); + struct mob_data* md = BL_CAST(BL_MOB, bl); + + if( md ) + { + if( md->spawn_timer != tid ) + { + ShowError("mob_delayspawn: Timer mismatch: %d != %d\n", tid, md->spawn_timer); + return 0; + } + md->spawn_timer = INVALID_TIMER; + mob_spawn(md); + } + return 0; +} + +/*========================================== + * spawn timing calculation + *------------------------------------------*/ +int mob_setdelayspawn(struct mob_data *md) +{ + unsigned int spawntime, mode; + struct mob_db *db; + + if (!md->spawn) //Doesn't has respawn data! + return unit_free(&md->bl,CLR_DEAD); + + spawntime = md->spawn->delay1; //Base respawn time + if (md->spawn->delay2) //random variance + spawntime+= rnd()%md->spawn->delay2; + + //Apply the spawn delay fix [Skotlex] + db = mob_db(md->spawn->class_); + mode = db->status.mode; + if (mode & MD_BOSS) { //Bosses + if (battle_config.boss_spawn_delay != 100) { + // Divide by 100 first to prevent overflows + //(precision loss is minimal as duration is in ms already) + spawntime = spawntime/100*battle_config.boss_spawn_delay; + } + } else if (mode&MD_PLANT) { //Plants + if (battle_config.plant_spawn_delay != 100) { + spawntime = spawntime/100*battle_config.plant_spawn_delay; + } + } else if (battle_config.mob_spawn_delay != 100) { //Normal mobs + spawntime = spawntime/100*battle_config.mob_spawn_delay; + } + + if (spawntime < 500) //Min respawn time (is it needed?) + spawntime = 500; + + if( md->spawn_timer != INVALID_TIMER ) + delete_timer(md->spawn_timer, mob_delayspawn); + md->spawn_timer = add_timer(gettick()+spawntime, mob_delayspawn, md->bl.id, 0); + return 0; +} + +int mob_count_sub(struct block_list *bl, va_list ap) { + int mobid[10], i; + ARR_FIND(0, 10, i, (mobid[i] = va_arg(ap, int)) == 0); //fetch till 0 + if (mobid[0]) { //if there one let's check it otherwise go backward + TBL_MOB *md = BL_CAST(BL_MOB, bl); + ARR_FIND(0, 10, i, md->class_ == mobid[i]); + return (i < 10) ? 1 : 0; + } + return 1; //backward compatibility +} + +/*========================================== + * Mob spawning. Initialization is also variously here. + *------------------------------------------*/ +int mob_spawn (struct mob_data *md) +{ + int i=0; + unsigned int tick = gettick(); + int c =0; + + md->last_thinktime = tick; + if (md->bl.prev != NULL) + unit_remove_map(&md->bl,CLR_RESPAWN); + else + if (md->spawn && md->class_ != md->spawn->class_) + { + md->class_ = md->spawn->class_; + status_set_viewdata(&md->bl, md->class_); + md->db = mob_db(md->class_); + memcpy(md->name,md->spawn->name,NAME_LENGTH); + } + + if (md->spawn) { //Respawn data + md->bl.m = md->spawn->m; + md->bl.x = md->spawn->x; + md->bl.y = md->spawn->y; + + if( (md->bl.x == 0 && md->bl.y == 0) || md->spawn->xs || md->spawn->ys ) + { //Monster can be spawned on an area. + if( !map_search_freecell(&md->bl, -1, &md->bl.x, &md->bl.y, md->spawn->xs, md->spawn->ys, battle_config.no_spawn_on_player?4:0) ) + { // retry again later + if( md->spawn_timer != INVALID_TIMER ) + delete_timer(md->spawn_timer, mob_delayspawn); + md->spawn_timer = add_timer(tick+5000,mob_delayspawn,md->bl.id,0); + return 1; + } + } + else if( battle_config.no_spawn_on_player > 99 && map_foreachinrange(mob_count_sub, &md->bl, AREA_SIZE, BL_PC) ) + { // retry again later (players on sight) + if( md->spawn_timer != INVALID_TIMER ) + delete_timer(md->spawn_timer, mob_delayspawn); + md->spawn_timer = add_timer(tick+5000,mob_delayspawn,md->bl.id,0); + return 1; + } + } + + memset(&md->state, 0, sizeof(md->state)); + status_calc_mob(md, 1); + md->attacked_id = 0; + md->target_id = 0; + md->move_fail_count = 0; + md->ud.state.attack_continue = 0; + md->ud.target_to = 0; + if( md->spawn_timer != INVALID_TIMER ) + { + delete_timer(md->spawn_timer, mob_delayspawn); + md->spawn_timer = INVALID_TIMER; + } + +// md->master_id = 0; + md->master_dist = 0; + + md->state.aggressive = md->status.mode&MD_ANGRY?1:0; + md->state.skillstate = MSS_IDLE; + md->next_walktime = tick+rnd()%5000+1000; + md->last_linktime = tick; + md->dmgtick = tick - 5000; + md->last_pcneartime = 0; + + for (i = 0, c = tick-MOB_MAX_DELAY; i < MAX_MOBSKILL; i++) + md->skilldelay[i] = c; + + memset(md->dmglog, 0, sizeof(md->dmglog)); + md->tdmg = 0; + + if (md->lootitem) + memset(md->lootitem, 0, sizeof(*md->lootitem)); + + md->lootitem_count = 0; + + if(md->db->option) + // Added for carts, falcons and pecos for cloned monsters. [Valaris] + md->sc.option = md->db->option; + + // MvP tomb [GreenBox] + if ( md->tomb_nid ) + mvptomb_destroy(md); + + map_addblock(&md->bl); + if( map[md->bl.m].users ) + clif_spawn(&md->bl); + skill_unit_move(&md->bl,tick,1); + mobskill_use(md, tick, MSC_SPAWN); + return 0; +} + +/*========================================== + * Determines if the mob can change target. [Skotlex] + *------------------------------------------*/ +static int mob_can_changetarget(struct mob_data* md, struct block_list* target, int mode) +{ + // if the monster was provoked ignore the above rule [celest] + if(md->state.provoke_flag) + { + if (md->state.provoke_flag == target->id) + return 1; + else if (!(battle_config.mob_ai&0x4)) + return 0; + } + + switch (md->state.skillstate) { + case MSS_BERSERK: + if (!(mode&MD_CHANGETARGET_MELEE)) + return 0; + return (battle_config.mob_ai&0x4 || check_distance_bl(&md->bl, target, 3)); + case MSS_RUSH: + return (mode&MD_CHANGETARGET_CHASE); + case MSS_FOLLOW: + case MSS_ANGRY: + case MSS_IDLE: + case MSS_WALK: + case MSS_LOOT: + return 1; + default: + return 0; + } +} + +/*========================================== + * Determination for an attack of a monster + *------------------------------------------*/ +int mob_target(struct mob_data *md,struct block_list *bl,int dist) +{ + nullpo_ret(md); + nullpo_ret(bl); + + // Nothing will be carried out if there is no mind of changing TAGE by TAGE ending. + if(md->target_id && !mob_can_changetarget(md, bl, status_get_mode(&md->bl))) + return 0; + + if(!status_check_skilluse(&md->bl, bl, 0, 0)) + return 0; + + md->target_id = bl->id; // Since there was no disturbance, it locks on to target. + if (md->state.provoke_flag && bl->id != md->state.provoke_flag) + md->state.provoke_flag = 0; + md->min_chase=dist+md->db->range3; + if(md->min_chase>MAX_MINCHASE) + md->min_chase=MAX_MINCHASE; + return 0; +} + +/*========================================== + * The ?? routine of an active monster + *------------------------------------------*/ +static int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + struct block_list **target; + int mode; + int dist; + + nullpo_ret(bl); + md=va_arg(ap,struct mob_data *); + target= va_arg(ap,struct block_list**); + mode= va_arg(ap,int); + + //If can't seek yet, not an enemy, or you can't attack it, skip. + if ((*target) == bl || !status_check_skilluse(&md->bl, bl, 0, 0)) + return 0; + + if ((mode&MD_TARGETWEAK) && status_get_lv(bl) >= md->level-5) + return 0; + + if(battle_check_target(&md->bl,bl,BCT_ENEMY)<=0) + return 0; + + switch (bl->type) + { + case BL_PC: + if (((TBL_PC*)bl)->state.gangsterparadise && + !(status_get_mode(&md->bl)&MD_BOSS)) + return 0; //Gangster paradise protection. + default: + if (battle_config.hom_setting&0x4 && + (*target) && (*target)->type == BL_HOM && bl->type != BL_HOM) + return 0; //For some reason Homun targets are never overriden. + + dist = distance_bl(&md->bl, bl); + if( + ((*target) == NULL || !check_distance_bl(&md->bl, *target, dist)) && + battle_check_range(&md->bl,bl,md->db->range2) + ) { //Pick closest target? + + if( map[bl->m].icewall_num && + !path_search_long(NULL,bl->m,md->bl.x,md->bl.y,bl->x,bl->y,CELL_CHKICEWALL) ) { + + if( !check_distance_bl(&md->bl, bl, status_get_range(&md->bl) ) ) + return 0; + + } + + (*target) = bl; + md->target_id=bl->id; + md->min_chase= dist + md->db->range3; + if(md->min_chase>MAX_MINCHASE) + md->min_chase=MAX_MINCHASE; + return 1; + } + break; + } + return 0; +} + +/*========================================== + * chase target-change routine. + *------------------------------------------*/ +static int mob_ai_sub_hard_changechase(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + struct block_list **target; + + nullpo_ret(bl); + md=va_arg(ap,struct mob_data *); + target= va_arg(ap,struct block_list**); + + //If can't seek yet, not an enemy, or you can't attack it, skip. + if ((*target) == bl || + battle_check_target(&md->bl,bl,BCT_ENEMY)<=0 || + !status_check_skilluse(&md->bl, bl, 0, 0)) + return 0; + + if(battle_check_range (&md->bl, bl, md->status.rhw.range)) + { + (*target) = bl; + md->target_id=bl->id; + md->min_chase= md->db->range3; + } + return 1; +} + +/*========================================== + * finds nearby bg ally for guardians looking for users to follow. + *------------------------------------------*/ +static int mob_ai_sub_hard_bg_ally(struct block_list *bl,va_list ap) { + struct mob_data *md; + struct block_list **target; + + nullpo_ret(bl); + md=va_arg(ap,struct mob_data *); + target= va_arg(ap,struct block_list**); + + if( status_check_skilluse(&md->bl, bl, 0, 0) && battle_check_target(&md->bl,bl,BCT_ENEMY)<=0 ) { + (*target) = bl; + } + return 1; +} + +/*========================================== + * loot monster item search + *------------------------------------------*/ +static int mob_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct mob_data* md; + struct block_list **target; + int dist; + + md=va_arg(ap,struct mob_data *); + target= va_arg(ap,struct block_list**); + + dist=distance_bl(&md->bl, bl); + if(mob_can_reach(md,bl,dist+1, MSS_LOOT) && + ((*target) == NULL || !check_distance_bl(&md->bl, *target, dist)) //New target closer than previous one. + ) { + (*target) = bl; + md->target_id=bl->id; + md->min_chase=md->db->range3; + } + return 0; +} + +static int mob_warpchase_sub(struct block_list *bl,va_list ap) { + struct block_list *target; + struct npc_data **target_nd; + struct npc_data *nd; + int *min_distance; + int cur_distance; + + target= va_arg(ap, struct block_list*); + target_nd= va_arg(ap, struct npc_data**); + min_distance= va_arg(ap, int*); + + nd = (TBL_NPC*) bl; + + if(nd->subtype != WARP) + return 0; //Not a warp + + if(nd->u.warp.mapindex != map[target->m].index) + return 0; //Does not lead to the same map. + + cur_distance = distance_blxy(target, nd->u.warp.x, nd->u.warp.y); + if (cur_distance < *min_distance) + { //Pick warp that leads closest to target. + *target_nd = nd; + *min_distance = cur_distance; + return 1; + } + return 0; +} +/*========================================== + * Processing of slave monsters + *------------------------------------------*/ +static int mob_ai_sub_hard_slavemob(struct mob_data *md,unsigned int tick) +{ + struct block_list *bl; + + bl=map_id2bl(md->master_id); + + if (!bl || status_isdead(bl)) { + status_kill(&md->bl); + return 1; + } + if (bl->prev == NULL) + return 0; //Master not on a map? Could be warping, do not process. + + if(status_get_mode(&md->bl)&MD_CANMOVE) + { //If the mob can move, follow around. [Check by Skotlex] + int old_dist; + + // Distance with between slave and master is measured. + old_dist=md->master_dist; + md->master_dist=distance_bl(&md->bl, bl); + + // Since the master was in near immediately before, teleport is carried out and it pursues. + if(bl->m != md->bl.m || + (old_dist<10 && md->master_dist>18) || + md->master_dist > MAX_MINCHASE + ){ + md->master_dist = 0; + unit_warp(&md->bl,bl->m,bl->x,bl->y,CLR_TELEPORT); + return 1; + } + + if(md->target_id) //Slave is busy with a target. + return 0; + + // Approach master if within view range, chase back to Master's area also if standing on top of the master. + if((md->master_dist>MOB_SLAVEDISTANCE || md->master_dist == 0) && + unit_can_move(&md->bl)) + { + short x = bl->x, y = bl->y; + mob_stop_attack(md); + if(map_search_freecell(&md->bl, bl->m, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 1) + && unit_walktoxy(&md->bl, x, y, 0)) + return 1; + } + } else if (bl->m != md->bl.m && map_flag_gvg(md->bl.m)) { + //Delete the summoned mob if it's in a gvg ground and the master is elsewhere. [Skotlex] + status_kill(&md->bl); + return 1; + } + + //Avoid attempting to lock the master's target too often to avoid unnecessary overload. [Skotlex] + if (DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME && !md->target_id) + { + struct unit_data *ud = unit_bl2ud(bl); + md->last_linktime = tick; + + if (ud) { + struct block_list *tbl=NULL; + if (ud->target && ud->state.attack_continue) + tbl=map_id2bl(ud->target); + else if (ud->skilltarget) { + tbl = map_id2bl(ud->skilltarget); + //Required check as skilltarget is not always an enemy. [Skotlex] + if (tbl && battle_check_target(&md->bl, tbl, BCT_ENEMY) <= 0) + tbl = NULL; + } + if (tbl && status_check_skilluse(&md->bl, tbl, 0, 0)) { + md->target_id=tbl->id; + md->min_chase=md->db->range3+distance_bl(&md->bl, tbl); + if(md->min_chase>MAX_MINCHASE) + md->min_chase=MAX_MINCHASE; + return 1; + } + } + } + return 0; +} + +/*========================================== + * A lock of target is stopped and mob moves to a standby state. + * This also triggers idle skill/movement since the AI can get stuck + * when trying to pick new targets when the current chosen target is + * unreachable. + *------------------------------------------*/ +int mob_unlocktarget(struct mob_data *md, unsigned int tick) +{ + nullpo_ret(md); + + switch (md->state.skillstate) { + case MSS_WALK: + if (md->ud.walktimer != INVALID_TIMER) + break; + //Because it is not unset when the mob finishes walking. + md->state.skillstate = MSS_IDLE; + case MSS_IDLE: + // Idle skill. + if ((md->target_id || !(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) && + mobskill_use(md, tick, -1)) + break; + //Random walk. + if (!md->master_id && + DIFF_TICK(md->next_walktime, tick) <= 0 && + !mob_randomwalk(md,tick)) + //Delay next random walk when this one failed. + md->next_walktime=tick+rnd()%3000; + break; + default: + mob_stop_attack(md); + if (battle_config.mob_ai&0x8) + mob_stop_walking(md,1); //Immediately stop chasing. + md->state.skillstate = MSS_IDLE; + md->next_walktime=tick+rnd()%3000+3000; + break; + } + if (md->target_id) { + md->target_id=0; + md->ud.target_to = 0; + unit_set_target(&md->ud, 0); + } + return 0; +} +/*========================================== + * Random walk + *------------------------------------------*/ +int mob_randomwalk(struct mob_data *md,unsigned int tick) +{ + const int retrycount=20; + int i,x,y,c,d; + int speed; + + nullpo_ret(md); + + if(DIFF_TICK(md->next_walktime,tick)>0 || + !unit_can_move(&md->bl) || + !(status_get_mode(&md->bl)&MD_CANMOVE)) + return 0; + + d =12-md->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ // Search of a movable place + int r=rnd(); + x=r%(d*2+1)-d; + y=r/(d*2+1)%(d*2+1)-d; + x+=md->bl.x; + y+=md->bl.y; + + if((map_getcell(md->bl.m,x,y,CELL_CHKPASS)) && unit_walktoxy(&md->bl,x,y,1)){ + break; + } + } + if(i==retrycount){ + md->move_fail_count++; + if(md->move_fail_count>1000){ + ShowWarning("MOB can't move. random spawn %d, class = %d, at %s (%d,%d)\n",md->bl.id,md->class_,map[md->bl.m].name, md->bl.x, md->bl.y); + md->move_fail_count=0; + mob_spawn(md); + } + return 0; + } + speed=status_get_speed(&md->bl); + for(i=c=0;i<md->ud.walkpath.path_len;i++){ // The next walk start time is calculated. + if(md->ud.walkpath.path[i]&1) + c+=speed*14/10; + else + c+=speed; + } + md->state.skillstate=MSS_WALK; + md->move_fail_count=0; + md->next_walktime = tick+rnd()%3000+3000+c; + return 1; +} + +int mob_warpchase(struct mob_data *md, struct block_list *target) +{ + struct npc_data *warp = NULL; + int distance = AREA_SIZE; + if (!(target && battle_config.mob_ai&0x40 && battle_config.mob_warp&1)) + return 0; //Can't warp chase. + + if (target->m == md->bl.m && check_distance_bl(&md->bl, target, AREA_SIZE)) + return 0; //No need to do a warp chase. + + if (md->ud.walktimer != INVALID_TIMER && + map_getcell(md->bl.m,md->ud.to_x,md->ud.to_y,CELL_CHKNPC)) + return 1; //Already walking to a warp. + + //Search for warps within mob's viewing range. + map_foreachinrange (mob_warpchase_sub, &md->bl, + md->db->range2, BL_NPC, target, &warp, &distance); + + if (warp && unit_walktobl(&md->bl, &warp->bl, 1, 1)) + return 1; + return 0; +} + +/*========================================== + * AI of MOB whose is near a Player + *------------------------------------------*/ +static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick) +{ + struct block_list *tbl = NULL, *abl = NULL; + int dist; + int mode; + int search_size; + int view_range, can_move; + + if(md->bl.prev == NULL || md->status.hp <= 0) + return false; + + if (DIFF_TICK(tick, md->last_thinktime) < MIN_MOBTHINKTIME) + return false; + + md->last_thinktime = tick; + + if (md->ud.skilltimer != INVALID_TIMER) + return false; + + if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= 3) + return false; + + // Abnormalities + if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE ) + || md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) {//Should reset targets. + md->target_id = md->attacked_id = 0; + return false; + } + + if (md->sc.count && md->sc.data[SC_BLIND]) + view_range = 3; + else + view_range = md->db->range2; + mode = status_get_mode(&md->bl); + + can_move = (mode&MD_CANMOVE)&&unit_can_move(&md->bl); + + if (md->target_id) + { //Check validity of current target. [Skotlex] + tbl = map_id2bl(md->target_id); + if (!tbl || tbl->m != md->bl.m || + (md->ud.attacktimer == INVALID_TIMER && !status_check_skilluse(&md->bl, tbl, 0, 0)) || + (md->ud.walktimer != INVALID_TIMER && !(battle_config.mob_ai&0x1) && !check_distance_bl(&md->bl, tbl, md->min_chase)) || + ( + tbl->type == BL_PC && + ((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_BOSS)) || + ((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER) + )) { //Unlock current target. + if (mob_warpchase(md, tbl)) + return true; //Chasing this target. + mob_unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Imediately do random walk. + tbl = NULL; + } + } + + // Check for target change. + if( md->attacked_id && mode&MD_CANATTACK ) + { + if( md->attacked_id == md->target_id ) + { //Rude attacked check. + if( !battle_check_range(&md->bl, tbl, md->status.rhw.range) + && ( //Can't attack back and can't reach back. + (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1) + || md->sc.data[SC_BITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNSTRAP] + || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target. + || !mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) + ) + && md->state.attacked_count++ >= RUDE_ATTACKED_COUNT + && !mobskill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack + && can_move && unit_escape(&md->bl, tbl, rnd()%10 +1)) // Attempt escape + { //Escaped + md->attacked_id = 0; + return true; + } + } + else + if( (abl = map_id2bl(md->attacked_id)) && (!tbl || mob_can_changetarget(md, abl, mode)) ) + { + if( md->bl.m != abl->m || abl->prev == NULL + || (dist = distance_bl(&md->bl, abl)) >= MAX_MINCHASE // Attacker longer than visual area + || battle_check_target(&md->bl, abl, BCT_ENEMY) <= 0 // Attacker is not enemy of mob + || (battle_config.mob_ai&0x2 && !status_check_skilluse(&md->bl, abl, 0, 0)) // Cannot normal attack back to Attacker + || (!battle_check_range(&md->bl, abl, md->status.rhw.range) // Not on Melee Range and ... + && ( // Reach check + (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1) + || md->sc.data[SC_BITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNSTRAP] + || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target. + || !mob_can_reach(md, abl, dist+md->db->range3, MSS_RUSH) + ) + ) ) + { // Rude attacked + if (md->state.attacked_count++ >= RUDE_ATTACKED_COUNT + && !mobskill_use(md, tick, MSC_RUDEATTACKED) && can_move + && !tbl && unit_escape(&md->bl, abl, rnd()%10 +1)) + { //Escaped. + //TODO: Maybe it shouldn't attempt to run if it has another, valid target? + md->attacked_id = 0; + return true; + } + } + else + if (!(battle_config.mob_ai&0x2) && !status_check_skilluse(&md->bl, abl, 0, 0)) + { + //Can't attack back, but didn't invoke a rude attacked skill... + } + else + { //Attackable + if (!tbl || dist < md->status.rhw.range || !check_distance_bl(&md->bl, tbl, dist) + || battle_gettarget(tbl) != md->bl.id) + { //Change if the new target is closer than the actual one + //or if the previous target is not attacking the mob. [Skotlex] + md->target_id = md->attacked_id; // set target + if (md->state.attacked_count) + md->state.attacked_count--; //Should we reset rude attack count? + md->min_chase = dist+md->db->range3; + if(md->min_chase>MAX_MINCHASE) + md->min_chase=MAX_MINCHASE; + tbl = abl; //Set the new target + } + } + } + + //Clear it since it's been checked for already. + md->attacked_id = 0; + } + + // Processing of slave monster + if (md->master_id > 0 && mob_ai_sub_hard_slavemob(md, tick)) + return true; + + // Scan area for targets + if (!tbl && mode&MD_LOOTER && md->lootitem && DIFF_TICK(tick, md->ud.canact_tick) > 0 && + (md->lootitem_count < LOOTITEM_SIZE || battle_config.monster_loot_type != 1)) + { // Scan area for items to loot, avoid trying to loot of the mob is full and can't consume the items. + map_foreachinrange (mob_ai_sub_hard_lootsearch, &md->bl, view_range, BL_ITEM, md, &tbl); + } + + if ((!tbl && mode&MD_AGGRESSIVE) || md->state.skillstate == MSS_FOLLOW) + { + map_foreachinrange (mob_ai_sub_hard_activesearch, &md->bl, view_range, DEFAULT_ENEMY_TYPE(md), md, &tbl, mode); + } + else + if (mode&MD_CHANGECHASE && (md->state.skillstate == MSS_RUSH || md->state.skillstate == MSS_FOLLOW)) + { + search_size = view_range<md->status.rhw.range ? view_range:md->status.rhw.range; + map_foreachinrange (mob_ai_sub_hard_changechase, &md->bl, search_size, DEFAULT_ENEMY_TYPE(md), md, &tbl); + } + + if (!tbl) { //No targets available. + if (mode&MD_ANGRY && !md->state.aggressive) + md->state.aggressive = 1; //Restore angry state when no targets are available. + + /* bg guardians follow allies when no targets nearby */ + if( md->bg_id && mode&MD_CANATTACK ) { + if( md->ud.walktimer != INVALID_TIMER ) + return true;/* we are already moving */ + map_foreachinrange (mob_ai_sub_hard_bg_ally, &md->bl, view_range, BL_PC, md, &tbl, mode); + if( tbl ) { + if( distance_blxy(&md->bl, tbl->x, tbl->y) <= 3 || unit_walktobl(&md->bl, tbl, 1, 1) ) + return true;/* we're moving or close enough don't unlock the target. */ + } + } + + //This handles triggering idle walk/skill. + mob_unlocktarget(md, tick); + return true; + } + + //Target exists, attack or loot as applicable. + if (tbl->type == BL_ITEM) + { //Loot time. + struct flooritem_data *fitem; + if (md->ud.target == tbl->id && md->ud.walktimer != INVALID_TIMER) + return true; //Already locked. + if (md->lootitem == NULL) + { //Can't loot... + mob_unlocktarget (md, tick); + return true; + } + if (!check_distance_bl(&md->bl, tbl, 1)) + { //Still not within loot range. + if (!(mode&MD_CANMOVE)) + { //A looter that can't move? Real smart. + mob_unlocktarget(md,tick); + return true; + } + if (!can_move) //Stuck. Wait before walking. + return true; + md->state.skillstate = MSS_LOOT; + if (!unit_walktobl(&md->bl, tbl, 1, 1)) + mob_unlocktarget(md, tick); //Can't loot... + return true; + } + //Within looting range. + if (md->ud.attacktimer != INVALID_TIMER) + return true; //Busy attacking? + + fitem = (struct flooritem_data *)tbl; + //Logs items, taken by (L)ooter Mobs [Lupus] + log_pick_mob(md, LOG_TYPE_LOOT, fitem->item_data.amount, &fitem->item_data); + + if (md->lootitem_count < LOOTITEM_SIZE) { + memcpy (&md->lootitem[md->lootitem_count++], &fitem->item_data, sizeof(md->lootitem[0])); + } else { //Destroy first looted item... + if (md->lootitem[0].card[0] == CARD0_PET) + intif_delete_petdata( MakeDWord(md->lootitem[0].card[1],md->lootitem[0].card[2]) ); + memmove(&md->lootitem[0], &md->lootitem[1], (LOOTITEM_SIZE-1)*sizeof(md->lootitem[0])); + memcpy (&md->lootitem[LOOTITEM_SIZE-1], &fitem->item_data, sizeof(md->lootitem[0])); + } + if (pcdb_checkid(md->vd->class_)) + { //Give them walk act/delay to properly mimic players. [Skotlex] + clif_takeitem(&md->bl,tbl); + md->ud.canact_tick = tick + md->status.amotion; + unit_set_walkdelay(&md->bl, tick, md->status.amotion, 1); + } + //Clear item. + map_clearflooritem (tbl); + mob_unlocktarget (md,tick); + return true; + } + //Attempt to attack. + //At this point we know the target is attackable, we just gotta check if the range matches. + if (md->ud.target == tbl->id && md->ud.attacktimer != INVALID_TIMER) //Already locked. + return true; + + if (battle_check_range (&md->bl, tbl, md->status.rhw.range)) + { //Target within range, engage + + if(tbl->type == BL_PC) + mob_log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus) + unit_attack(&md->bl,tbl->id,1); + return true; + } + + //Out of range... + if (!(mode&MD_CANMOVE)) + { //Can't chase. Attempt an idle skill before unlocking. + md->state.skillstate = MSS_IDLE; + if (!mobskill_use(md, tick, -1)) + mob_unlocktarget(md,tick); + return true; + } + + if (!can_move) + { //Stuck. Attempt an idle skill + md->state.skillstate = MSS_IDLE; + if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) + mobskill_use(md, tick, -1); + return true; + } + + if (md->ud.walktimer != INVALID_TIMER && md->ud.target == tbl->id && + ( + !(battle_config.mob_ai&0x1) || + check_distance_blxy(tbl, md->ud.to_x, md->ud.to_y, md->status.rhw.range) + )) //Current target tile is still within attack range. + return true; + + //Follow up if possible. + if(!mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) || + !unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2)) + mob_unlocktarget(md,tick); + + return true; +} + +static int mob_ai_sub_hard_timer(struct block_list *bl,va_list ap) +{ + struct mob_data *md = (struct mob_data*)bl; + unsigned int tick = va_arg(ap, unsigned int); + if (mob_ai_sub_hard(md, tick)) + { //Hard AI triggered. + if(!md->state.spotted) + md->state.spotted = 1; + md->last_pcneartime = tick; + } + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (foreachclient) + *------------------------------------------*/ +static int mob_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick; + tick=va_arg(ap,unsigned int); + map_foreachinrange(mob_ai_sub_hard_timer,&sd->bl, AREA_SIZE+ACTIVE_AI_RANGE, BL_MOB,tick); + + return 0; +} + +/*========================================== + * Negligent mode MOB AI (PC is not in near) + *------------------------------------------*/ +static int mob_ai_sub_lazy(struct mob_data *md, va_list args) +{ + unsigned int tick; + + nullpo_ret(md); + + if(md->bl.prev == NULL) + return 0; + + tick = va_arg(args,unsigned int); + + if (battle_config.mob_ai&0x20 && map[md->bl.m].users>0) + return (int)mob_ai_sub_hard(md, tick); + + if (md->bl.prev==NULL || md->status.hp == 0) + return 1; + + if(battle_config.mob_active_time && + md->last_pcneartime && + !(md->status.mode&MD_BOSS) && + DIFF_TICK(tick,md->last_thinktime) > MIN_MOBTHINKTIME) + { + if (DIFF_TICK(tick,md->last_pcneartime) < battle_config.mob_active_time) + return (int)mob_ai_sub_hard(md, tick); + md->last_pcneartime = 0; + } + + if(battle_config.boss_active_time && + md->last_pcneartime && + (md->status.mode&MD_BOSS) && + DIFF_TICK(tick,md->last_thinktime) > MIN_MOBTHINKTIME) + { + if (DIFF_TICK(tick,md->last_pcneartime) < battle_config.boss_active_time) + return (int)mob_ai_sub_hard(md, tick); + md->last_pcneartime = 0; + } + + if(DIFF_TICK(tick,md->last_thinktime)< 10*MIN_MOBTHINKTIME) + return 0; + + md->last_thinktime=tick; + + if (md->master_id) { + mob_ai_sub_hard_slavemob (md,tick); + return 0; + } + + if( DIFF_TICK(md->next_walktime,tick) < 0 && (status_get_mode(&md->bl)&MD_CANMOVE) && unit_can_move(&md->bl) ) + { + if( map[md->bl.m].users > 0 ) + { + if( rnd()%1000 < MOB_LAZYMOVEPERC(md) ) + mob_randomwalk(md, tick); + else + if( rnd()%1000 < MOB_LAZYSKILLPERC ) //Chance to do a mob's idle skill. + mobskill_use(md, tick, -1); + } + else + { + if( rnd()%1000 < MOB_LAZYMOVEPERC(md) ) + mob_randomwalk(md, tick); + } + } + return 0; +} + +/*========================================== + * Negligent processing for mob outside PC field of view (interval timer function) + *------------------------------------------*/ +static int mob_ai_lazy(int tid, unsigned int tick, int id, intptr_t data) +{ + map_foreachmob(mob_ai_sub_lazy,tick); + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (interval timer function) + *------------------------------------------*/ +static int mob_ai_hard(int tid, unsigned int tick, int id, intptr_t data) +{ + + if (battle_config.mob_ai&0x20) + map_foreachmob(mob_ai_sub_lazy,tick); + else + map_foreachpc(mob_ai_sub_foreachclient,tick); + + return 0; +} + +/*========================================== + * Initializes the delay drop structure for mob-dropped items. + *------------------------------------------*/ +static struct item_drop* mob_setdropitem(int nameid, int qty) +{ + struct item_drop *drop = ers_alloc(item_drop_ers, struct item_drop); + memset(&drop->item_data, 0, sizeof(struct item)); + drop->item_data.nameid = nameid; + drop->item_data.amount = qty; + drop->item_data.identify = itemdb_isidentified(nameid); + drop->next = NULL; + return drop; +} + +/*========================================== + * Initializes the delay drop structure for mob-looted items. + *------------------------------------------*/ +static struct item_drop* mob_setlootitem(struct item* item) +{ + struct item_drop *drop = ers_alloc(item_drop_ers, struct item_drop); + memcpy(&drop->item_data, item, sizeof(struct item)); + drop->next = NULL; + return drop; +} + +/*========================================== + * item drop with delay (timer function) + *------------------------------------------*/ +static int mob_delay_item_drop(int tid, unsigned int tick, int id, intptr_t data) +{ + struct item_drop_list *list; + struct item_drop *ditem, *ditem_prev; + list=(struct item_drop_list *)data; + ditem = list->item; + while (ditem) { + map_addflooritem(&ditem->item_data,ditem->item_data.amount, + list->m,list->x,list->y, + list->first_charid,list->second_charid,list->third_charid,0); + ditem_prev = ditem; + ditem = ditem->next; + ers_free(item_drop_ers, ditem_prev); + } + ers_free(item_drop_list_ers, list); + return 0; +} + +/*========================================== + * Sets the item_drop into the item_drop_list. + * Also performs logging and autoloot if enabled. + * rate is the drop-rate of the item, required for autoloot. + * flag : Killed only by homunculus? + *------------------------------------------*/ +static void mob_item_drop(struct mob_data *md, struct item_drop_list *dlist, struct item_drop *ditem, int loot, int drop_rate, unsigned short flag) +{ + TBL_PC* sd; + + //Logs items, dropped by mobs [Lupus] + log_pick_mob(md, loot?LOG_TYPE_LOOT:LOG_TYPE_PICKDROP_MONSTER, -ditem->item_data.amount, &ditem->item_data); + + sd = map_charid2sd(dlist->first_charid); + if( sd == NULL ) sd = map_charid2sd(dlist->second_charid); + if( sd == NULL ) sd = map_charid2sd(dlist->third_charid); + + if( sd + && (drop_rate <= sd->state.autoloot || pc_isautolooting(sd, ditem->item_data.nameid)) + && (battle_config.idle_no_autoloot == 0 || DIFF_TICK(last_tick, sd->idletime) < battle_config.idle_no_autoloot) + && (battle_config.homunculus_autoloot?1:!flag) +#ifdef AUTOLOOT_DISTANCE + && sd->bl.m == md->bl.m + && check_distance_blxy(&sd->bl, dlist->x, dlist->y, AUTOLOOT_DISTANCE) +#endif + ) { //Autoloot. + if (party_share_loot(party_search(sd->status.party_id), + sd, &ditem->item_data, sd->status.char_id) == 0 + ) { + ers_free(item_drop_ers, ditem); + return; + } + } + ditem->next = dlist->item; + dlist->item = ditem; +} + +int mob_timer_delete(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list* bl = map_id2bl(id); + struct mob_data* md = BL_CAST(BL_MOB, bl); + + if( md ) + { + if( md->deletetimer != tid ) + { + ShowError("mob_timer_delete: Timer mismatch: %d != %d\n", tid, md->deletetimer); + return 0; + } + //for Alchemist CANNIBALIZE [Lupus] + md->deletetimer = INVALID_TIMER; + unit_free(bl, CLR_TELEPORT); + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int mob_deleteslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + int id; + + nullpo_ret(bl); + nullpo_ret(md = (struct mob_data *)bl); + + id=va_arg(ap,int); + if(md->master_id > 0 && md->master_id == id ) + status_kill(bl); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int mob_deleteslave(struct mob_data *md) +{ + nullpo_ret(md); + + map_foreachinmap(mob_deleteslave_sub, md->bl.m, BL_MOB,md->bl.id); + return 0; +} +// Mob respawning through KAIZEL or NPC_REBIRTH [Skotlex] +int mob_respawn(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl = map_id2bl(id); + + if(!bl) return 0; + status_revive(bl, (uint8)data, 0); + return 1; +} + +void mob_log_damage(struct mob_data *md, struct block_list *src, int damage) +{ + int char_id = 0, flag = MDLF_NORMAL; + + if( damage < 0 ) + return; //Do nothing for absorbed damage. + if( !damage && !(src->type&DEFAULT_ENEMY_TYPE(md)) ) + return; //Do not log non-damaging effects from non-enemies. + if( src->id == md->bl.id ) + return; //Do not log self-damage. + + switch( src->type ) + { + case BL_PC: + { + struct map_session_data *sd = (TBL_PC*)src; + char_id = sd->status.char_id; + if( damage ) + md->attacked_id = src->id; + break; + } + case BL_HOM: + { + struct homun_data *hd = (TBL_HOM*)src; + flag = MDLF_HOMUN; + if( hd->master ) + char_id = hd->master->status.char_id; + if( damage ) + md->attacked_id = src->id; + break; + } + case BL_MER: + { + struct mercenary_data *mer = (TBL_MER*)src; + if( mer->master ) + char_id = mer->master->status.char_id; + if( damage ) + md->attacked_id = src->id; + break; + } + case BL_PET: + { + struct pet_data *pd = (TBL_PET*)src; + flag = MDLF_PET; + if( pd->msd ) + { + char_id = pd->msd->status.char_id; + if( damage ) //Let mobs retaliate against the pet's master [Skotlex] + md->attacked_id = pd->msd->bl.id; + } + break; + } + case BL_MOB: + { + struct mob_data* md2 = (TBL_MOB*)src; + if( md2->special_state.ai && md2->master_id ) + { + struct map_session_data* msd = map_id2sd(md2->master_id); + if( msd ) + char_id = msd->status.char_id; + } + if( !damage ) + break; + //Let players decide whether to retaliate versus the master or the mob. [Skotlex] + if( md2->master_id && battle_config.retaliate_to_master ) + md->attacked_id = md2->master_id; + else + md->attacked_id = src->id; + break; + } + case BL_ELEM: + { + struct elemental_data *ele = (TBL_ELEM*)src; + if( ele->master ) + char_id = ele->master->status.char_id; + if( damage ) + md->attacked_id = src->id; + break; + } + default: //For all unhandled types. + md->attacked_id = src->id; + } + + if( char_id ) + { //Log damage... + int i,minpos; + unsigned int mindmg; + for(i=0,minpos=DAMAGELOG_SIZE-1,mindmg=UINT_MAX;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==char_id && + md->dmglog[i].flag==flag) + break; + if(md->dmglog[i].id==0) { //Store data in first empty slot. + md->dmglog[i].id = char_id; + md->dmglog[i].flag= flag; + break; + } + if(md->dmglog[i].dmg<mindmg && i) + { //Never overwrite first hit slot (he gets double exp bonus) + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=damage; + else { + md->dmglog[minpos].id = char_id; + md->dmglog[minpos].flag= flag; + md->dmglog[minpos].dmg = damage; + } + } + return; +} +//Call when a mob has received damage. +void mob_damage(struct mob_data *md, struct block_list *src, int damage) +{ + if (damage > 0) { //Store total damage... + if (UINT_MAX - (unsigned int)damage > md->tdmg) + md->tdmg+=damage; + else if (md->tdmg == UINT_MAX) + damage = 0; //Stop recording damage once the cap has been reached. + else { //Cap damage log... + damage = (int)(UINT_MAX - md->tdmg); + md->tdmg = UINT_MAX; + } + if (md->state.aggressive) { //No longer aggressive, change to retaliate AI. + md->state.aggressive = 0; + if(md->state.skillstate== MSS_ANGRY) + md->state.skillstate = MSS_BERSERK; + if(md->state.skillstate== MSS_FOLLOW) + md->state.skillstate = MSS_RUSH; + } + //Log damage + if (src) + mob_log_damage(md, src, damage); + md->dmgtick = gettick(); + } + + if (battle_config.show_mob_info&3) + clif_charnameack (0, &md->bl); + + if (!src) + return; + +#if PACKETVER >= 20120404 + if( !(md->status.mode&MD_BOSS) ){ + int i; + for(i = 0; i < DAMAGELOG_SIZE; i++){ // must show hp bar to all char who already hit the mob. + struct map_session_data *sd = map_charid2sd(md->dmglog[i].id); + if( sd && check_distance_bl(&md->bl, &sd->bl, AREA_SIZE) ) // check if in range + clif_monster_hp_bar(md, sd->fd); + } + } +#endif + + if( md->special_state.ai == 2 ) {//LOne WOlf explained that ANYONE can trigger the marine countdown skill. [Skotlex] + md->state.alchemist = 1; + mobskill_use(md, gettick(), MSC_ALCHEMIST); + } +} + +/*========================================== + * Signals death of mob. + * type&1 -> no drops, type&2 -> no exp + *------------------------------------------*/ +int mob_dead(struct mob_data *md, struct block_list *src, int type) +{ + struct status_data *status; + struct map_session_data *sd = NULL, *tmpsd[DAMAGELOG_SIZE]; + struct map_session_data *mvp_sd = NULL, *second_sd = NULL, *third_sd = NULL; + + struct { + struct party_data *p; + int id,zeny; + unsigned int base_exp,job_exp; + } pt[DAMAGELOG_SIZE]; + int i,temp,count,pnum=0,m=md->bl.m; + int dmgbltypes = 0; // bitfield of all bl types, that caused damage to the mob and are elligible for exp distribution + unsigned int mvp_damage, tick = gettick(); + bool rebirth, homkillonly; + + status = &md->status; + + if( src && src->type == BL_PC ) + { + sd = (struct map_session_data *)src; + mvp_sd = sd; + } + + if( md->guardian_data && md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS ) + guild_castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number,0); + + if( src ) + { // Use Dead skill only if not killed by Script or Command + md->state.skillstate = MSS_DEAD; + mobskill_use(md,tick,-1); + } + + map_freeblock_lock(); + + memset(pt,0,sizeof(pt)); + + if(src && src->type == BL_MOB) + mob_unlocktarget((struct mob_data *)src,tick); + + // filter out entries not eligible for exp distribution + memset(tmpsd,0,sizeof(tmpsd)); + for(i = 0, count = 0, mvp_damage = 0; i < DAMAGELOG_SIZE && md->dmglog[i].id; i++) + { + struct map_session_data* tsd = map_charid2sd(md->dmglog[i].id); + + if(tsd == NULL) + continue; // skip empty entries + if(tsd->bl.m != m) + continue; // skip players not on this map + count++; //Only logged into same map chars are counted for the total. + if (pc_isdead(tsd)) + continue; // skip dead players + if(md->dmglog[i].flag == MDLF_HOMUN && !merc_is_hom_active(tsd->hd)) + continue; // skip homunc's share if inactive + if( md->dmglog[i].flag == MDLF_PET && (!tsd->status.pet_id || !tsd->pd) ) + continue; // skip pet's share if inactive + + if(md->dmglog[i].dmg > mvp_damage) + { + third_sd = second_sd; + second_sd = mvp_sd; + mvp_sd = tsd; + mvp_damage = md->dmglog[i].dmg; + } + + tmpsd[i] = tsd; // record as valid damage-log entry + + switch( md->dmglog[i].flag ) + { + case MDLF_NORMAL: dmgbltypes|= BL_PC; break; + case MDLF_HOMUN: dmgbltypes|= BL_HOM; break; + case MDLF_PET: dmgbltypes|= BL_PET; break; + } + } + + // determines, if the monster was killed by homunculus' damage only + homkillonly = (bool)( ( dmgbltypes&BL_HOM ) && !( dmgbltypes&~BL_HOM ) ); + + if(!battle_config.exp_calc_type && count > 1) + { //Apply first-attacker 200% exp share bonus + //TODO: Determine if this should go before calculating the MVP player instead of after. + if (UINT_MAX - md->dmglog[0].dmg > md->tdmg) { + md->tdmg += md->dmglog[0].dmg; + md->dmglog[0].dmg<<=1; + } else { + md->dmglog[0].dmg+= UINT_MAX - md->tdmg; + md->tdmg = UINT_MAX; + } + } + + if(!(type&2) && //No exp + (!map[m].flag.pvp || battle_config.pvp_exp) && //Pvp no exp rule [MouseJstr] + (!md->master_id || !md->special_state.ai) && //Only player-summoned mobs do not give exp. [Skotlex] + (!map[m].flag.nobaseexp || !map[m].flag.nojobexp) //Gives Exp + ) { //Experience calculation. + int bonus = 100; //Bonus on top of your share (common to all attackers). + if (md->sc.data[SC_RICHMANKIM]) + bonus += md->sc.data[SC_RICHMANKIM]->val2; + if(sd) { + temp = status_get_class(&md->bl); + if(sd->sc.data[SC_MIRACLE]) i = 2; //All mobs are Star Targets + else + ARR_FIND(0, MAX_PC_FEELHATE, i, temp == sd->hate_mob[i] && + (battle_config.allow_skill_without_day || sg_info[i].day_func())); + if(i<MAX_PC_FEELHATE && (temp=pc_checkskill(sd,sg_info[i].bless_id))) + bonus += (i==2?20:10)*temp; + } + if(battle_config.mobs_level_up && md->level > md->db->lv) // [Valaris] + bonus += (md->level-md->db->lv)*battle_config.mobs_level_up_exp_rate; + + for(i = 0; i < DAMAGELOG_SIZE && md->dmglog[i].id; i++) + { + int flag=1,zeny=0; + unsigned int base_exp, job_exp; + double per; //Your share of the mob's exp + + if (!tmpsd[i]) continue; + + if (!battle_config.exp_calc_type && md->tdmg) + //jAthena's exp formula based on total damage. + per = (double)md->dmglog[i].dmg/(double)md->tdmg; + else { + //eAthena's exp formula based on max hp. + per = (double)md->dmglog[i].dmg/(double)status->max_hp; + if (per > 2) per = 2; // prevents unlimited exp gain + } + + if (count>1 && battle_config.exp_bonus_attacker) { + //Exp bonus per additional attacker. + if (count > battle_config.exp_bonus_max_attacker) + count = battle_config.exp_bonus_max_attacker; + per += per*((count-1)*battle_config.exp_bonus_attacker)/100.; + } + + // change experience for different sized monsters [Valaris] + if (battle_config.mob_size_influence) + { + if (md->special_state.size == SZ_MEDIUM) + per /= 2.; + else if (md->special_state.size == SZ_BIG) + per *= 2.; + } + + if( md->dmglog[i].flag == MDLF_PET ) + per *= battle_config.pet_attack_exp_rate/100.; + + if(battle_config.zeny_from_mobs && md->level) { + // zeny calculation moblv + random moblv [Valaris] + zeny=(int) ((md->level+rnd()%md->level)*per*bonus/100.); + if(md->db->mexp > 0) + zeny*=rnd()%250; + } + + if (map[m].flag.nobaseexp || !md->db->base_exp) + base_exp = 0; + else + base_exp = (unsigned int)cap_value(md->db->base_exp * per * bonus/100. * map[m].bexp/100., 1, UINT_MAX); + + if (map[m].flag.nojobexp || !md->db->job_exp || md->dmglog[i].flag == MDLF_HOMUN) //Homun earned job-exp is always lost. + job_exp = 0; + else + job_exp = (unsigned int)cap_value(md->db->job_exp * per * bonus/100. * map[m].jexp/100., 1, UINT_MAX); + + if ((temp = tmpsd[i]->status.party_id)>0 /*&& !md->dmglog[i].flag == MDLF_HOMUN*/) //Homun-done damage (flag 1) is given to party + { + int j; + for(j=0;j<pnum && pt[j].id!=temp;j++); //Locate party. + + if(j==pnum){ //Possibly add party. + pt[pnum].p = party_search(temp); + if(pt[pnum].p && pt[pnum].p->party.exp) + { + pt[pnum].id=temp; + pt[pnum].base_exp=base_exp; + pt[pnum].job_exp=job_exp; + pt[pnum].zeny=zeny; // zeny share [Valaris] + pnum++; + flag=0; + } + }else{ //Add to total + if (pt[j].base_exp > UINT_MAX - base_exp) + pt[j].base_exp=UINT_MAX; + else + pt[j].base_exp+=base_exp; + + if (pt[j].job_exp > UINT_MAX - job_exp) + pt[j].job_exp=UINT_MAX; + else + pt[j].job_exp+=job_exp; + + pt[j].zeny+=zeny; // zeny share [Valaris] + flag=0; + } + } + if(flag) { + if(base_exp && md->dmglog[i].flag == MDLF_HOMUN) //tmpsd[i] is null if it has no homunc. + merc_hom_gainexp(tmpsd[i]->hd, base_exp); + if(base_exp || job_exp) + { + if( md->dmglog[i].flag != MDLF_PET || battle_config.pet_attack_exp_to_master ) { +#ifdef RENEWAL_EXP + int rate = pc_level_penalty_mod(tmpsd[i], md, 1); + base_exp = (unsigned int)cap_value(base_exp * rate / 100, 1, UINT_MAX); + job_exp = (unsigned int)cap_value(job_exp * rate / 100, 1, UINT_MAX); +#endif + pc_gainexp(tmpsd[i], &md->bl, base_exp, job_exp, false); + } + } + if(zeny) // zeny from mobs [Valaris] + pc_getzeny(tmpsd[i], zeny, LOG_TYPE_PICKDROP_MONSTER, NULL); + } + } + + for(i=0;i<pnum;i++) //Party share. + party_exp_share(pt[i].p, &md->bl, pt[i].base_exp,pt[i].job_exp,pt[i].zeny); + + } //End EXP giving. + + if( !(type&1) && !map[m].flag.nomobloot && !md->state.rebirth && ( + !md->special_state.ai || //Non special mob + battle_config.alchemist_summon_reward == 2 || //All summoned give drops + (md->special_state.ai==2 && battle_config.alchemist_summon_reward == 1) //Marine Sphere Drops items. + ) ) + { // Item Drop + struct item_drop_list *dlist = ers_alloc(item_drop_list_ers, struct item_drop_list); + struct item_drop *ditem; + struct item_data* it = NULL; + int drop_rate; +#ifdef RENEWAL_DROP + int drop_modifier = mvp_sd ? pc_level_penalty_mod(mvp_sd, md, 2) : + second_sd ? pc_level_penalty_mod(second_sd, md, 2): + third_sd ? pc_level_penalty_mod(third_sd, md, 2) : + 100;/* no player was attached, we dont use any modifier (100 = rates are not touched) */ +#endif + dlist->m = md->bl.m; + dlist->x = md->bl.x; + dlist->y = md->bl.y; + dlist->first_charid = (mvp_sd ? mvp_sd->status.char_id : 0); + dlist->second_charid = (second_sd ? second_sd->status.char_id : 0); + dlist->third_charid = (third_sd ? third_sd->status.char_id : 0); + dlist->item = NULL; + + for (i = 0; i < MAX_MOB_DROP; i++) + { + if (md->db->dropitem[i].nameid <= 0) + continue; + if ( !(it = itemdb_exists(md->db->dropitem[i].nameid)) ) + continue; + drop_rate = md->db->dropitem[i].p; + if (drop_rate <= 0) { + if (battle_config.drop_rate0item) + continue; + drop_rate = 1; + } + + // change drops depending on monsters size [Valaris] + if (battle_config.mob_size_influence) + { + if (md->special_state.size == SZ_MEDIUM && drop_rate >= 2) + drop_rate /= 2; + else if( md->special_state.size == SZ_BIG) + drop_rate *= 2; + } + + if (src) { + //Drops affected by luk as a fixed increase [Valaris] + if (battle_config.drops_by_luk) + drop_rate += status_get_luk(src)*battle_config.drops_by_luk/100; + //Drops affected by luk as a % increase [Skotlex] + if (battle_config.drops_by_luk2) + drop_rate += (int)(0.5+drop_rate*status_get_luk(src)*battle_config.drops_by_luk2/10000.); + } + if (sd && battle_config.pk_mode && + (int)(md->level - sd->status.base_level) >= 20) + drop_rate = (int)(drop_rate*1.25); // pk_mode increase drops if 20 level difference [Valaris] + + // Increase drop rate if user has SC_ITEMBOOST + if (sd && sd->sc.data[SC_ITEMBOOST]) // now rig the drop rate to never be over 90% unless it is originally >90%. + drop_rate = max(drop_rate,cap_value((int)(0.5+drop_rate*(sd->sc.data[SC_ITEMBOOST]->val1)/100.),0,9000)); +#ifdef RENEWAL_DROP + if( drop_modifier != 100 ) { + drop_rate = drop_rate * drop_modifier / 100; + if( drop_rate < 1 ) + drop_rate = 1; + } +#endif + // attempt to drop the item + if (rnd() % 10000 >= drop_rate) + continue; + + if( mvp_sd && it->type == IT_PETEGG ) { + pet_create_egg(mvp_sd, md->db->dropitem[i].nameid); + continue; + } + + ditem = mob_setdropitem(md->db->dropitem[i].nameid, 1); + + //A Rare Drop Global Announce by Lupus + if( mvp_sd && drop_rate <= battle_config.rare_drop_announce ) { + char message[128]; + sprintf (message, msg_txt(541), mvp_sd->status.name, md->name, it->jname, (float)drop_rate/100); + //MSG: "'%s' won %s's %s (chance: %0.02f%%)" + intif_broadcast(message,strlen(message)+1,0); + } + // Announce first, or else ditem will be freed. [Lance] + // By popular demand, use base drop rate for autoloot code. [Skotlex] + mob_item_drop(md, dlist, ditem, 0, md->db->dropitem[i].p, homkillonly); + } + + // Ore Discovery [Celest] + if (sd == mvp_sd && pc_checkskill(sd,BS_FINDINGORE)>0 && battle_config.finding_ore_rate/10 >= rnd()%10000) { + ditem = mob_setdropitem(itemdb_searchrandomid(IG_FINDINGORE), 1); + mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly); + } + + if(sd) { + // process script-granted extra drop bonuses + int itemid = 0; + for (i = 0; i < ARRAYLENGTH(sd->add_drop) && (sd->add_drop[i].id || sd->add_drop[i].group); i++) + { + if ( sd->add_drop[i].race == -md->class_ || + ( sd->add_drop[i].race > 0 && ( + sd->add_drop[i].race & (1<<status->race) || + sd->add_drop[i].race & (1<<(status->mode&MD_BOSS?RC_BOSS:RC_NONBOSS)) + ))) + { + //check if the bonus item drop rate should be multiplied with mob level/10 [Lupus] + if(sd->add_drop[i].rate < 0) { + //it's negative, then it should be multiplied. e.g. for Mimic,Myst Case Cards, etc + // rate = base_rate * (mob_level/10) + 1 + drop_rate = -sd->add_drop[i].rate*(md->level/10)+1; + drop_rate = cap_value(drop_rate, battle_config.item_drop_adddrop_min, battle_config.item_drop_adddrop_max); + if (drop_rate > 10000) drop_rate = 10000; + } + else + //it's positive, then it goes as it is + drop_rate = sd->add_drop[i].rate; + + if (rnd()%10000 >= drop_rate) + continue; + itemid = (sd->add_drop[i].id > 0) ? sd->add_drop[i].id : itemdb_searchrandomid(sd->add_drop[i].group); + mob_item_drop(md, dlist, mob_setdropitem(itemid,1), 0, drop_rate, homkillonly); + } + } + + // process script-granted zeny bonus (get_zeny_num) [Skotlex] + if( sd->bonus.get_zeny_num && rnd()%100 < sd->bonus.get_zeny_rate ) { + i = sd->bonus.get_zeny_num > 0 ? sd->bonus.get_zeny_num : -md->level * sd->bonus.get_zeny_num; + if (!i) i = 1; + pc_getzeny(sd, 1+rnd()%i, LOG_TYPE_PICKDROP_MONSTER, NULL); + } + } + + // process items looted by the mob + if(md->lootitem) { + for(i = 0; i < md->lootitem_count; i++) + mob_item_drop(md, dlist, mob_setlootitem(&md->lootitem[i]), 1, 10000, homkillonly); + } + if (dlist->item) //There are drop items. + add_timer(tick + (!battle_config.delay_battle_damage?500:0), mob_delay_item_drop, 0, (intptr_t)dlist); + else //No drops + ers_free(item_drop_list_ers, dlist); + } else if (md->lootitem && md->lootitem_count) { //Loot MUST drop! + struct item_drop_list *dlist = ers_alloc(item_drop_list_ers, struct item_drop_list); + dlist->m = md->bl.m; + dlist->x = md->bl.x; + dlist->y = md->bl.y; + dlist->first_charid = (mvp_sd ? mvp_sd->status.char_id : 0); + dlist->second_charid = (second_sd ? second_sd->status.char_id : 0); + dlist->third_charid = (third_sd ? third_sd->status.char_id : 0); + dlist->item = NULL; + for(i = 0; i < md->lootitem_count; i++) + mob_item_drop(md, dlist, mob_setlootitem(&md->lootitem[i]), 1, 10000, homkillonly); + add_timer(tick + (!battle_config.delay_battle_damage?500:0), mob_delay_item_drop, 0, (intptr_t)dlist); + } + + if(mvp_sd && md->db->mexp > 0 && !md->special_state.ai) { + int log_mvp[2] = {0}; + unsigned int mexp; + struct item item; + double exp; + + //mapflag: noexp check [Lorky] + if (map[m].flag.nobaseexp || type&2) + exp =1; + else { + exp = md->db->mexp; + if (count > 1) + exp += exp*(battle_config.exp_bonus_attacker*(count-1))/100.; //[Gengar] + } + + mexp = (unsigned int)cap_value(exp, 1, UINT_MAX); + + clif_mvp_effect(mvp_sd); + clif_mvp_exp(mvp_sd,mexp); + pc_gainexp(mvp_sd, &md->bl, mexp,0, false); + log_mvp[1] = mexp; + + if( !(map[m].flag.nomvploot || type&1) ) { + /* pose them randomly in the list -- so on 100% drop servers it wont always drop the same item */ + int mdrop_id[MAX_MVP_DROP]; + int mdrop_p[MAX_MVP_DROP]; + + memset(&mdrop_id,0,MAX_MVP_DROP*sizeof(int)); + + for(i = 0; i < MAX_MVP_DROP; i++) { + while( 1 ) { + int va = rand()%MAX_MVP_DROP; + if( !mdrop_id[va] || !md->db->mvpitem[i].nameid ) { + mdrop_id[va] = md->db->mvpitem[i].nameid; + mdrop_p[va] = md->db->mvpitem[i].p; + break; + } + } + } + + for(i = 0; i < MAX_MVP_DROP; i++) { + if(mdrop_id[i] <= 0) + continue; + if(!itemdb_exists(mdrop_id[i])) + continue; + + temp = mdrop_p[i]; + if(temp <= 0 && !battle_config.drop_rate0item) + temp = 1; + if(temp <= rnd()%10000+1) //if ==0, then it doesn't drop + continue; + + memset(&item,0,sizeof(item)); + item.nameid=mdrop_id[i]; + item.identify= itemdb_isidentified(item.nameid); + clif_mvp_item(mvp_sd,item.nameid); + log_mvp[0] = item.nameid; + + //A Rare MVP Drop Global Announce by Lupus + if(temp<=battle_config.rare_drop_announce) { + struct item_data *i_data; + char message[128]; + i_data = itemdb_exists(item.nameid); + sprintf (message, msg_txt(541), mvp_sd->status.name, md->name, i_data->jname, temp/100.); + //MSG: "'%s' won %s's %s (chance: %0.02f%%)" + intif_broadcast(message,strlen(message)+1,0); + } + + if((temp = pc_additem(mvp_sd,&item,1,LOG_TYPE_PICKDROP_PLAYER)) != 0) { + clif_additem(mvp_sd,0,0,temp); + map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd->status.char_id,(second_sd?second_sd->status.char_id:0),(third_sd?third_sd->status.char_id:0),1); + } + + //Logs items, MVP prizes [Lupus] + log_pick_mob(md, LOG_TYPE_MVP, -1, &item); + break; + } + } + + log_mvpdrop(mvp_sd, md->class_, log_mvp); + } + + if (type&2 && !sd && md->class_ == MOBID_EMPERIUM) + //Emperium destroyed by script. Discard mvp character. [Skotlex] + mvp_sd = NULL; + + rebirth = ( md->sc.data[SC_KAIZEL] || (md->sc.data[SC_REBIRTH] && !md->state.rebirth) ); + if( !rebirth ) { // Only trigger event on final kill + md->status.hp = 0; //So that npc_event invoked functions KNOW that mob is dead + if( src ) { + switch( src->type ) { + case BL_PET: sd = ((TBL_PET*)src)->msd; break; + case BL_HOM: sd = ((TBL_HOM*)src)->master; break; + case BL_MER: sd = ((TBL_MER*)src)->master; break; + case BL_ELEM: sd = ((TBL_ELEM*)src)->master; break; + } + } + + if( sd ) { + if( sd->mission_mobid == md->class_) { //TK_MISSION [Skotlex] + if( ++sd->mission_count >= 100 && (temp = mob_get_random_id(0, 0xE, sd->status.base_level)) ) { + pc_addfame(sd, 1); + sd->mission_mobid = temp; + pc_setglobalreg(sd,"TK_MISSION_ID", temp); + sd->mission_count = 0; + clif_mission_info(sd, temp, 0); + } + pc_setglobalreg(sd,"TK_MISSION_COUNT", sd->mission_count); + } + + if( sd->status.party_id ) + map_foreachinrange(quest_update_objective_sub,&md->bl,AREA_SIZE,BL_PC,sd->status.party_id,md->class_); + else if( sd->avail_quests ) + quest_update_objective(sd, md->class_); + + if( sd->md && src && src->type != BL_HOM && mob_db(md->class_)->lv > sd->status.base_level/2 ) + mercenary_kills(sd->md); + } + + if( md->npc_event[0] && !md->state.npc_killmonster ) { + if( sd && battle_config.mob_npc_event_type ) { + pc_setparam(sd, SP_KILLERRID, sd->bl.id); + npc_event(sd,md->npc_event,0); + } else if( mvp_sd ) { + pc_setparam(mvp_sd, SP_KILLERRID, sd?sd->bl.id:0); + npc_event(mvp_sd,md->npc_event,0); + } else + npc_event_do(md->npc_event); + } else if( mvp_sd && !md->state.npc_killmonster ) { + pc_setparam(mvp_sd, SP_KILLEDRID, md->class_); + npc_script_event(mvp_sd, NPCE_KILLNPC); // PCKillNPC [Lance] + } + + md->status.hp = 1; + } + + if(md->deletetimer != INVALID_TIMER) { + delete_timer(md->deletetimer,mob_timer_delete); + md->deletetimer = INVALID_TIMER; + } + /** + * Only loops if necessary (e.g. a poring would never need to loop) + **/ + if( md->can_summon ) + mob_deleteslave(md); + + map_freeblock_unlock(); + + if( !rebirth ) { + + if( pcdb_checkid(md->vd->class_) ) {//Player mobs are not removed automatically by the client. + /* first we set them dead, then we delay the outsight effect */ + clif_clearunit_area(&md->bl,CLR_DEAD); + clif_clearunit_delayed(&md->bl, CLR_OUTSIGHT,tick+3000); + } else + /** + * We give the client some time to breath and this allows it to display anything it'd like with the dead corpose + * For example, this delay allows it to display soul drain effect + **/ + clif_clearunit_delayed(&md->bl, CLR_DEAD, tick+250); + + } + + if(!md->spawn) //Tell status_damage to remove it from memory. + return 5; // Note: Actually, it's 4. Oh well... + + // MvP tomb [GreenBox] + if (battle_config.mvp_tomb_enabled && md->spawn->state.boss) + mvptomb_create(md, mvp_sd ? mvp_sd->status.name : NULL, time(NULL)); + + if( !rebirth ) + mob_setdelayspawn(md); //Set respawning. + return 3; //Remove from map. +} + +void mob_revive(struct mob_data *md, unsigned int hp) +{ + unsigned int tick = gettick(); + md->state.skillstate = MSS_IDLE; + md->last_thinktime = tick; + md->next_walktime = tick+rnd()%50+5000; + md->last_linktime = tick; + md->last_pcneartime = 0; + memset(md->dmglog, 0, sizeof(md->dmglog)); // Reset the damage done on the rebirthed monster, otherwise will grant full exp + damage done. [Valaris] + md->tdmg = 0; + if (!md->bl.prev) + map_addblock(&md->bl); + clif_spawn(&md->bl); + skill_unit_move(&md->bl,tick,1); + mobskill_use(md, tick, MSC_SPAWN); + if (battle_config.show_mob_info&3) + clif_charnameack (0, &md->bl); +} + +int mob_guardian_guildchange(struct mob_data *md) +{ + struct guild *g; + nullpo_ret(md); + + if (!md->guardian_data) + return 0; + + if (md->guardian_data->castle->guild_id == 0) + { //Castle with no owner? Delete the guardians. + if (md->class_ == MOBID_EMPERIUM) + { //But don't delete the emperium, just clear it's guild-data + md->guardian_data->guild_id = 0; + md->guardian_data->emblem_id = 0; + md->guardian_data->guild_name[0] = '\0'; + } else { + if (md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS && md->guardian_data->castle->guardian[md->guardian_data->number].visible) + guild_castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number, 0); + unit_free(&md->bl,CLR_OUTSIGHT); //Remove guardian. + } + return 0; + } + + g = guild_search(md->guardian_data->castle->guild_id); + if (g == NULL) + { //Properly remove guardian info from Castle data. + ShowError("mob_guardian_guildchange: New Guild (id %d) does not exists!\n", md->guardian_data->guild_id); + if (md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS) + guild_castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number, 0); + unit_free(&md->bl,CLR_OUTSIGHT); + return 0; + } + + md->guardian_data->guild_id = g->guild_id; + md->guardian_data->emblem_id = g->emblem_id; + md->guardian_data->guardup_lv = guild_checkskill(g,GD_GUARDUP); + memcpy(md->guardian_data->guild_name, g->name, NAME_LENGTH); + + return 1; +} + +/*========================================== + * Pick a random class for the mob + *------------------------------------------*/ +int mob_random_class (int *value, size_t count) +{ + nullpo_ret(value); + + // no count specified, look into the array manually, but take only max 5 elements + if (count < 1) { + count = 0; + while(count < 5 && mobdb_checkid(value[count])) count++; + if(count < 1) // nothing found + return 0; + } else { + // check if at least the first value is valid + if(mobdb_checkid(value[0]) == 0) + return 0; + } + //Pick a random value, hoping it exists. [Skotlex] + return mobdb_checkid(value[rnd()%count]); +} + +/*========================================== + * Change mob base class + *------------------------------------------*/ +int mob_class_change (struct mob_data *md, int class_) +{ + unsigned int tick = gettick(); + int i, c, hp_rate; + + nullpo_ret(md); + + if( md->bl.prev == NULL ) + return 0; + + //Disable class changing for some targets... + if (md->guardian_data) + return 0; //Guardians/Emperium + + if( mob_is_treasure(md) ) + return 0; //Treasure Boxes + + if( md->special_state.ai > 1 ) + return 0; //Marine Spheres and Floras. + + if( mob_is_clone(md->class_) ) + return 0; //Clones + + if( md->class_ == class_ ) + return 0; //Nothing to change. + + hp_rate = get_percentage(md->status.hp, md->status.max_hp); + md->class_ = class_; + md->db = mob_db(class_); + if (battle_config.override_mob_names==1) + memcpy(md->name,md->db->name,NAME_LENGTH); + else + memcpy(md->name,md->db->jname,NAME_LENGTH); + + mob_stop_attack(md); + mob_stop_walking(md, 0); + unit_skillcastcancel(&md->bl, 0); + status_set_viewdata(&md->bl, class_); + clif_mob_class_change(md,md->vd->class_); + status_calc_mob(md, 1); + md->ud.state.speed_changed = 1; //Speed change update. + + if (battle_config.monster_class_change_recover) { + memset(md->dmglog, 0, sizeof(md->dmglog)); + md->tdmg = 0; + } else { + md->status.hp = md->status.max_hp*hp_rate/100; + if(md->status.hp < 1) md->status.hp = 1; + } + + for(i=0,c=tick-MOB_MAX_DELAY;i<MAX_MOBSKILL;i++) + md->skilldelay[i] = c; + + if(md->lootitem == NULL && md->db->status.mode&MD_LOOTER) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + + //Targets should be cleared no morph + md->target_id = md->attacked_id = 0; + + //Need to update name display. + clif_charnameack(0, &md->bl); + status_change_end(&md->bl,SC_KEEPING,INVALID_TIMER); + return 0; +} + +/*========================================== + * mob heal, update display hp info of mob for players + *------------------------------------------*/ +void mob_heal(struct mob_data *md,unsigned int heal) +{ + if (battle_config.show_mob_info&3) + clif_charnameack (0, &md->bl); +} + +/*========================================== + * Added by RoVeRT + *------------------------------------------*/ +int mob_warpslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md=(struct mob_data *)bl; + struct block_list *master; + short x,y,range=0; + master = va_arg(ap, struct block_list*); + range = va_arg(ap, int); + + if(md->master_id!=master->id) + return 0; + + map_search_freecell(master, 0, &x, &y, range, range, 0); + unit_warp(&md->bl, master->m, x, y,CLR_RESPAWN); + return 1; +} + +/*========================================== + * Added by RoVeRT + * Warps slaves. Range is the area around the master that they can + * appear in randomly. + *------------------------------------------*/ +int mob_warpslave(struct block_list *bl, int range) +{ + if (range < 1) + range = 1; //Min range needed to avoid crashes and stuff. [Skotlex] + + return map_foreachinmap(mob_warpslave_sub, bl->m, BL_MOB, bl, range); +} + +/*========================================== + * Counts slave sub, curently checking if mob master is the given ID. + *------------------------------------------*/ +int mob_countslave_sub(struct block_list *bl,va_list ap) +{ + int id; + struct mob_data *md; + id=va_arg(ap,int); + + md = (struct mob_data *)bl; + if( md->master_id==id ) + return 1; + return 0; +} + +/*========================================== + * Counts the number of slaves a mob has on the map. + *------------------------------------------*/ +int mob_countslave(struct block_list *bl) +{ + return map_foreachinmap(mob_countslave_sub, bl->m, BL_MOB,bl->id); +} + +/*========================================== + * Summons amount slaves contained in the value[5] array using round-robin. [adapted by Skotlex] + *------------------------------------------*/ +int mob_summonslave(struct mob_data *md2,int *value,int amount,uint16 skill_id) +{ + struct mob_data *md; + struct spawn_data data; + int count = 0,k=0,hp_rate=0; + + nullpo_ret(md2); + nullpo_ret(value); + + memset(&data, 0, sizeof(struct spawn_data)); + data.m = md2->bl.m; + data.x = md2->bl.x; + data.y = md2->bl.y; + data.num = 1; + data.state.size = md2->special_state.size; + data.state.ai = md2->special_state.ai; + + if(mobdb_checkid(value[0]) == 0) + return 0; + /** + * Flags this monster is able to summon; saves a worth amount of memory upon deletion + **/ + md2->can_summon = 1; + + while(count < 5 && mobdb_checkid(value[count])) count++; + if(count < 1) return 0; + if (amount > 0 && amount < count) { //Do not start on 0, pick some random sub subset [Skotlex] + k = rnd()%count; + amount+=k; //Increase final value by same amount to preserve total number to summon. + } + + if (!battle_config.monster_class_change_recover && + (skill_id == NPC_TRANSFORMATION || skill_id == NPC_METAMORPHOSIS)) + hp_rate = get_percentage(md2->status.hp, md2->status.max_hp); + + for(;k<amount;k++) { + short x,y; + data.class_ = value[k%count]; //Summon slaves in round-robin fashion. [Skotlex] + if (mobdb_checkid(data.class_) == 0) + continue; + + if (map_search_freecell(&md2->bl, 0, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 0)) { + data.x = x; + data.y = y; + } else { + data.x = md2->bl.x; + data.y = md2->bl.y; + } + + //These two need to be loaded from the db for each slave. + if(battle_config.override_mob_names==1) + strcpy(data.name,"--en--"); + else + strcpy(data.name,"--ja--"); + + if (!mob_parse_dataset(&data)) + continue; + + md= mob_spawn_dataset(&data); + if(skill_id == NPC_SUMMONSLAVE){ + md->master_id=md2->bl.id; + md->special_state.ai = md2->special_state.ai; + } + mob_spawn(md); + + if (hp_rate) //Scale HP + md->status.hp = md->status.max_hp*hp_rate/100; + + //Inherit the aggressive mode of the master. + if (battle_config.slaves_inherit_mode && md->master_id) + { + switch (battle_config.slaves_inherit_mode) { + case 1: //Always aggressive + if (!(md->status.mode&MD_AGGRESSIVE)) + sc_start4(&md->bl, SC_MODECHANGE, 100,1,0, MD_AGGRESSIVE, 0, 0); + break; + case 2: //Always passive + if (md->status.mode&MD_AGGRESSIVE) + sc_start4(&md->bl, SC_MODECHANGE, 100,1,0, 0, MD_AGGRESSIVE, 0); + break; + default: //Copy master. + if (md2->status.mode&MD_AGGRESSIVE) + sc_start4(&md->bl, SC_MODECHANGE, 100,1,0, MD_AGGRESSIVE, 0, 0); + else + sc_start4(&md->bl, SC_MODECHANGE, 100,1,0, 0, MD_AGGRESSIVE, 0); + break; + } + } + + clif_skill_nodamage(&md->bl,&md->bl,skill_id,amount,1); + } + + return 0; +} + +/*========================================== + * MOBskill lookup (get skillindex through skill_id) + * Returns -1 if not found. + *------------------------------------------*/ +int mob_skill_id2skill_idx(int class_,uint16 skill_id) +{ + int i, max = mob_db(class_)->maxskill; + struct mob_skill *ms=mob_db(class_)->skill; + + if(ms==NULL) + return -1; + + ARR_FIND( 0, max, i, ms[i].skill_id == skill_id ); + return ( i < max ) ? i : -1; +} + +/*========================================== + * Friendly Mob whose HP is decreasing by a nearby MOB is looked for. + *------------------------------------------*/ +int mob_getfriendhprate_sub(struct block_list *bl,va_list ap) +{ + int min_rate, max_rate,rate; + struct block_list **fr; + struct mob_data *md; + + md = va_arg(ap,struct mob_data *); + min_rate=va_arg(ap,int); + max_rate=va_arg(ap,int); + fr=va_arg(ap,struct block_list **); + + if( md->bl.id == bl->id && !(battle_config.mob_ai&0x10)) + return 0; + + if ((*fr) != NULL) //A friend was already found. + return 0; + + if (battle_check_target(&md->bl,bl,BCT_ENEMY)>0) + return 0; + + rate = get_percentage(status_get_hp(bl), status_get_max_hp(bl)); + + if (rate >= min_rate && rate <= max_rate) + (*fr) = bl; + return 1; +} +static struct block_list *mob_getfriendhprate(struct mob_data *md,int min_rate,int max_rate) +{ + struct block_list *fr=NULL; + int type = BL_MOB; + + nullpo_retr(NULL, md); + + if (md->special_state.ai) //Summoned creatures. [Skotlex] + type = BL_PC; + + map_foreachinrange(mob_getfriendhprate_sub, &md->bl, 8, type,md,min_rate,max_rate,&fr); + return fr; +} +/*========================================== + * Check hp rate of its master + *------------------------------------------*/ +struct block_list *mob_getmasterhpltmaxrate(struct mob_data *md,int rate) +{ + if( md && md->master_id > 0 ) + { + struct block_list *bl = map_id2bl(md->master_id); + if( bl && get_percentage(status_get_hp(bl), status_get_max_hp(bl)) < rate ) + return bl; + } + + return NULL; +} +/*========================================== + * What a status state suits by nearby MOB is looked for. + *------------------------------------------*/ +int mob_getfriendstatus_sub(struct block_list *bl,va_list ap) +{ + int cond1,cond2; + struct mob_data **fr, *md, *mmd; + int flag=0; + + nullpo_ret(bl); + nullpo_ret(md=(struct mob_data *)bl); + nullpo_ret(mmd=va_arg(ap,struct mob_data *)); + + if( mmd->bl.id == bl->id && !(battle_config.mob_ai&0x10) ) + return 0; + + if (battle_check_target(&mmd->bl,bl,BCT_ENEMY)>0) + return 0; + cond1=va_arg(ap,int); + cond2=va_arg(ap,int); + fr=va_arg(ap,struct mob_data **); + if( cond2==-1 ){ + int j; + for(j=SC_COMMON_MIN;j<=SC_COMMON_MAX && !flag;j++){ + if ((flag=(md->sc.data[j] != NULL))) //Once an effect was found, break out. [Skotlex] + break; + } + }else + flag=( md->sc.data[cond2] != NULL ); + if( flag^( cond1==MSC_FRIENDSTATUSOFF ) ) + (*fr)=md; + + return 0; +} + +struct mob_data *mob_getfriendstatus(struct mob_data *md,int cond1,int cond2) +{ + struct mob_data* fr = NULL; + nullpo_ret(md); + + map_foreachinrange(mob_getfriendstatus_sub, &md->bl, 8,BL_MOB, md,cond1,cond2,&fr); + return fr; +} + +/*========================================== + * Skill use judging + *------------------------------------------*/ +int mobskill_use(struct mob_data *md, unsigned int tick, int event) +{ + struct mob_skill *ms; + struct block_list *fbl = NULL; //Friend bl, which can either be a BL_PC or BL_MOB depending on the situation. [Skotlex] + struct block_list *bl; + struct mob_data *fmd = NULL; + int i,j,n; + + nullpo_ret(md); + nullpo_ret(ms = md->db->skill); + + if (!battle_config.mob_skill_rate || md->ud.skilltimer != INVALID_TIMER || !md->db->maxskill) + return 0; + + if (event == -1 && DIFF_TICK(md->ud.canact_tick, tick) > 0) + return 0; //Skill act delay only affects non-event skills. + + //Pick a starting position and loop from that. + i = battle_config.mob_ai&0x100?rnd()%md->db->maxskill:0; + for (n = 0; n < md->db->maxskill; i++, n++) { + int c2, flag = 0; + + if (i == md->db->maxskill) + i = 0; + + if (DIFF_TICK(tick, md->skilldelay[i]) < ms[i].delay) + continue; + + c2 = ms[i].cond2; + + if (ms[i].state != md->state.skillstate) { + if (md->state.skillstate != MSS_DEAD && (ms[i].state == MSS_ANY || + (ms[i].state == MSS_ANYTARGET && md->target_id && md->state.skillstate != MSS_LOOT) + )) //ANYTARGET works with any state as long as there's a target. [Skotlex] + ; + else + continue; + } + if (rnd() % 10000 > ms[i].permillage) //Lupus (max value = 10000) + continue; + + if (ms[i].cond1 == event) + flag = 1; //Trigger skill. + else if (ms[i].cond1 == MSC_SKILLUSED) + flag = ((event & 0xffff) == MSC_SKILLUSED && ((event >> 16) == c2 || c2 == 0)); + else if(event == -1){ + //Avoid entering on defined events to avoid "hyper-active skill use" due to the overflow of calls to this function in battle. + switch (ms[i].cond1) + { + case MSC_ALWAYS: + flag = 1; break; + case MSC_MYHPLTMAXRATE: // HP< maxhp% + flag = get_percentage(md->status.hp, md->status.max_hp); + flag = (flag <= c2); + break; + case MSC_MYHPINRATE: + flag = get_percentage(md->status.hp, md->status.max_hp); + flag = (flag >= c2 && flag <= ms[i].val[0]); + break; + case MSC_MYSTATUSON: // status[num] on + case MSC_MYSTATUSOFF: // status[num] off + if (!md->sc.count) { + flag = 0; + } else if (ms[i].cond2 == -1) { + for (j = SC_COMMON_MIN; j <= SC_COMMON_MAX; j++) + if ((flag = (md->sc.data[j]!=NULL)) != 0) + break; + } else { + flag = (md->sc.data[ms[i].cond2]!=NULL); + } + flag ^= (ms[i].cond1 == MSC_MYSTATUSOFF); break; + case MSC_FRIENDHPLTMAXRATE: // friend HP < maxhp% + flag = ((fbl = mob_getfriendhprate(md, 0, ms[i].cond2)) != NULL); break; + case MSC_FRIENDHPINRATE : + flag = ((fbl = mob_getfriendhprate(md, ms[i].cond2, ms[i].val[0])) != NULL); break; + case MSC_FRIENDSTATUSON: // friend status[num] on + case MSC_FRIENDSTATUSOFF: // friend status[num] off + flag = ((fmd = mob_getfriendstatus(md, ms[i].cond1, ms[i].cond2)) != NULL); break; + case MSC_SLAVELT: // slave < num + flag = (mob_countslave(&md->bl) < c2 ); break; + case MSC_ATTACKPCGT: // attack pc > num + flag = (unit_counttargeted(&md->bl) > c2); break; + case MSC_SLAVELE: // slave <= num + flag = (mob_countslave(&md->bl) <= c2 ); break; + case MSC_ATTACKPCGE: // attack pc >= num + flag = (unit_counttargeted(&md->bl) >= c2); break; + case MSC_AFTERSKILL: + flag = (md->ud.skill_id == c2); break; + case MSC_RUDEATTACKED: + flag = (md->state.attacked_count >= RUDE_ATTACKED_COUNT); + if (flag) md->state.attacked_count = 0; //Rude attacked count should be reset after the skill condition is met. Thanks to Komurka [Skotlex] + break; + case MSC_MASTERHPLTMAXRATE: + flag = ((fbl = mob_getmasterhpltmaxrate(md, ms[i].cond2)) != NULL); break; + case MSC_MASTERATTACKED: + flag = (md->master_id > 0 && (fbl=map_id2bl(md->master_id)) && unit_counttargeted(fbl) > 0); break; + case MSC_ALCHEMIST: + flag = (md->state.alchemist); + break; + } + } + + if (!flag) + continue; //Skill requisite failed to be fulfilled. + + //Execute skill + if (skill_get_casttype(ms[i].skill_id) == CAST_GROUND) + { //Ground skill. + short x, y; + switch (ms[i].target) { + case MST_RANDOM: //Pick a random enemy within skill range. + bl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md), + skill_get_range2(&md->bl, ms[i].skill_id, ms[i].skill_lv)); + break; + case MST_TARGET: + case MST_AROUND5: + case MST_AROUND6: + case MST_AROUND7: + case MST_AROUND8: + bl = map_id2bl(md->target_id); + break; + case MST_MASTER: + bl = &md->bl; + if (md->master_id) + bl = map_id2bl(md->master_id); + if (bl) //Otherwise, fall through. + break; + case MST_FRIEND: + bl = fbl?fbl:(fmd?&fmd->bl:&md->bl); + break; + default: + bl = &md->bl; + break; + } + if (!bl) continue; + + x = bl->x; + y = bl->y; + // Look for an area to cast the spell around... + if (ms[i].target >= MST_AROUND1 || ms[i].target >= MST_AROUND5) { + j = ms[i].target >= MST_AROUND1? + (ms[i].target-MST_AROUND1) +1: + (ms[i].target-MST_AROUND5) +1; + map_search_freecell(&md->bl, md->bl.m, &x, &y, j, j, 3); + } + md->skill_idx = i; + map_freeblock_lock(); + if( !battle_check_range(&md->bl,bl,skill_get_range2(&md->bl, ms[i].skill_id,ms[i].skill_lv)) || + !unit_skilluse_pos2(&md->bl, x, y,ms[i].skill_id, ms[i].skill_lv,ms[i].casttime, ms[i].cancel) ) + { + map_freeblock_unlock(); + continue; + } + } else { + //Targetted skill + switch (ms[i].target) { + case MST_RANDOM: //Pick a random enemy within skill range. + bl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md), + skill_get_range2(&md->bl, ms[i].skill_id, ms[i].skill_lv)); + break; + case MST_TARGET: + bl = map_id2bl(md->target_id); + break; + case MST_MASTER: + bl = &md->bl; + if (md->master_id) + bl = map_id2bl(md->master_id); + if (bl) //Otherwise, fall through. + break; + case MST_FRIEND: + if (fbl) { + bl = fbl; + break; + } else if (fmd) { + bl = &fmd->bl; + break; + } // else fall through + default: + bl = &md->bl; + break; + } + if (!bl) continue; + + md->skill_idx = i; + map_freeblock_lock(); + if( !battle_check_range(&md->bl,bl,skill_get_range2(&md->bl, ms[i].skill_id,ms[i].skill_lv)) || + !unit_skilluse_id2(&md->bl, bl->id,ms[i].skill_id, ms[i].skill_lv,ms[i].casttime, ms[i].cancel) ) + { + map_freeblock_unlock(); + continue; + } + } + //Skill used. Post-setups... + if ( ms[ i ].msg_id ){ //Display color message [SnakeDrak] + struct mob_chat *mc = mob_chat(ms[i].msg_id); + char temp[CHAT_SIZE_MAX]; + char name[NAME_LENGTH]; + snprintf(name, sizeof name,"%s", md->name); + strtok(name, "#"); // discard extra name identifier if present [Daegaladh] + snprintf(temp, sizeof temp,"%s : %s", name, mc->msg); + clif_messagecolor(&md->bl, mc->color, temp); + } + if(!(battle_config.mob_ai&0x200)) { //pass on delay to same skill. + for (j = 0; j < md->db->maxskill; j++) + if (md->db->skill[j].skill_id == ms[i].skill_id) + md->skilldelay[j]=tick; + } else + md->skilldelay[i]=tick; + map_freeblock_unlock(); + return 1; + } + //No skill was used. + md->skill_idx = -1; + return 0; +} +/*========================================== + * Skill use event processing + *------------------------------------------*/ +int mobskill_event(struct mob_data *md, struct block_list *src, unsigned int tick, int flag) +{ + int target_id, res = 0; + + if(md->bl.prev == NULL || md->status.hp <= 0) + return 0; + + target_id = md->target_id; + if (!target_id || battle_config.mob_changetarget_byskill) + md->target_id = src->id; + + if (flag == -1) + res = mobskill_use(md, tick, MSC_CASTTARGETED); + else if ((flag&0xffff) == MSC_SKILLUSED) + res = mobskill_use(md, tick, flag); + else if (flag&BF_SHORT) + res = mobskill_use(md, tick, MSC_CLOSEDATTACKED); + else if (flag&BF_LONG && !(flag&BF_MAGIC)) //Long-attacked should not include magic. + res = mobskill_use(md, tick, MSC_LONGRANGEATTACKED); + + if (!res) + //Restore previous target only if skill condition failed to trigger. [Skotlex] + md->target_id = target_id; + //Otherwise check if the target is an enemy, and unlock if needed. + else if (battle_check_target(&md->bl, src, BCT_ENEMY) <= 0) + md->target_id = target_id; + + return res; +} + +// Player cloned mobs. [Valaris] +int mob_is_clone(int class_) +{ + if(class_ < MOB_CLONE_START || class_ > MOB_CLONE_END) + return 0; + if (mob_db(class_) == mob_dummy) + return 0; + return class_; +} + +//Flag values: +//&1: Set special ai (fight mobs, not players) +//If mode is not passed, a default aggressive mode is used. +//If master_id is passed, clone is attached to him. +//Returns: ID of newly crafted copy. +int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, int mode, int flag, unsigned int duration) +{ + int class_; + int i,j,inf,skill_id, fd; + struct mob_data *md; + struct mob_skill *ms; + struct mob_db* db; + struct status_data *status; + + nullpo_ret(sd); + + if(pc_isdead(sd) && master_id && flag&1) + return 0; + + ARR_FIND( MOB_CLONE_START, MOB_CLONE_END, class_, mob_db_data[class_] == NULL ); + if(class_ >= MOB_CLONE_END) + return 0; + + db = mob_db_data[class_]=(struct mob_db*)aCalloc(1, sizeof(struct mob_db)); + status = &db->status; + strcpy(db->sprite,sd->status.name); + strcpy(db->name,sd->status.name); + strcpy(db->jname,sd->status.name); + db->lv=status_get_lv(&sd->bl); + memcpy(status, &sd->base_status, sizeof(struct status_data)); + status->rhw.atk2= status->dex + status->rhw.atk + status->rhw.atk2; //Max ATK + status->rhw.atk = status->dex; //Min ATK + if (status->lhw.atk) { + status->lhw.atk2= status->dex + status->lhw.atk + status->lhw.atk2; //Max ATK + status->lhw.atk = status->dex; //Min ATK + } + if (mode) //User provided mode. + status->mode = mode; + else if (flag&1) //Friendly Character, remove looting. + status->mode &= ~MD_LOOTER; + status->hp = status->max_hp; + status->sp = status->max_sp; + memcpy(&db->vd, &sd->vd, sizeof(struct view_data)); + db->base_exp=1; + db->job_exp=1; + db->range2=AREA_SIZE; //Let them have the same view-range as players. + db->range3=AREA_SIZE; //Min chase of a screen. + db->option=sd->sc.option; + + //Skill copy [Skotlex] + ms = &db->skill[0]; + + /** + * We temporarily disable sd's fd so it doesn't receive the messages from skill_check_condition_castbegin + **/ + fd = sd->fd; + sd->fd = 0; + + //Go Backwards to give better priority to advanced skills. + for (i=0,j = MAX_SKILL_TREE-1;j>=0 && i< MAX_MOBSKILL ;j--) { + skill_id = skill_tree[pc_class2idx(sd->status.class_)][j].id; + if (!skill_id || sd->status.skill[skill_id].lv < 1 || + (skill_get_inf2(skill_id)&(INF2_WEDDING_SKILL|INF2_GUILD_SKILL)) || + skill_get_nocast(skill_id)&16 + ) + continue; + //Normal aggressive mob, disable skills that cannot help them fight + //against players (those with flags UF_NOMOB and UF_NOPC are specific + //to always aid players!) [Skotlex] + if (!(flag&1) && + skill_get_unit_id(skill_id, 0) && + skill_get_unit_flag(skill_id)&(UF_NOMOB|UF_NOPC)) + continue; + /** + * The clone should be able to cast the skill (e.g. have the required weapon) bugreport:5299) + **/ + if( !skill_check_condition_castbegin(sd,skill_id,sd->status.skill[skill_id].lv) ) + continue; + + memset (&ms[i], 0, sizeof(struct mob_skill)); + ms[i].skill_id = skill_id; + ms[i].skill_lv = sd->status.skill[skill_id].lv; + ms[i].state = MSS_ANY; + ms[i].permillage = 500*battle_config.mob_skill_rate/100; //Default chance of all skills: 5% + ms[i].emotion = -1; + ms[i].cancel = 0; + ms[i].casttime = skill_castfix(&sd->bl,skill_id, ms[i].skill_lv); + ms[i].delay = 5000+skill_delayfix(&sd->bl,skill_id, ms[i].skill_lv); + + inf = skill_get_inf(skill_id); + if (inf&INF_ATTACK_SKILL) { + ms[i].target = MST_TARGET; + ms[i].cond1 = MSC_ALWAYS; + if (skill_get_range(skill_id, ms[i].skill_lv) > 3) + ms[i].state = MSS_ANYTARGET; + else + ms[i].state = MSS_BERSERK; + } else if(inf&INF_GROUND_SKILL) { + if (skill_get_inf2(skill_id)&INF2_TRAP) { //Traps! + ms[i].state = MSS_IDLE; + ms[i].target = MST_AROUND2; + ms[i].delay = 60000; + } else if (skill_get_unit_target(skill_id) == BCT_ENEMY) { //Target Enemy + ms[i].state = MSS_ANYTARGET; + ms[i].target = MST_TARGET; + ms[i].cond1 = MSC_ALWAYS; + } else { //Target allies + ms[i].target = MST_FRIEND; + ms[i].cond1 = MSC_FRIENDHPLTMAXRATE; + ms[i].cond2 = 95; + } + } else if (inf&INF_SELF_SKILL) { + if (skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF) { //auto-select target skill. + ms[i].target = MST_TARGET; + ms[i].cond1 = MSC_ALWAYS; + if (skill_get_range(skill_id, ms[i].skill_lv) > 3) { + ms[i].state = MSS_ANYTARGET; + } else { + ms[i].state = MSS_BERSERK; + } + } else { //Self skill + ms[i].target = MST_SELF; + ms[i].cond1 = MSC_MYHPLTMAXRATE; + ms[i].cond2 = 90; + ms[i].permillage = 2000; + //Delay: Remove the stock 5 secs and add half of the support time. + ms[i].delay += -5000 +(skill_get_time(skill_id, ms[i].skill_lv) + skill_get_time2(skill_id, ms[i].skill_lv))/2; + if (ms[i].delay < 5000) + ms[i].delay = 5000; //With a minimum of 5 secs. + } + } else if (inf&INF_SUPPORT_SKILL) { + ms[i].target = MST_FRIEND; + ms[i].cond1 = MSC_FRIENDHPLTMAXRATE; + ms[i].cond2 = 90; + if (skill_id == AL_HEAL) + ms[i].permillage = 5000; //Higher skill rate usage for heal. + else if (skill_id == ALL_RESURRECTION) + ms[i].cond2 = 1; + //Delay: Remove the stock 5 secs and add half of the support time. + ms[i].delay += -5000 +(skill_get_time(skill_id, ms[i].skill_lv) + skill_get_time2(skill_id, ms[i].skill_lv))/2; + if (ms[i].delay < 2000) + ms[i].delay = 2000; //With a minimum of 2 secs. + + if (i+1 < MAX_MOBSKILL) { //duplicate this so it also triggers on self. + memcpy(&ms[i+1], &ms[i], sizeof(struct mob_skill)); + db->maxskill = ++i; + ms[i].target = MST_SELF; + ms[i].cond1 = MSC_MYHPLTMAXRATE; + } + } else { + switch (skill_id) { //Certain Special skills that are passive, and thus, never triggered. + case MO_TRIPLEATTACK: + case TF_DOUBLE: + case GS_CHAINACTION: + ms[i].state = MSS_BERSERK; + ms[i].target = MST_TARGET; + ms[i].cond1 = MSC_ALWAYS; + ms[i].permillage = skill_id==MO_TRIPLEATTACK?(3000-ms[i].skill_lv*100):(ms[i].skill_lv*500); + ms[i].delay -= 5000; //Remove the added delay as these could trigger on "all hits". + break; + default: //Untreated Skill + continue; + } + } + if (battle_config.mob_skill_rate!= 100) + ms[i].permillage = ms[i].permillage*battle_config.mob_skill_rate/100; + if (battle_config.mob_skill_delay != 100) + ms[i].delay = ms[i].delay*battle_config.mob_skill_delay/100; + + db->maxskill = ++i; + } + + /** + * We grant the session it's fd value back. + **/ + sd->fd = fd; + + //Finally, spawn it. + md = mob_once_spawn_sub(&sd->bl, m, x, y, "--en--", class_, event, SZ_SMALL, AI_NONE); + if (!md) return 0; //Failed? + + md->special_state.clone = 1; + + if (master_id || flag || duration) { //Further manipulate crafted char. + if (flag&1) //Friendly Character + md->special_state.ai = AI_ATTACK; + if (master_id) //Attach to Master + md->master_id = master_id; + if (duration) //Auto Delete after a while. + { + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + duration, mob_timer_delete, md->bl.id, 0); + } + } + + mob_spawn(md); + + return md->bl.id; +} + +int mob_clone_delete(struct mob_data *md) +{ + const int class_ = md->class_; + if (class_ >= MOB_CLONE_START && class_ < MOB_CLONE_END + && mob_db_data[class_]!=NULL) { + aFree(mob_db_data[class_]); + mob_db_data[class_]=NULL; + //Clear references to the db + md->db = mob_dummy; + md->vd = NULL; + return 1; + } + return 0; +} + +// +// Initialization +// +/*========================================== + * Since un-setting [ mob ] up was used, it is an initial provisional value setup. + *------------------------------------------*/ +static int mob_makedummymobdb(int class_) +{ + if (mob_dummy != NULL) + { + if (mob_db(class_) == mob_dummy) + return 1; //Using the mob_dummy data already. [Skotlex] + if (class_ > 0 && class_ <= MAX_MOB_DB) + { //Remove the mob data so that it uses the dummy data instead. + aFree(mob_db_data[class_]); + mob_db_data[class_] = NULL; + } + return 0; + } + //Initialize dummy data. + mob_dummy = (struct mob_db*)aCalloc(1, sizeof(struct mob_db)); //Initializing the dummy mob. + sprintf(mob_dummy->sprite,"DUMMY"); + sprintf(mob_dummy->name,"Dummy"); + sprintf(mob_dummy->jname,"Dummy"); + mob_dummy->lv=1; + mob_dummy->status.max_hp=1000; + mob_dummy->status.max_sp=1; + mob_dummy->status.rhw.range=1; + mob_dummy->status.rhw.atk=7; + mob_dummy->status.rhw.atk2=10; + mob_dummy->status.str=1; + mob_dummy->status.agi=1; + mob_dummy->status.vit=1; + mob_dummy->status.int_=1; + mob_dummy->status.dex=6; + mob_dummy->status.luk=2; + mob_dummy->status.speed=300; + mob_dummy->status.adelay=1000; + mob_dummy->status.amotion=500; + mob_dummy->status.dmotion=500; + mob_dummy->base_exp=2; + mob_dummy->job_exp=1; + mob_dummy->range2=10; + mob_dummy->range3=10; + + return 0; +} + +//Adjusts the drop rate of item according to the criteria given. [Skotlex] +static unsigned int mob_drop_adjust(int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max) +{ + double rate = baserate; + + if (battle_config.logarithmic_drops && rate_adjust > 0 && rate_adjust != 100 && baserate > 0) //Logarithmic drops equation by Ishizu-Chan + //Equation: Droprate(x,y) = x * (5 - log(x)) ^ (ln(y) / ln(5)) + //x is the normal Droprate, y is the Modificator. + rate = rate * pow((5.0 - log10(rate)), (log(rate_adjust/100.) / log(5.0))) + 0.5; + else + //Classical linear rate adjustment. + rate = rate * rate_adjust/100; + + return (unsigned int)cap_value(rate,rate_min,rate_max); +} + +/** + * Check if global item drop rate is overriden for given item + * in db/mob_item_ratio.txt + * @param nameid ID of the item + * @param mob_id ID of the monster + * @param rate_adjust pointer to store ratio if found + */ +static void item_dropratio_adjust(int nameid, int mob_id, int *rate_adjust) +{ + int i; + if( item_drop_ratio_db[nameid] ) { + if( item_drop_ratio_db[nameid]->mob_id[0] ) { // only for listed mobs + ARR_FIND(0, MAX_ITEMRATIO_MOBS, i, item_drop_ratio_db[nameid]->mob_id[i] == mob_id); + if(i < MAX_ITEMRATIO_MOBS) // found + *rate_adjust = item_drop_ratio_db[nameid]->drop_ratio; + } + else // for all mobs + *rate_adjust = item_drop_ratio_db[nameid]->drop_ratio; + } +} + +/*========================================== + * processes one mobdb entry + *------------------------------------------*/ +static bool mob_parse_dbrow(char** str) +{ + struct mob_db *db, entry; + struct status_data *status; + int class_, i, k; + double exp, maxhp; + struct mob_data data; + + class_ = atoi(str[0]); + + if (class_ <= 1000 || class_ > MAX_MOB_DB) { + ShowError("mob_parse_dbrow: Invalid monster ID %d, must be in range %d-%d.\n", class_, 1000, MAX_MOB_DB); + return false; + } + if (pcdb_checkid(class_)) { + ShowError("mob_parse_dbrow: Invalid monster ID %d, reserved for player classes.\n", class_); + return false; + } + + if (class_ >= MOB_CLONE_START && class_ < MOB_CLONE_END) { + ShowError("mob_parse_dbrow: Invalid monster ID %d. Range %d-%d is reserved for player clones. Please increase MAX_MOB_DB (%d).\n", class_, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB); + return false; + } + + memset(&entry, 0, sizeof(entry)); + + db = &entry; + status = &db->status; + + db->vd.class_ = class_; + safestrncpy(db->sprite, str[1], sizeof(db->sprite)); + safestrncpy(db->jname, str[2], sizeof(db->jname)); + safestrncpy(db->name, str[3], sizeof(db->name)); + db->lv = atoi(str[4]); + db->lv = cap_value(db->lv, 1, USHRT_MAX); + status->max_hp = atoi(str[5]); + status->max_sp = atoi(str[6]); + + exp = (double)atoi(str[7]) * (double)battle_config.base_exp_rate / 100.; + db->base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); + + exp = (double)atoi(str[8]) * (double)battle_config.job_exp_rate / 100.; + db->job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); + + status->rhw.range = atoi(str[9]); + status->rhw.atk = atoi(str[10]); + status->rhw.atk2 = atoi(str[11]); + status->def = atoi(str[12]); + status->mdef = atoi(str[13]); + status->str = atoi(str[14]); + status->agi = atoi(str[15]); + status->vit = atoi(str[16]); + status->int_ = atoi(str[17]); + status->dex = atoi(str[18]); + status->luk = atoi(str[19]); + //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex] + if (status->str < 1) status->str = 1; + if (status->agi < 1) status->agi = 1; + if (status->vit < 1) status->vit = 1; + if (status->int_< 1) status->int_= 1; + if (status->dex < 1) status->dex = 1; + if (status->luk < 1) status->luk = 1; + + db->range2 = atoi(str[20]); + db->range3 = atoi(str[21]); + if (battle_config.view_range_rate != 100) { + db->range2 = db->range2 * battle_config.view_range_rate / 100; + if (db->range2 < 1) + db->range2 = 1; + } + if (battle_config.chase_range_rate != 100) { + db->range3 = db->range3 * battle_config.chase_range_rate / 100; + if (db->range3 < db->range2) + db->range3 = db->range2; + } + + status->size = atoi(str[22]); + status->race = atoi(str[23]); + + i = atoi(str[24]); //Element + status->def_ele = i%10; + status->ele_lv = i/20; + if (status->def_ele >= ELE_MAX) { + ShowError("mob_parse_dbrow: Invalid element type %d for monster ID %d (max=%d).\n", status->def_ele, class_, ELE_MAX-1); + return false; + } + if (status->ele_lv < 1 || status->ele_lv > 4) { + ShowError("mob_parse_dbrow: Invalid element level %d for monster ID %d, must be in range 1-4.\n", status->ele_lv, class_); + return false; + } + + status->mode = (int)strtol(str[25], NULL, 0); + if (!battle_config.monster_active_enable) + status->mode &= ~MD_AGGRESSIVE; + + status->speed = atoi(str[26]); + status->aspd_rate = 1000; + i = atoi(str[27]); + status->adelay = cap_value(i, battle_config.monster_max_aspd*2, 4000); + i = atoi(str[28]); + status->amotion = cap_value(i, battle_config.monster_max_aspd, 2000); + //If the attack animation is longer than the delay, the client crops the attack animation! + //On aegis there is no real visible effect of having a recharge-time less than amotion anyway. + if (status->adelay < status->amotion) + status->adelay = status->amotion; + status->dmotion = atoi(str[29]); + if(battle_config.monster_damage_delay_rate != 100) + status->dmotion = status->dmotion * battle_config.monster_damage_delay_rate / 100; + + // Fill in remaining status data by using a dummy monster. + data.bl.type = BL_MOB; + data.level = db->lv; + memcpy(&data.status, status, sizeof(struct status_data)); + status_calc_misc(&data.bl, status, db->lv); + + // MVP EXP Bonus: MEXP + // Some new MVP's MEXP multipled by high exp-rate cause overflow. [LuzZza] + exp = (double)atoi(str[30]) * (double)battle_config.mvp_exp_rate / 100.; + db->mexp = (unsigned int)cap_value(exp, 0, UINT_MAX); + + //Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex] + maxhp = (double)status->max_hp; + if (db->mexp > 0) { //Mvp + if (battle_config.mvp_hp_rate != 100) + maxhp = maxhp * (double)battle_config.mvp_hp_rate / 100.; + } else //Normal mob + if (battle_config.monster_hp_rate != 100) + maxhp = maxhp * (double)battle_config.monster_hp_rate / 100.; + + status->max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX); + if(status->max_sp < 1) status->max_sp = 1; + + //Since mobs always respawn with full life... + status->hp = status->max_hp; + status->sp = status->max_sp; + + // MVP Drops: MVP1id,MVP1per,MVP2id,MVP2per,MVP3id,MVP3per + for(i = 0; i < MAX_MVP_DROP; i++) { + struct item_data *id; + int rate_adjust = battle_config.item_rate_mvp;; + db->mvpitem[i].nameid = atoi(str[31+i*2]); + if (!db->mvpitem[i].nameid) { + db->mvpitem[i].p = 0; //No item.... + continue; + } + item_dropratio_adjust(db->mvpitem[i].nameid, class_, &rate_adjust); + db->mvpitem[i].p = mob_drop_adjust(atoi(str[32+i*2]), rate_adjust, battle_config.item_drop_mvp_min, battle_config.item_drop_mvp_max); + + //calculate and store Max available drop chance of the MVP item + if (db->mvpitem[i].p) { + id = itemdb_search(db->mvpitem[i].nameid); + if (id->maxchance == -1 || (id->maxchance < db->mvpitem[i].p/10 + 1) ) { + //item has bigger drop chance or sold in shops + id->maxchance = db->mvpitem[i].p/10 + 1; //reduce MVP drop info to not spoil common drop rate + } + } + } + + for(i = 0; i < MAX_MOB_DROP; i++) { + int rate = 0, rate_adjust, type; + unsigned short ratemin, ratemax; + struct item_data *id; + k = 31 + MAX_MVP_DROP*2 + i*2; + db->dropitem[i].nameid = atoi(str[k]); + if (!db->dropitem[i].nameid) { + db->dropitem[i].p = 0; //No drop. + continue; + } + id = itemdb_search(db->dropitem[i].nameid); + type = id->type; + rate = atoi(str[k+1]); + if( (class_ >= 1324 && class_ <= 1363) || (class_ >= 1938 && class_ <= 1946) ) + { //Treasure box drop rates [Skotlex] + rate_adjust = battle_config.item_rate_treasure; + ratemin = battle_config.item_drop_treasure_min; + ratemax = battle_config.item_drop_treasure_max; + } + else switch (type) + { // Added suport to restrict normal drops of MVP's [Reddozen] + case IT_HEALING: + rate_adjust = (status->mode&MD_BOSS) ? battle_config.item_rate_heal_boss : battle_config.item_rate_heal; + ratemin = battle_config.item_drop_heal_min; + ratemax = battle_config.item_drop_heal_max; + break; + case IT_USABLE: + case IT_CASH: + rate_adjust = (status->mode&MD_BOSS) ? battle_config.item_rate_use_boss : battle_config.item_rate_use; + ratemin = battle_config.item_drop_use_min; + ratemax = battle_config.item_drop_use_max; + break; + case IT_WEAPON: + case IT_ARMOR: + case IT_PETARMOR: + rate_adjust = (status->mode&MD_BOSS) ? battle_config.item_rate_equip_boss : battle_config.item_rate_equip; + ratemin = battle_config.item_drop_equip_min; + ratemax = battle_config.item_drop_equip_max; + break; + case IT_CARD: + rate_adjust = (status->mode&MD_BOSS) ? battle_config.item_rate_card_boss : battle_config.item_rate_card; + ratemin = battle_config.item_drop_card_min; + ratemax = battle_config.item_drop_card_max; + break; + default: + rate_adjust = (status->mode&MD_BOSS) ? battle_config.item_rate_common_boss : battle_config.item_rate_common; + ratemin = battle_config.item_drop_common_min; + ratemax = battle_config.item_drop_common_max; + break; + } + item_dropratio_adjust(id->nameid, class_, &rate_adjust); + db->dropitem[i].p = mob_drop_adjust(rate, rate_adjust, ratemin, ratemax); + + //calculate and store Max available drop chance of the item + if( db->dropitem[i].p && (class_ < 1324 || class_ > 1363) && (class_ < 1938 || class_ > 1946) ) + { //Skip treasure chests. + if (id->maxchance == -1 || (id->maxchance < db->dropitem[i].p) ) { + id->maxchance = db->dropitem[i].p; //item has bigger drop chance or sold in shops + } + for (k = 0; k< MAX_SEARCH; k++) { + if (id->mob[k].chance <= db->dropitem[i].p) + break; + } + if (k == MAX_SEARCH) + continue; + + if (id->mob[k].id != class_) + memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); + id->mob[k].chance = db->dropitem[i].p; + id->mob[k].id = class_; + } + } + + // Finally insert monster's data into the database. + if (mob_db_data[class_] == NULL) + mob_db_data[class_] = (struct mob_db*)aCalloc(1, sizeof(struct mob_db)); + else + //Copy over spawn data + memcpy(&db->spawn, mob_db_data[class_]->spawn, sizeof(db->spawn)); + + memcpy(mob_db_data[class_], db, sizeof(struct mob_db)); + return true; +} + +/*========================================== + * mob_db.txt reading + *------------------------------------------*/ +static bool mob_readdb_sub(char* fields[], int columns, int current) +{ + return mob_parse_dbrow(fields); +} + +static void mob_readdb(void) +{ + const char* filename[] = { + DBPATH"mob_db.txt", + "mob_db2.txt" }; + int fi; + + for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) + { + char path[256]; + + if(fi > 0) + { + sprintf(path, "%s/%s", db_path, filename[fi]); + if(!exists(path)) + { + continue; + } + } + + sv_readdb(db_path, filename[fi], ',', 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP, 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP, -1, &mob_readdb_sub); + } +} + +/*========================================== + * mob_db table reading + *------------------------------------------*/ +static int mob_read_sqldb(void) +{ + const char* mob_db_name[] = { mob_db_db, mob_db2_db }; + int fi; + + for( fi = 0; fi < ARRAYLENGTH(mob_db_name); ++fi ) { + uint32 lines = 0, count = 0; + + // retrieve all rows from the mob database + if( SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", mob_db_name[fi]) ) { + Sql_ShowDebug(mmysql_handle); + continue; + } + + // process rows one by one + while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) { + // wrap the result into a TXT-compatible format + char line[1024]; + char* str[31+2*MAX_MVP_DROP+2*MAX_MOB_DROP]; + char* p; + int i; + + lines++; + for(i = 0, p = line; i < 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP; i++) + { + char* data; + size_t len; + Sql_GetData(mmysql_handle, i, &data, &len); + + strcpy(p, data); + str[i] = p; + p+= len + 1; + } + + if (!mob_parse_dbrow(str)) + continue; + + count++; + } + + // free the query result + Sql_FreeResult(mmysql_handle); + + ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, mob_db_name[fi]); + } + return 0; +} + +/*========================================== + * MOB display graphic change data reading + *------------------------------------------*/ +static bool mob_readdb_mobavail(char* str[], int columns, int current) +{ + int class_, k; + + class_=atoi(str[0]); + + if(mob_db(class_) == mob_dummy) // invalid class (probably undefined in db) + { + ShowWarning("mob_readdb_mobavail: Unknown mob id %d.\n", class_); + return false; + } + + k=atoi(str[1]); + + memset(&mob_db_data[class_]->vd, 0, sizeof(struct view_data)); + mob_db_data[class_]->vd.class_=k; + + //Player sprites + if(pcdb_checkid(k) && columns==12) { + mob_db_data[class_]->vd.sex=atoi(str[2]); + mob_db_data[class_]->vd.hair_style=atoi(str[3]); + mob_db_data[class_]->vd.hair_color=atoi(str[4]); + mob_db_data[class_]->vd.weapon=atoi(str[5]); + mob_db_data[class_]->vd.shield=atoi(str[6]); + mob_db_data[class_]->vd.head_top=atoi(str[7]); + mob_db_data[class_]->vd.head_mid=atoi(str[8]); + mob_db_data[class_]->vd.head_bottom=atoi(str[9]); + mob_db_data[class_]->option=atoi(str[10])&~(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE); + mob_db_data[class_]->vd.cloth_color=atoi(str[11]); // Monster player dye option - Valaris + } + else if(columns==3) + mob_db_data[class_]->vd.head_bottom=atoi(str[2]); // mob equipment [Valaris] + else if( columns != 2 ) + return false; + + return true; +} + +/*========================================== + * Reading of random monster data + *------------------------------------------*/ +static int mob_read_randommonster(void) +{ + FILE *fp; + char line[1024]; + char *str[10],*p; + int i,j; + const char* mobfile[] = { + DBPATH"mob_branch.txt", + DBPATH"mob_poring.txt", + DBPATH"mob_boss.txt", + "mob_pouch.txt", + "mob_classchange.txt"}; + + memset(&summon, 0, sizeof(summon)); + + for( i = 0; i < ARRAYLENGTH(mobfile) && i < MAX_RANDOMMONSTER; i++ ) + { + mob_db_data[0]->summonper[i] = 1002; // Default fallback value, in case the database does not provide one + sprintf(line, "%s/%s", db_path, mobfile[i]); + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n",line); + return -1; + } + while(fgets(line, sizeof(line), fp)) + { + int class_; + if(line[0] == '/' && line[1] == '/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL || str[2]==NULL) + continue; + + class_ = atoi(str[0]); + if(mob_db(class_) == mob_dummy) + continue; + mob_db_data[class_]->summonper[i]=atoi(str[2]); + if (i) { + if( summon[i].qty < ARRAYLENGTH(summon[i].class_) ) //MvPs + summon[i].class_[summon[i].qty++] = class_; + else { + ShowDebug("Can't store more random mobs from %s, increase size of mob.c:summon variable!\n", mobfile[i]); + break; + } + } + } + if (i && !summon[i].qty) + { //At least have the default here. + summon[i].class_[0] = mob_db_data[0]->summonper[i]; + summon[i].qty = 1; + } + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n",mobfile[i]); + } + return 0; +} + +/*========================================== + * processes one mob_chat_db entry [SnakeDrak] + * @param last_msg_id ensures that only one error message per mob id is printed + *------------------------------------------*/ +static bool mob_parse_row_chatdb(char** str, const char* source, int line, int* last_msg_id) +{ + char* msg; + struct mob_chat *ms; + int msg_id; + size_t len; + + msg_id = atoi(str[0]); + + if (msg_id <= 0 || msg_id > MAX_MOB_CHAT) + { + if (msg_id != *last_msg_id) { + ShowError("mob_chat: Invalid chat ID: %d at %s, line %d\n", msg_id, source, line); + *last_msg_id = msg_id; + } + return false; + } + + if (mob_chat_db[msg_id] == NULL) + mob_chat_db[msg_id] = (struct mob_chat*)aCalloc(1, sizeof (struct mob_chat)); + + ms = mob_chat_db[msg_id]; + //MSG ID + ms->msg_id=msg_id; + //Color + ms->color=strtoul(str[1],NULL,0); + //Message + msg = str[2]; + len = strlen(msg); + + while( len && ( msg[len-1]=='\r' || msg[len-1]=='\n' ) ) + {// find EOL to strip + len--; + } + + if(len>(CHAT_SIZE_MAX-1)) + { + if (msg_id != *last_msg_id) { + ShowError("mob_chat: readdb: Message too long! Line %d, id: %d\n", line, msg_id); + *last_msg_id = msg_id; + } + return false; + } + else if( !len ) + { + ShowWarning("mob_parse_row_chatdb: Empty message for id %d.\n", msg_id); + return false; + } + + msg[len] = 0; // strip previously found EOL + strncpy(ms->msg, str[2], CHAT_SIZE_MAX); + + return true; +} + +/*========================================== + * mob_chat_db.txt reading [SnakeDrak] + *-------------------------------------------------------------------------*/ +static void mob_readchatdb(void) +{ + char arc[]="mob_chat_db.txt"; + uint32 lines=0, count=0; + char line[1024], path[256]; + int i, tmp=0; + FILE *fp; + sprintf(path, "%s/%s", db_path, arc); + fp=fopen(path, "r"); + if(fp == NULL) + { + ShowWarning("mob_readchatdb: File not found \"%s\", skipping.\n", path); + return; + } + + while(fgets(line, sizeof(line), fp)) + { + char *str[3], *p, *np; + int j=0; + + lines++; + if(line[0] == '/' && line[1] == '/') + continue; + memset(str, 0, sizeof(str)); + + p=line; + while(ISSPACE(*p)) + ++p; + if(*p == '\0') + continue;// empty line + for(i = 0; i <= 2; i++) + { + str[i] = p; + if(i<2 && (np = strchr(p, ',')) != NULL) { + *np = '\0'; p = np + 1; j++; + } + } + + if( j < 2 || str[2]==NULL) + { + ShowError("mob_readchatdb: Insufficient number of fields for skill at %s, line %d\n", arc, lines); + continue; + } + + if( !mob_parse_row_chatdb(str, path, lines, &tmp) ) + continue; + + count++; + } + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", arc); +} + +/*========================================== + * processes one mob_skill_db entry + *------------------------------------------*/ +static bool mob_parse_row_mobskilldb(char** str, int columns, int current) +{ + static const struct { + char str[32]; + enum MobSkillState id; + } state[] = { + { "any", MSS_ANY }, //All states except Dead + { "idle", MSS_IDLE }, + { "walk", MSS_WALK }, + { "loot", MSS_LOOT }, + { "dead", MSS_DEAD }, + { "attack", MSS_BERSERK }, //Retaliating attack + { "angry", MSS_ANGRY }, //Preemptive attack (aggressive mobs) + { "chase", MSS_RUSH }, //Chase escaping target + { "follow", MSS_FOLLOW }, //Preemptive chase (aggressive mobs) + { "anytarget",MSS_ANYTARGET }, //Berserk+Angry+Rush+Follow + }; + static const struct { + char str[32]; + int id; + } cond1[] = { + { "always", MSC_ALWAYS }, + { "myhpltmaxrate", MSC_MYHPLTMAXRATE }, + { "myhpinrate", MSC_MYHPINRATE }, + { "friendhpltmaxrate", MSC_FRIENDHPLTMAXRATE }, + { "friendhpinrate", MSC_FRIENDHPINRATE }, + { "mystatuson", MSC_MYSTATUSON }, + { "mystatusoff", MSC_MYSTATUSOFF }, + { "friendstatuson", MSC_FRIENDSTATUSON }, + { "friendstatusoff", MSC_FRIENDSTATUSOFF }, + { "attackpcgt", MSC_ATTACKPCGT }, + { "attackpcge", MSC_ATTACKPCGE }, + { "slavelt", MSC_SLAVELT }, + { "slavele", MSC_SLAVELE }, + { "closedattacked", MSC_CLOSEDATTACKED }, + { "longrangeattacked", MSC_LONGRANGEATTACKED }, + { "skillused", MSC_SKILLUSED }, + { "afterskill", MSC_AFTERSKILL }, + { "casttargeted", MSC_CASTTARGETED }, + { "rudeattacked", MSC_RUDEATTACKED }, + { "masterhpltmaxrate", MSC_MASTERHPLTMAXRATE }, + { "masterattacked", MSC_MASTERATTACKED }, + { "alchemist", MSC_ALCHEMIST }, + { "onspawn", MSC_SPAWN }, + }, cond2[] ={ + { "anybad", -1 }, + { "stone", SC_STONE }, + { "freeze", SC_FREEZE }, + { "stun", SC_STUN }, + { "sleep", SC_SLEEP }, + { "poison", SC_POISON }, + { "curse", SC_CURSE }, + { "silence", SC_SILENCE }, + { "confusion", SC_CONFUSION }, + { "blind", SC_BLIND }, + { "hiding", SC_HIDING }, + { "sight", SC_SIGHT }, + }, target[] = { + { "target", MST_TARGET }, + { "randomtarget", MST_RANDOM }, + { "self", MST_SELF }, + { "friend", MST_FRIEND }, + { "master", MST_MASTER }, + { "around5", MST_AROUND5 }, + { "around6", MST_AROUND6 }, + { "around7", MST_AROUND7 }, + { "around8", MST_AROUND8 }, + { "around1", MST_AROUND1 }, + { "around2", MST_AROUND2 }, + { "around3", MST_AROUND3 }, + { "around4", MST_AROUND4 }, + { "around", MST_AROUND }, + }; + static int last_mob_id = 0; // ensures that only one error message per mob id is printed + + struct mob_skill *ms, gms; + int mob_id; + int i =0, j, tmp; + + mob_id = atoi(str[0]); + + if (mob_id > 0 && mob_db(mob_id) == mob_dummy) + { + if (mob_id != last_mob_id) { + ShowError("mob_parse_row_mobskilldb: Non existant Mob id %d\n", mob_id); + last_mob_id = mob_id; + } + return false; + } + if( strcmp(str[1],"clear")==0 ){ + if (mob_id < 0) + return false; + memset(mob_db_data[mob_id]->skill,0,sizeof(struct mob_skill)); + mob_db_data[mob_id]->maxskill=0; + return true; + } + + if (mob_id < 0) + { //Prepare global skill. [Skotlex] + memset(&gms, 0, sizeof (struct mob_skill)); + ms = &gms; + } else { + ARR_FIND( 0, MAX_MOBSKILL, i, (ms = &mob_db_data[mob_id]->skill[i])->skill_id == 0 ); + if( i == MAX_MOBSKILL ) + { + if (mob_id != last_mob_id) { + ShowError("mob_parse_row_mobskilldb: Too many skills for monster %d[%s]\n", mob_id, mob_db_data[mob_id]->sprite); + last_mob_id = mob_id; + } + return false; + } + } + + //State + ARR_FIND( 0, ARRAYLENGTH(state), j, strcmp(str[2],state[j].str) == 0 ); + if( j < ARRAYLENGTH(state) ) + ms->state = state[j].id; + else { + ShowWarning("mob_parse_row_mobskilldb: Unrecognized state %s\n", str[2]); + ms->state = MSS_ANY; + } + + //Skill ID + j=atoi(str[3]); + if (j<=0 || j>MAX_SKILL_DB) //fixed Lupus + { + if (mob_id < 0) + ShowError("mob_parse_row_mobskilldb: Invalid Skill ID (%d) for all mobs\n", j); + else + ShowError("mob_parse_row_mobskilldb: Invalid Skill ID (%d) for mob %d (%s)\n", j, mob_id, mob_db_data[mob_id]->sprite); + return false; + } + ms->skill_id=j; + + //Skill lvl + j= atoi(str[4])<=0 ? 1 : atoi(str[4]); + ms->skill_lv= j>battle_config.mob_max_skilllvl ? battle_config.mob_max_skilllvl : j; //we strip max skill level + + //Apply battle_config modifiers to rate (permillage) and delay [Skotlex] + tmp = atoi(str[5]); + if (battle_config.mob_skill_rate != 100) + tmp = tmp*battle_config.mob_skill_rate/100; + if (tmp > 10000) + ms->permillage= 10000; + else if (!tmp && battle_config.mob_skill_rate) + ms->permillage= 1; + else + ms->permillage= tmp; + ms->casttime=atoi(str[6]); + ms->delay=atoi(str[7]); + if (battle_config.mob_skill_delay != 100) + ms->delay = ms->delay*battle_config.mob_skill_delay/100; + if (ms->delay < 0 || ms->delay > MOB_MAX_DELAY) //time overflow? + ms->delay = MOB_MAX_DELAY; + ms->cancel=atoi(str[8]); + if( strcmp(str[8],"yes")==0 ) ms->cancel=1; + + //Target + ARR_FIND( 0, ARRAYLENGTH(target), j, strcmp(str[9],target[j].str) == 0 ); + if( j < ARRAYLENGTH(target) ) + ms->target = target[j].id; + else { + ShowWarning("mob_parse_row_mobskilldb: Unrecognized target %s for %d\n", str[9], mob_id); + ms->target = MST_TARGET; + } + + //Check that the target condition is right for the skill type. [Skotlex] + if (skill_get_casttype(ms->skill_id) == CAST_GROUND) + { //Ground skill. + if (ms->target > MST_AROUND) + { + ShowWarning("mob_parse_row_mobskilldb: Wrong mob skill target for ground skill %d (%s) for %s.\n", + ms->skill_id, skill_get_name(ms->skill_id), + mob_id < 0?"all mobs":mob_db_data[mob_id]->sprite); + ms->target = MST_TARGET; + } + } else if (ms->target > MST_MASTER) { + ShowWarning("mob_parse_row_mobskilldb: Wrong mob skill target 'around' for non-ground skill %d (%s) for %s.\n", + ms->skill_id, skill_get_name(ms->skill_id), + mob_id < 0?"all mobs":mob_db_data[mob_id]->sprite); + ms->target = MST_TARGET; + } + + //Cond1 + ARR_FIND( 0, ARRAYLENGTH(cond1), j, strcmp(str[10],cond1[j].str) == 0 ); + if( j < ARRAYLENGTH(cond1) ) + ms->cond1 = cond1[j].id; + else { + ShowWarning("mob_parse_row_mobskilldb: Unrecognized condition 1 %s for %d\n", str[10], mob_id); + ms->cond1 = -1; + } + + //Cond2 + // numeric value + ms->cond2 = atoi(str[11]); + // or special constant + ARR_FIND( 0, ARRAYLENGTH(cond2), j, strcmp(str[11],cond2[j].str) == 0 ); + if( j < ARRAYLENGTH(cond2) ) + ms->cond2 = cond2[j].id; + + ms->val[0]=(int)strtol(str[12],NULL,0); + ms->val[1]=(int)strtol(str[13],NULL,0); + ms->val[2]=(int)strtol(str[14],NULL,0); + ms->val[3]=(int)strtol(str[15],NULL,0); + ms->val[4]=(int)strtol(str[16],NULL,0); + + if(ms->skill_id == NPC_EMOTION && mob_id>0 && + ms->val[1] == mob_db(mob_id)->status.mode) + { + ms->val[1] = 0; + ms->val[4] = 1; //request to return mode to normal. + } + if(ms->skill_id == NPC_EMOTION_ON && mob_id>0 && ms->val[1]) + { //Adds a mode to the mob. + //Remove aggressive mode when the new mob type is passive. + if (!(ms->val[1]&MD_AGGRESSIVE)) + ms->val[3]|=MD_AGGRESSIVE; + ms->val[2]|= ms->val[1]; //Add the new mode. + ms->val[1] = 0; //Do not "set" it. + } + + if(*str[17]) + ms->emotion=atoi(str[17]); + else + ms->emotion=-1; + + if(str[18]!=NULL && mob_chat_db[atoi(str[18])]!=NULL) + ms->msg_id=atoi(str[18]); + else + ms->msg_id=0; + + if (mob_id < 0) + { //Set this skill to ALL mobs. [Skotlex] + mob_id *= -1; + for (i = 1; i < MAX_MOB_DB; i++) + { + if (mob_db_data[i] == NULL) + continue; + if (mob_db_data[i]->status.mode&MD_BOSS) + { + if (!(mob_id&2)) //Skill not for bosses + continue; + } else + if (!(mob_id&1)) //Skill not for normal enemies. + continue; + + ARR_FIND( 0, MAX_MOBSKILL, j, mob_db_data[i]->skill[j].skill_id == 0 ); + if(j==MAX_MOBSKILL) + continue; + + memcpy (&mob_db_data[i]->skill[j], ms, sizeof(struct mob_skill)); + mob_db_data[i]->maxskill=j+1; + } + } else //Skill set on a single mob. + mob_db_data[mob_id]->maxskill=i+1; + + return true; +} + +/*========================================== + * mob_skill_db.txt reading + *------------------------------------------*/ +static void mob_readskilldb(void) { + const char* filename[] = { + DBPATH"mob_skill_db.txt", + "mob_skill_db2.txt" }; + int fi; + + if( battle_config.mob_skill_rate == 0 ) + { + ShowStatus("Mob skill use disabled. Not reading mob skills.\n"); + return; + } + + for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) + { + char path[256]; + + if(fi > 0) + { + sprintf(path, "%s/%s", db_path, filename[fi]); + if(!exists(path)) + { + continue; + } + } + + sv_readdb(db_path, filename[fi], ',', 19, 19, -1, &mob_parse_row_mobskilldb); + } +} + +/** + * mob_skill_db table reading [CalciumKid] + * not overly sure if this is all correct + * seems to work though... + */ +static int mob_read_sqlskilldb(void) +{ + const char* mob_skill_db_name[] = { mob_skill_db_db, mob_skill_db2_db }; + int fi; + + if( battle_config.mob_skill_rate == 0 ) { + ShowStatus("Mob skill use disabled. Not reading mob skills.\n"); + return 0; + } + + + for( fi = 0; fi < ARRAYLENGTH(mob_skill_db_name); ++fi ) { + uint32 lines = 0, count = 0; + + // retrieve all rows from the mob skill database + if( SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", mob_skill_db_name[fi]) ) { + Sql_ShowDebug(mmysql_handle); + continue; + } + + // process rows one by one + while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) { + // wrap the result into a TXT-compatible format + char* str[19]; + char* dummy = ""; + int i; + ++lines; + for( i = 0; i < 19; ++i ) + { + Sql_GetData(mmysql_handle, i, &str[i], NULL); + if( str[i] == NULL ) str[i] = dummy; // get rid of NULL columns + } + + if (!mob_parse_row_mobskilldb(str, 19, count)) + continue; + + count++; + } + + // free the query result + Sql_FreeResult(mmysql_handle); + + ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, mob_skill_db_name[fi]); + } + return 0; +} + +/*========================================== + * mob_race2_db.txt reading + *------------------------------------------*/ +static bool mob_readdb_race2(char* fields[], int columns, int current) +{ + int race, mobid, i; + + race = atoi(fields[0]); + + if (race < RC2_NONE || race >= RC2_MAX) + { + ShowWarning("mob_readdb_race2: Unknown race2 %d.\n", race); + return false; + } + + for(i = 1; i<columns; i++) + { + mobid = atoi(fields[i]); + if (mob_db(mobid) == mob_dummy) + { + ShowWarning("mob_readdb_race2: Unknown mob id %d for race2 %d.\n", mobid, race); + continue; + } + mob_db_data[mobid]->race2 = race; + } + return true; +} + +/** + * Read mob_item_ratio.txt + */ +static bool mob_readdb_itemratio(char* str[], int columns, int current) +{ + int nameid, ratio, i; + struct item_data *id; + + nameid = atoi(str[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_itemratio: Invalid item id %d.\n", nameid); + return false; + } + + ratio = atoi(str[1]); + + if(item_drop_ratio_db[nameid] == NULL) + item_drop_ratio_db[nameid] = (struct item_drop_ratio*)aCalloc(1, sizeof(struct item_drop_ratio)); + + item_drop_ratio_db[nameid]->drop_ratio = ratio; + for(i = 0; i < columns-2; i++) + item_drop_ratio_db[nameid]->mob_id[i] = atoi(str[i+2]); + + return true; +} + +/** + * read all mob-related databases + */ +static void mob_load(void) +{ + sv_readdb(db_path, "mob_item_ratio.txt", ',', 2, 2+MAX_ITEMRATIO_MOBS, -1, &mob_readdb_itemratio); // must be read before mobdb + mob_readchatdb(); + if (db_use_sqldbs) + { + mob_read_sqldb(); + mob_read_sqlskilldb(); + } + else + { + mob_readdb(); + mob_readskilldb(); + } + sv_readdb(db_path, "mob_avail.txt", ',', 2, 12, -1, &mob_readdb_mobavail); + mob_read_randommonster(); + sv_readdb(db_path, DBPATH"mob_race2_db.txt", ',', 2, 20, -1, &mob_readdb_race2); +} + +void mob_reload(void) { + int i; + + //Mob skills need to be cleared before re-reading them. [Skotlex] + for (i = 0; i < MAX_MOB_DB; i++) + if (mob_db_data[i]) { + memset(&mob_db_data[i]->skill,0,sizeof(mob_db_data[i]->skill)); + mob_db_data[i]->maxskill=0; + } + + // Clear item_drop_ratio_db + for (i = 0; i < MAX_ITEMDB; i++) { + if (item_drop_ratio_db[i]) { + aFree(item_drop_ratio_db[i]); + item_drop_ratio_db[i] = NULL; + } + } + + mob_load(); +} + +void mob_clear_spawninfo() +{ //Clears spawn related information for a script reload. + int i; + for (i = 0; i < MAX_MOB_DB; i++) + if (mob_db_data[i]) + memset(&mob_db_data[i]->spawn,0,sizeof(mob_db_data[i]->spawn)); +} + +/*========================================== + * Circumference initialization of mob + *------------------------------------------*/ +int do_init_mob(void) +{ //Initialize the mob database + memset(mob_db_data,0,sizeof(mob_db_data)); //Clear the array + mob_db_data[0] = (struct mob_db*)aCalloc(1, sizeof (struct mob_db)); //This mob is used for random spawns + mob_makedummymobdb(0); //The first time this is invoked, it creates the dummy mob + item_drop_ers = ers_new(sizeof(struct item_drop),"mob.c::item_drop_ers",ERS_OPT_NONE); + item_drop_list_ers = ers_new(sizeof(struct item_drop_list),"mob.c::item_drop_list_ers",ERS_OPT_NONE); + + mob_load(); + + add_timer_func_list(mob_delayspawn,"mob_delayspawn"); + add_timer_func_list(mob_delay_item_drop,"mob_delay_item_drop"); + add_timer_func_list(mob_ai_hard,"mob_ai_hard"); + add_timer_func_list(mob_ai_lazy,"mob_ai_lazy"); + add_timer_func_list(mob_timer_delete,"mob_timer_delete"); + add_timer_func_list(mob_spawn_guardian_sub,"mob_spawn_guardian_sub"); + add_timer_func_list(mob_respawn,"mob_respawn"); + add_timer_interval(gettick()+MIN_MOBTHINKTIME,mob_ai_hard,0,0,MIN_MOBTHINKTIME); + add_timer_interval(gettick()+MIN_MOBTHINKTIME*10,mob_ai_lazy,0,0,MIN_MOBTHINKTIME*10); + + return 0; +} + +/*========================================== + * Clean memory usage. + *------------------------------------------*/ +int do_final_mob(void) +{ + int i; + if (mob_dummy) + { + aFree(mob_dummy); + mob_dummy = NULL; + } + for (i = 0; i <= MAX_MOB_DB; i++) + { + if (mob_db_data[i] != NULL) + { + aFree(mob_db_data[i]); + mob_db_data[i] = NULL; + } + } + for (i = 0; i <= MAX_MOB_CHAT; i++) + { + if (mob_chat_db[i] != NULL) + { + aFree(mob_chat_db[i]); + mob_chat_db[i] = NULL; + } + } + for (i = 0; i < MAX_ITEMDB; i++) + { + if (item_drop_ratio_db[i] != NULL) + { + aFree(item_drop_ratio_db[i]); + item_drop_ratio_db[i] = NULL; + } + } + ers_destroy(item_drop_ers); + ers_destroy(item_drop_list_ers); + return 0; +} diff --git a/src/map/mob.h b/src/map/mob.h new file mode 100644 index 000000000..34e5a81c0 --- /dev/null +++ b/src/map/mob.h @@ -0,0 +1,320 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MOB_H_ +#define _MOB_H_ + +#include "../common/mmo.h" // struct item +#include "guild.h" // struct guardian_data +#include "map.h" // struct status_data, struct view_data, struct mob_skill +#include "status.h" // struct status data, struct status_change +#include "unit.h" // unit_stop_walking(), unit_stop_attack() +#include "npc.h" + +#define MAX_RANDOMMONSTER 5 + +// Change this to increase the table size in your mob_db to accomodate a larger mob database. +// Be sure to note that IDs 4001 to 4048 are reserved for advanced/baby/expanded classes. +// Notice that the last 1000 entries are used for player clones, so always set this to desired value +1000 +#define MAX_MOB_DB 4000 + +//The number of drops all mobs have and the max drop-slot that the steal skill will attempt to steal from. +#define MAX_MOB_DROP 10 +#define MAX_MVP_DROP 3 +#define MAX_STEAL_DROP 7 + +//Min time between AI executions +#define MIN_MOBTHINKTIME 100 +//Min time before mobs do a check to call nearby friends for help (or for slaves to support their master) +#define MIN_MOBLINKTIME 1000 + +//Distance that slaves should keep from their master. +#define MOB_SLAVEDISTANCE 2 + +// These define the range of available IDs for clones. [Valaris] +#define MOB_CLONE_START (MAX_MOB_DB-999) +#define MOB_CLONE_END MAX_MOB_DB + +//Used to determine default enemy type of mobs (for use in eachinrange calls) +#define DEFAULT_ENEMY_TYPE(md) (md->special_state.ai?BL_CHAR:BL_MOB|BL_PC|BL_HOM|BL_MER) + +//Externals for the status effects. [Epoque] +extern const int mob_manuk[8]; +extern const int mob_splendide[5]; + +//Mob skill states. +enum MobSkillState { + MSS_ANY = -1, + MSS_IDLE, + MSS_WALK, + MSS_LOOT, + MSS_DEAD, + MSS_BERSERK, //Aggressive mob attacking + MSS_ANGRY, //Mob retaliating from being attacked. + MSS_RUSH, //Mob following a player after being attacked. + MSS_FOLLOW, //Mob following a player without being attacked. + MSS_ANYTARGET, +}; + +enum MobDamageLogFlag +{ + MDLF_NORMAL = 0, + MDLF_HOMUN, + MDLF_PET, +}; + +enum size { + SZ_SMALL = 0, + SZ_MEDIUM, + SZ_BIG, +}; + +enum ai { + AI_NONE = 0, + AI_ATTACK, + AI_SPHERE, + AI_FLORA, + AI_ZANZOU, +}; + +struct mob_skill { + enum MobSkillState state; + uint16 skill_id,skill_lv; + short permillage; + int casttime,delay; + short cancel; + short cond1,cond2; + short target; + int val[5]; + short emotion; + unsigned short msg_id; +}; + +struct mob_chat { + unsigned short msg_id; + unsigned long color; + char msg[CHAT_SIZE_MAX]; +}; + +struct spawn_info { + unsigned short mapindex; + unsigned short qty; +}; + +struct mob_db { + char sprite[NAME_LENGTH],name[NAME_LENGTH],jname[NAME_LENGTH]; + unsigned int base_exp,job_exp; + unsigned int mexp; + short range2,range3; + short race2; // celest + unsigned short lv; + struct { int nameid,p; } dropitem[MAX_MOB_DROP]; + struct { int nameid,p; } mvpitem[MAX_MVP_DROP]; + struct status_data status; + struct view_data vd; + unsigned int option; + int summonper[MAX_RANDOMMONSTER]; + int maxskill; + struct mob_skill skill[MAX_MOBSKILL]; + struct spawn_info spawn[10]; +}; + +struct mob_data { + struct block_list bl; + struct unit_data ud; + struct view_data *vd; + struct status_data status, *base_status; //Second one is in case of leveling up mobs, or tiny/large mobs. + struct status_change sc; + struct mob_db *db; //For quick data access (saves doing mob_db(md->class_) all the time) [Skotlex] + char name[NAME_LENGTH]; + struct { + unsigned int size : 2; //Small/Big monsters. + unsigned int ai : 4; //Special ai for summoned monsters. + //0: Normal mob. + //1: Standard summon, attacks mobs. + //2: Alchemist Marine Sphere + //3: Alchemist Summon Flora + //4: Summon Zanzou + unsigned int clone : 1;/* is clone? 1:0 */ + } special_state; //Special mob information that does not needs to be zero'ed on mob respawn. + struct { + unsigned int aggressive : 1; //Signals whether the mob AI is in aggressive mode or reactive mode. [Skotlex] + unsigned int steal_coin_flag : 1; + unsigned int soul_change_flag : 1; // Celest + unsigned int alchemist: 1; + unsigned int spotted: 1; + unsigned int npc_killmonster: 1; //for new killmonster behavior + unsigned int rebirth: 1; // NPC_Rebirth used + unsigned int boss : 1; + enum MobSkillState skillstate; + unsigned char steal_flag; //number of steal tries (to prevent steal exploit on mobs with few items) [Lupus] + unsigned char attacked_count; //For rude attacked. + int provoke_flag; // Celest + } state; + struct guardian_data* guardian_data; + struct { + int id; + unsigned int dmg; + unsigned int flag : 2; //0: Normal. 1: Homunc exp. 2: Pet exp + } dmglog[DAMAGELOG_SIZE]; + struct spawn_data *spawn; //Spawn data. + int spawn_timer; //Required for Convex Mirror + struct item *lootitem; + short class_; + unsigned int tdmg; //Stores total damage given to the mob, for exp calculations. [Skotlex] + int level; + int target_id,attacked_id; + int areanpc_id; //Required in OnTouchNPC (to avoid multiple area touchs) + unsigned int bg_id; // BattleGround System + + unsigned int next_walktime,last_thinktime,last_linktime,last_pcneartime,dmgtick; + short move_fail_count; + short lootitem_count; + short min_chase; + + int deletetimer; + int master_id,master_dist; + + int8 skill_idx;// key of array + unsigned int skilldelay[MAX_MOBSKILL]; + char npc_event[EVENT_NAME_LENGTH]; + /** + * Did this monster summon something? + * Used to flag summon deletions, saves a worth amount of memory + **/ + bool can_summon; + /** + * MvP Tombstone NPC ID + **/ + int tomb_nid; +}; + + + +enum { + MST_TARGET = 0, + MST_RANDOM, //Random Target! + MST_SELF, + MST_FRIEND, + MST_MASTER, + MST_AROUND5, + MST_AROUND6, + MST_AROUND7, + MST_AROUND8, + MST_AROUND1, + MST_AROUND2, + MST_AROUND3, + MST_AROUND4, + MST_AROUND = MST_AROUND4, + + MSC_ALWAYS = 0x0000, + MSC_MYHPLTMAXRATE, + MSC_MYHPINRATE, + MSC_FRIENDHPLTMAXRATE, + MSC_FRIENDHPINRATE, + MSC_MYSTATUSON, + MSC_MYSTATUSOFF, + MSC_FRIENDSTATUSON, + MSC_FRIENDSTATUSOFF, + MSC_ATTACKPCGT, + MSC_ATTACKPCGE, + MSC_SLAVELT, + MSC_SLAVELE, + MSC_CLOSEDATTACKED, + MSC_LONGRANGEATTACKED, + MSC_AFTERSKILL, + MSC_SKILLUSED, + MSC_CASTTARGETED, + MSC_RUDEATTACKED, + MSC_MASTERHPLTMAXRATE, + MSC_MASTERATTACKED, + MSC_ALCHEMIST, + MSC_SPAWN, +}; + +// The data structures for storing delayed item drops +struct item_drop { + struct item item_data; + struct item_drop* next; +}; +struct item_drop_list { + int16 m, x, y; // coordinates + int first_charid, second_charid, third_charid; // charid's of players with higher pickup priority + struct item_drop* item; // linked list of drops +}; + +struct mob_db* mob_db(int class_); +int mobdb_searchname(const char *str); +int mobdb_searchname_array(struct mob_db** data, int size, const char *str); +int mobdb_checkid(const int id); +struct view_data* mob_get_viewdata(int class_); + +struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, + short x, short y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai); + +int mob_once_spawn(struct map_session_data* sd, int16 m, int16 x, int16 y, + const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai); + +int mob_once_spawn_area(struct map_session_data* sd, int16 m, + int16 x0, int16 y0, int16 x1, int16 y1, const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai); + +bool mob_ksprotected (struct block_list *src, struct block_list *target); + +int mob_spawn_guardian(const char* mapname, int16 x, int16 y, const char* mobname, int class_, const char* event, int guardian, bool has_index); // Spawning Guardians [Valaris] +int mob_spawn_bg(const char* mapname, int16 x, int16 y, const char* mobname, int class_, const char* event, unsigned int bg_id); +int mob_guardian_guildchange(struct mob_data *md); //Change Guardian's ownership. [Skotlex] + +int mob_randomwalk(struct mob_data *md,unsigned int tick); +int mob_warpchase(struct mob_data *md, struct block_list *target); +int mob_target(struct mob_data *md,struct block_list *bl,int dist); +int mob_unlocktarget(struct mob_data *md, unsigned int tick); +struct mob_data* mob_spawn_dataset(struct spawn_data *data); +int mob_spawn(struct mob_data *md); +int mob_delayspawn(int tid, unsigned int tick, int id, intptr_t data); +int mob_setdelayspawn(struct mob_data *md); +int mob_parse_dataset(struct spawn_data *data); +void mob_log_damage(struct mob_data *md, struct block_list *src, int damage); +void mob_damage(struct mob_data *md, struct block_list *src, int damage); +int mob_dead(struct mob_data *md, struct block_list *src, int type); +void mob_revive(struct mob_data *md, unsigned int hp); +void mob_heal(struct mob_data *md,unsigned int heal); + +#define mob_stop_walking(md, type) unit_stop_walking(&(md)->bl, type) +#define mob_stop_attack(md) unit_stop_attack(&(md)->bl) +#define mob_is_battleground(md) ( map[(md)->bl.m].flag.battleground && ((md)->class_ == MOBID_BARRICADE2 || ((md)->class_ >= MOBID_FOOD_STOR && (md)->class_ <= MOBID_PINK_CRYST)) ) +#define mob_is_gvg(md) (map[(md)->bl.m].flag.gvg_castle && ( (md)->class_ == MOBID_EMPERIUM || (md)->class_ == MOBID_BARRICADE1 || (md)->class_ == MOBID_GUARIDAN_STONE1 || (md)->class_ == MOBID_GUARIDAN_STONE2) ) +#define mob_is_treasure(md) (((md)->class_ >= MOBID_TREAS01 && (md)->class_ <= MOBID_TREAS40) || ((md)->class_ >= MOBID_TREAS41 && (md)->class_ <= MOBID_TREAS49)) + +void mob_clear_spawninfo(); +int do_init_mob(void); +int do_final_mob(void); + +int mob_timer_delete(int tid, unsigned int tick, int id, intptr_t data); +int mob_deleteslave(struct mob_data *md); + +int mob_random_class (int *value, size_t count); +int mob_get_random_id(int type, int flag, int lv); +int mob_class_change(struct mob_data *md,int class_); +int mob_warpslave(struct block_list *bl, int range); +int mob_linksearch(struct block_list *bl,va_list ap); + +int mobskill_use(struct mob_data *md,unsigned int tick,int event); +int mobskill_event(struct mob_data *md,struct block_list *src,unsigned int tick, int flag); +int mobskill_castend_id( int tid, unsigned int tick, int id,int data ); +int mobskill_castend_pos( int tid, unsigned int tick, int id,int data ); +int mob_summonslave(struct mob_data *md2,int *value,int amount,uint16 skill_id); +int mob_countslave(struct block_list *bl); +int mob_count_sub(struct block_list *bl, va_list ap); + +int mob_is_clone(int class_); + +int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, int mode, int flag, unsigned int duration); +int mob_clone_delete(struct mob_data *md); + +void mob_reload(void); + +// MvP Tomb System +void mvptomb_create(struct mob_data *md, char *killer, time_t time); +void mvptomb_destroy(struct mob_data *md); + +#endif /* _MOB_H_ */ diff --git a/src/map/npc.c b/src/map/npc.c new file mode 100644 index 000000000..5d8a0274e --- /dev/null +++ b/src/map/npc.c @@ -0,0 +1,3895 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" +#include "../common/db.h" +#include "../common/socket.h" +#include "map.h" +#include "log.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "status.h" +#include "itemdb.h" +#include "script.h" +#include "mob.h" +#include "pet.h" +#include "instance.h" +#include "battle.h" +#include "skill.h" +#include "unit.h" +#include "npc.h" +#include "chat.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <time.h> +#include <errno.h> + + +struct npc_data* fake_nd; + +// linked list of npc source files +struct npc_src_list { + struct npc_src_list* next; + char name[4]; // dynamic array, the structure is allocated with extra bytes (string length) +}; +static struct npc_src_list* npc_src_files = NULL; + +static int npc_id=START_NPC_NUM; +static int npc_warp=0; +static int npc_shop=0; +static int npc_script=0; +static int npc_mob=0; +static int npc_delay_mob=0; +static int npc_cache_mob=0; + +/// Returns a new npc id that isn't being used in id_db. +/// Fatal error if nothing is available. +int npc_get_new_npc_id(void) { + if( npc_id >= START_NPC_NUM && !map_blid_exists(npc_id) ) + return npc_id++;// available + else {// find next id + int base_id = npc_id; + while( base_id != ++npc_id ) { + if( npc_id < START_NPC_NUM ) + npc_id = START_NPC_NUM; + if( !map_blid_exists(npc_id) ) + return npc_id++;// available + } + // full loop, nothing available + ShowFatalError("npc_get_new_npc_id: All ids are taken. Exiting..."); + exit(1); + } +} + +static DBMap* ev_db; // const char* event_name -> struct event_data* +static DBMap* npcname_db; // const char* npc_name -> struct npc_data* + +struct event_data { + struct npc_data *nd; + int pos; +}; + +static struct eri *timer_event_ers; //For the npc timer data. [Skotlex] + +/* hello */ +static char *npc_last_path; +static char *npc_last_ref; + +struct npc_path_data { + char* path; + unsigned short references; +}; +struct npc_path_data *npc_last_npd; +static DBMap *npc_path_db; + +//For holding the view data of npc classes. [Skotlex] +static struct view_data npc_viewdb[MAX_NPC_CLASS]; + +static struct script_event_s +{ //Holds pointers to the commonly executed scripts for speedup. [Skotlex] + struct event_data *event[UCHAR_MAX]; + const char *event_name[UCHAR_MAX]; + uint8 event_count; +} script_event[NPCE_MAX]; + +struct view_data* npc_get_viewdata(int class_) +{ //Returns the viewdata for normal npc classes. + if( class_ == INVISIBLE_CLASS ) + return &npc_viewdb[0]; + if (npcdb_checkid(class_) || class_ == WARP_CLASS) + return &npc_viewdb[class_]; + return NULL; +} + +int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd) +{ + char name[EVENT_NAME_LENGTH]; + + if( nd->touching_id ) + return 0; // Attached a player already. Can't trigger on anyone else. + + if( pc_ishiding(sd) ) + return 1; // Can't trigger 'OnTouch_'. try 'OnTouch' later. + + snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script_config.ontouch_name); + return npc_event(sd,name,1); +} + +int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd) +{ + char name[EVENT_NAME_LENGTH]; + + if( sd->areanpc_id == nd->bl.id ) + return 0; + + snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script_config.ontouch2_name); + return npc_event(sd,name,2); +} + +/*========================================== + * Sub-function of npc_enable, runs OnTouch event when enabled + *------------------------------------------*/ +int npc_enable_sub(struct block_list *bl, va_list ap) +{ + struct npc_data *nd; + + nullpo_ret(bl); + nullpo_ret(nd=va_arg(ap,struct npc_data *)); + if(bl->type == BL_PC) + { + TBL_PC *sd = (TBL_PC*)bl; + + if (nd->sc.option&OPTION_INVISIBLE) + return 1; + + if( npc_ontouch_event(sd,nd) > 0 && npc_ontouch2_event(sd,nd) > 0 ) + { // failed to run OnTouch event, so just click the npc + if (sd->npc_id != 0) + return 0; + + pc_stop_walking(sd,1); + npc_click(sd,nd); + } + } + return 0; +} + +/*========================================== + * Disable / Enable NPC + *------------------------------------------*/ +int npc_enable(const char* name, int flag) +{ + struct npc_data* nd = npc_name2id(name); + + if (nd==NULL) + { + ShowError("npc_enable: Attempted to %s a non-existing NPC '%s' (flag=%d).\n", (flag&3) ? "show" : "hide", name, flag); + return 0; + } + + if (flag&1) { + nd->sc.option&=~OPTION_INVISIBLE; + clif_spawn(&nd->bl); + } else if (flag&2) + nd->sc.option&=~OPTION_HIDE; + else if (flag&4) + nd->sc.option|= OPTION_HIDE; + else { //Can't change the view_data to invisible class because the view_data for all npcs is shared! [Skotlex] + nd->sc.option|= OPTION_INVISIBLE; + clif_clearunit_area(&nd->bl,CLR_OUTSIGHT); // Hack to trick maya purple card [Xazax] + } + + if (nd->class_ == WARP_CLASS || nd->class_ == FLAG_CLASS) + { //Client won't display option changes for these classes [Toms] + if (nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE)) + clif_clearunit_area(&nd->bl, CLR_OUTSIGHT); + else + clif_spawn(&nd->bl); + } else + clif_changeoption(&nd->bl); + + if( flag&3 && (nd->u.scr.xs >= 0 || nd->u.scr.ys >= 0) ) //check if player standing on a OnTouchArea + map_foreachinarea( npc_enable_sub, nd->bl.m, nd->bl.x-nd->u.scr.xs, nd->bl.y-nd->u.scr.ys, nd->bl.x+nd->u.scr.xs, nd->bl.y+nd->u.scr.ys, BL_PC, nd ); + + return 0; +} + +/*========================================== + * NPC lookup (get npc_data through npcname) + *------------------------------------------*/ +struct npc_data* npc_name2id(const char* name) +{ + return (struct npc_data *) strdb_get(npcname_db, name); +} +/** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT +/** + * Timer to check for idle time and timeout the dialog if necessary + **/ +int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data) { + struct map_session_data* sd = NULL; + if( (sd = map_id2sd(id)) == NULL || !sd->npc_id ) { + if( sd ) sd->npc_idle_timer = INVALID_TIMER; + return 0;//Not logged in anymore OR no longer attached to a npc + } + if( DIFF_TICK(tick,sd->npc_idle_tick) > (SECURE_NPCTIMEOUT*1000) ) { + /** + * If we still have the NPC script attached, tell it to stop. + **/ + if( sd->st ) + sd->st->state = END; + /** + * This guy's been idle for longer than allowed, close him. + **/ + clif_scriptclose(sd,sd->npc_id); + sd->npc_idle_timer = INVALID_TIMER; + } else //Create a new instance of ourselves to continue + sd->npc_idle_timer = add_timer(gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc_rr_secure_timeout_timer,sd->bl.id,0); + return 0; +} +#endif + +/*========================================== + * Dequeue event and add timer for execution (100ms) + *------------------------------------------*/ +int npc_event_dequeue(struct map_session_data* sd) +{ + nullpo_ret(sd); + + if(sd->npc_id) + { //Current script is aborted. + if(sd->state.using_fake_npc){ + clif_clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd); + sd->state.using_fake_npc = 0; + } + if (sd->st) { + script_free_state(sd->st); + sd->st = NULL; + } + sd->npc_id = 0; + } + + if (!sd->eventqueue[0][0]) + return 0; //Nothing to dequeue + + if (!pc_addeventtimer(sd,100,sd->eventqueue[0])) + { //Failed to dequeue, couldn't set a timer. + ShowWarning("npc_event_dequeue: event timer is full !\n"); + return 0; + } + //Event dequeued successfully, shift other elements. + memmove(sd->eventqueue[0], sd->eventqueue[1], (MAX_EVENTQUEUE-1)*sizeof(sd->eventqueue[0])); + sd->eventqueue[MAX_EVENTQUEUE-1][0]=0; + return 1; +} + +/*========================================== + * exports a npc event label + * called from npc_parse_script + *------------------------------------------*/ +static int npc_event_export(struct npc_data *nd, int i) +{ + char* lname = nd->u.scr.label_list[i].name; + int pos = nd->u.scr.label_list[i].pos; + if ((lname[0] == 'O' || lname[0] == 'o') && (lname[1] == 'N' || lname[1] == 'n')) { + struct event_data *ev; + char buf[EVENT_NAME_LENGTH]; + snprintf(buf, ARRAYLENGTH(buf), "%s::%s", nd->exname, lname); + // generate the data and insert it + CREATE(ev, struct event_data, 1); + ev->nd = nd; + ev->pos = pos; + if (strdb_put(ev_db, buf, ev)) // There was already another event of the same name? + return 1; + } + return 0; +} + +int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char* eventname); //[Lance] + +/** + * Exec name (NPC events) on player or global + * Do on all NPC when called with foreach + * @see DBApply + */ +int npc_event_doall_sub(DBKey key, DBData *data, va_list ap) +{ + const char* p = key.str; + struct event_data* ev; + int* c; + const char* name; + int rid; + + nullpo_ret(ev = db_data2ptr(data)); + nullpo_ret(c = va_arg(ap, int *)); + nullpo_ret(name = va_arg(ap, const char *)); + rid = va_arg(ap, int); + + p = strchr(p, ':'); // match only the event name + if( p && strcmpi(name, p) == 0 /* && !ev->nd->src_id */ ) // Do not run on duplicates. [Paradox924X] + { + if(rid) // a player may only have 1 script running at the same time + npc_event_sub(map_id2sd(rid),ev,key.str); + else + run_script(ev->nd->u.scr.script,ev->pos,rid,ev->nd->bl.id); + (*c)++; + } + + return 0; +} + +/** + * @see DBApply + */ +static int npc_event_do_sub(DBKey key, DBData *data, va_list ap) +{ + const char* p = key.str; + struct event_data* ev; + int* c; + const char* name; + + nullpo_ret(ev = db_data2ptr(data)); + nullpo_ret(c = va_arg(ap, int *)); + nullpo_ret(name = va_arg(ap, const char *)); + + if( p && strcmpi(name, p) == 0 ) + { + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + (*c)++; + } + + return 0; +} + +// runs the specified event (supports both single-npc and global events) +int npc_event_do(const char* name) +{ + int c = 0; + + if( name[0] == ':' && name[1] == ':' ) + ev_db->foreach(ev_db,npc_event_doall_sub,&c,name,0); + else + ev_db->foreach(ev_db,npc_event_do_sub,&c,name); + + return c; +} + +// runs the specified event (global only) +int npc_event_doall(const char* name) +{ + return npc_event_doall_id(name, 0); +} + +// runs the specified event, with a RID attached (global only) +int npc_event_doall_id(const char* name, int rid) +{ + int c = 0; + char buf[64]; + safesnprintf(buf, sizeof(buf), "::%s", name); + ev_db->foreach(ev_db,npc_event_doall_sub,&c,buf,rid); + return c; +} + +/*========================================== + * Clock event execution + * OnMinute/OnClock/OnHour/OnDay/OnDDHHMM + *------------------------------------------*/ +int npc_event_do_clock(int tid, unsigned int tick, int id, intptr_t data) +{ + static struct tm ev_tm_b; // tracks previous execution time + time_t timer; + struct tm* t; + char buf[64]; + int c = 0; + + timer = time(NULL); + t = localtime(&timer); + + if (t->tm_min != ev_tm_b.tm_min ) { + char* day; + + switch (t->tm_wday) { + case 0: day = "Sun"; break; + case 1: day = "Mon"; break; + case 2: day = "Tue"; break; + case 3: day = "Wed"; break; + case 4: day = "Thu"; break; + case 5: day = "Fri"; break; + case 6: day = "Sat"; break; + default:day = ""; break; + } + + sprintf(buf,"OnMinute%02d",t->tm_min); + c += npc_event_doall(buf); + + sprintf(buf,"OnClock%02d%02d",t->tm_hour,t->tm_min); + c += npc_event_doall(buf); + + sprintf(buf,"On%s%02d%02d",day,t->tm_hour,t->tm_min); + c += npc_event_doall(buf); + } + + if (t->tm_hour != ev_tm_b.tm_hour) { + sprintf(buf,"OnHour%02d",t->tm_hour); + c += npc_event_doall(buf); + } + + if (t->tm_mday != ev_tm_b.tm_mday) { + sprintf(buf,"OnDay%02d%02d",t->tm_mon+1,t->tm_mday); + c += npc_event_doall(buf); + } + + memcpy(&ev_tm_b,t,sizeof(ev_tm_b)); + return c; +} + +/*========================================== + * OnInit Event execution (the start of the event and watch) + *------------------------------------------*/ +void npc_event_do_oninit(void) +{ + ShowStatus("Event '"CL_WHITE"OnInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs."CL_CLL"\n", npc_event_doall("OnInit")); + + add_timer_interval(gettick()+100,npc_event_do_clock,0,0,1000); +} + +/*========================================== + * Incorporation of the label for the timer event + * called from npc_parse_script + *------------------------------------------*/ +int npc_timerevent_export(struct npc_data *nd, int i) +{ + int t = 0, k = 0; + char *lname = nd->u.scr.label_list[i].name; + int pos = nd->u.scr.label_list[i].pos; + if (sscanf(lname, "OnTimer%d%n", &t, &k) == 1 && lname[k] == '\0') { + // Timer event + struct npc_timerevent_list *te = nd->u.scr.timer_event; + int j, k = nd->u.scr.timeramount; + if (te == NULL) + te = (struct npc_timerevent_list *)aMalloc(sizeof(struct npc_timerevent_list)); + else + te = (struct npc_timerevent_list *)aRealloc( te, sizeof(struct npc_timerevent_list) * (k+1) ); + for (j = 0; j < k; j++) { + if (te[j].timer > t) { + memmove(te+j+1, te+j, sizeof(struct npc_timerevent_list)*(k-j)); + break; + } + } + te[j].timer = t; + te[j].pos = pos; + nd->u.scr.timer_event = te; + nd->u.scr.timeramount++; + } + return 0; +} + +struct timer_event_data { + int rid; //Attached player for this timer. + int next; //timer index (starts with 0, then goes up to nd->u.scr.timeramount) + int time; //holds total time elapsed for the script from when timer was started to when last time the event triggered. +}; + +/*========================================== + * triger 'OnTimerXXXX' events + *------------------------------------------*/ +int npc_timerevent(int tid, unsigned int tick, int id, intptr_t data) +{ + int next; + int old_rid, old_timer; + unsigned int old_tick; + struct npc_data* nd=(struct npc_data *)map_id2bl(id); + struct npc_timerevent_list *te; + struct timer_event_data *ted = (struct timer_event_data*)data; + struct map_session_data *sd=NULL; + + if( nd == NULL ) + { + ShowError("npc_timerevent: NPC not found??\n"); + return 0; + } + + if( ted->rid && !(sd = map_id2sd(ted->rid)) ) + { + ShowError("npc_timerevent: Attached player not found.\n"); + ers_free(timer_event_ers, ted); + return 0; + } + + // These stuffs might need to be restored. + old_rid = nd->u.scr.rid; + old_tick = nd->u.scr.timertick; + old_timer = nd->u.scr.timer; + + // Set the values of the timer + nd->u.scr.rid = sd?sd->bl.id:0; //attached rid + nd->u.scr.timertick = tick; //current time tick + nd->u.scr.timer = ted->time; //total time from beginning to now + + // Locate the event + te = nd->u.scr.timer_event + ted->next; + + // Arrange for the next event + ted->next++; + if( nd->u.scr.timeramount > ted->next ) + { + next = nd->u.scr.timer_event[ ted->next ].timer - nd->u.scr.timer_event[ ted->next - 1 ].timer; + ted->time += next; + if( sd ) + sd->npc_timer_id = add_timer(tick+next,npc_timerevent,id,(intptr_t)ted); + else + nd->u.scr.timerid = add_timer(tick+next,npc_timerevent,id,(intptr_t)ted); + } + else + { + if( sd ) + sd->npc_timer_id = INVALID_TIMER; + else + nd->u.scr.timerid = INVALID_TIMER; + + ers_free(timer_event_ers, ted); + } + + // Run the script + run_script(nd->u.scr.script,te->pos,nd->u.scr.rid,nd->bl.id); + + nd->u.scr.rid = old_rid; // Attached-rid should be restored anyway. + if( sd ) + { // Restore previous data, only if this timer is a player-attached one. + nd->u.scr.timer = old_timer; + nd->u.scr.timertick = old_tick; + } + + return 0; +} +/*========================================== + * Start/Resume NPC timer + *------------------------------------------*/ +int npc_timerevent_start(struct npc_data* nd, int rid) +{ + int j, next; + unsigned int tick = gettick(); + struct map_session_data *sd = NULL; //Player to whom script is attached. + struct timer_event_data *ted; + + nullpo_ret(nd); + + // Check if there is an OnTimer Event + ARR_FIND( 0, nd->u.scr.timeramount, j, nd->u.scr.timer_event[j].timer > nd->u.scr.timer ); + + if( nd->u.scr.rid > 0 && !(sd = map_id2sd(nd->u.scr.rid)) ) + { // Failed to attach timer to this player. + ShowError("npc_timerevent_start: Attached player not found!\n"); + return 1; + } + + // Check if timer is already started. + if( sd ) + { + if( sd->npc_timer_id != INVALID_TIMER ) + return 0; + } + else if( nd->u.scr.timerid != INVALID_TIMER || nd->u.scr.timertick ) + return 0; + + if (j < nd->u.scr.timeramount) + { + // Arrange for the next event + ted = ers_alloc(timer_event_ers, struct timer_event_data); + ted->next = j; // Set event index + ted->time = nd->u.scr.timer_event[j].timer; + next = nd->u.scr.timer_event[j].timer - nd->u.scr.timer; + if( sd ) + { + ted->rid = sd->bl.id; // Attach only the player if attachplayerrid was used. + sd->npc_timer_id = add_timer(tick+next,npc_timerevent,nd->bl.id,(intptr_t)ted); + } + else + { + ted->rid = 0; + nd->u.scr.timertick = tick; // Set when timer is started + nd->u.scr.timerid = add_timer(tick+next,npc_timerevent,nd->bl.id,(intptr_t)ted); + } + } + else if (!sd) + { + nd->u.scr.timertick = tick; + } + + return 0; +} +/*========================================== + * Stop NPC timer + *------------------------------------------*/ +int npc_timerevent_stop(struct npc_data* nd) +{ + struct map_session_data *sd = NULL; + const struct TimerData *td = NULL; + int *tid; + + nullpo_ret(nd); + + if( nd->u.scr.rid && !(sd = map_id2sd(nd->u.scr.rid)) ) + { + ShowError("npc_timerevent_stop: Attached player not found!\n"); + return 1; + } + + tid = sd?&sd->npc_timer_id:&nd->u.scr.timerid; + if( *tid == INVALID_TIMER && (sd || !nd->u.scr.timertick) ) // Nothing to stop + return 0; + + // Delete timer + if ( *tid != INVALID_TIMER ) + { + td = get_timer(*tid); + if( td && td->data ) + ers_free(timer_event_ers, (void*)td->data); + delete_timer(*tid,npc_timerevent); + *tid = INVALID_TIMER; + } + + if( !sd && nd->u.scr.timertick ) + { + nd->u.scr.timer += DIFF_TICK(gettick(),nd->u.scr.timertick); // Set 'timer' to the time that has passed since the beginning of the timers + nd->u.scr.timertick = 0; // Set 'tick' to zero so that we know it's off. + } + + return 0; +} +/*========================================== + * Aborts a running NPC timer that is attached to a player. + *------------------------------------------*/ +void npc_timerevent_quit(struct map_session_data* sd) +{ + const struct TimerData *td; + struct npc_data* nd; + struct timer_event_data *ted; + + // Check timer existance + if( sd->npc_timer_id == INVALID_TIMER ) + return; + if( !(td = get_timer(sd->npc_timer_id)) ) + { + sd->npc_timer_id = INVALID_TIMER; + return; + } + + // Delete timer + nd = (struct npc_data *)map_id2bl(td->id); + ted = (struct timer_event_data*)td->data; + delete_timer(sd->npc_timer_id, npc_timerevent); + sd->npc_timer_id = INVALID_TIMER; + + // Execute OnTimerQuit + if( nd && nd->bl.type == BL_NPC ) + { + char buf[EVENT_NAME_LENGTH]; + struct event_data *ev; + + snprintf(buf, ARRAYLENGTH(buf), "%s::OnTimerQuit", nd->exname); + ev = (struct event_data*)strdb_get(ev_db, buf); + if( ev && ev->nd != nd ) + { + ShowWarning("npc_timerevent_quit: Unable to execute \"OnTimerQuit\", two NPCs have the same event name [%s]!\n",buf); + ev = NULL; + } + if( ev ) + { + int old_rid,old_timer; + unsigned int old_tick; + + //Set timer related info. + old_rid = (nd->u.scr.rid == sd->bl.id ? 0 : nd->u.scr.rid); // Detach rid if the last attached player logged off. + old_tick = nd->u.scr.timertick; + old_timer = nd->u.scr.timer; + + nd->u.scr.rid = sd->bl.id; + nd->u.scr.timertick = gettick(); + nd->u.scr.timer = ted->time; + + //Execute label + run_script(nd->u.scr.script,ev->pos,sd->bl.id,nd->bl.id); + + //Restore previous data. + nd->u.scr.rid = old_rid; + nd->u.scr.timer = old_timer; + nd->u.scr.timertick = old_tick; + } + } + ers_free(timer_event_ers, ted); +} + +/*========================================== + * Get the tick value of an NPC timer + * If it's stopped, return stopped time + *------------------------------------------*/ +int npc_gettimerevent_tick(struct npc_data* nd) +{ + int tick; + nullpo_ret(nd); + + // TODO: Get player attached timer's tick. Now we can just get it by using 'getnpctimer' inside OnTimer event. + + tick = nd->u.scr.timer; // The last time it's active(start, stop or event trigger) + if( nd->u.scr.timertick ) // It's a running timer + tick += DIFF_TICK(gettick(), nd->u.scr.timertick); + + return tick; +} + +/*========================================== + * Set tick for running and stopped timer + *------------------------------------------*/ +int npc_settimerevent_tick(struct npc_data* nd, int newtimer) +{ + bool flag; + int old_rid; + //struct map_session_data *sd = NULL; + + nullpo_ret(nd); + + // TODO: Set player attached timer's tick. + + old_rid = nd->u.scr.rid; + nd->u.scr.rid = 0; + + // Check if timer is started + flag = (nd->u.scr.timerid != INVALID_TIMER); + + if( flag ) npc_timerevent_stop(nd); + nd->u.scr.timer = newtimer; + if( flag ) npc_timerevent_start(nd, -1); + + nd->u.scr.rid = old_rid; + return 0; +} + +int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char* eventname) +{ + if ( sd->npc_id != 0 ) + { + //Enqueue the event trigger. + int i; + ARR_FIND( 0, MAX_EVENTQUEUE, i, sd->eventqueue[i][0] == '\0' ); + if( i < MAX_EVENTQUEUE ) + { + safestrncpy(sd->eventqueue[i],eventname,50); //Event enqueued. + return 0; + } + + ShowWarning("npc_event: player's event queue is full, can't add event '%s' !\n", eventname); + return 1; + } + if( ev->nd->sc.option&OPTION_INVISIBLE ) + { + //Disabled npc, shouldn't trigger event. + npc_event_dequeue(sd); + return 2; + } + run_script(ev->nd->u.scr.script,ev->pos,sd->bl.id,ev->nd->bl.id); + return 0; +} + +/*========================================== + * NPC processing event type + *------------------------------------------*/ +int npc_event(struct map_session_data* sd, const char* eventname, int ontouch) +{ + struct event_data* ev = (struct event_data*)strdb_get(ev_db, eventname); + struct npc_data *nd; + + nullpo_ret(sd); + + if( ev == NULL || (nd = ev->nd) == NULL ) + { + if( !ontouch ) + ShowError("npc_event: event not found [%s]\n", eventname); + return ontouch; + } + + switch(ontouch) + { + case 1: + nd->touching_id = sd->bl.id; + sd->touching_id = nd->bl.id; + break; + case 2: + sd->areanpc_id = nd->bl.id; + break; + } + + return npc_event_sub(sd,ev,eventname); +} + +/*========================================== + * Sub chk then execute area event type + *------------------------------------------*/ +int npc_touch_areanpc_sub(struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int pc_id; + char *name; + + nullpo_ret(bl); + nullpo_ret((sd = map_id2sd(bl->id))); + + pc_id = va_arg(ap,int); + name = va_arg(ap,char*); + + if( sd->state.warping ) + return 0; + if( pc_ishiding(sd) ) + return 0; + if( pc_id == sd->bl.id ) + return 0; + + npc_event(sd,name,1); + + return 1; +} + +/*========================================== + * Chk if sd is still touching his assigned npc. + * If not, it unsets it and searches for another player in range. + *------------------------------------------*/ +int npc_touchnext_areanpc(struct map_session_data* sd, bool leavemap) +{ + struct npc_data *nd = map_id2nd(sd->touching_id); + short xs, ys; + + if( !nd || nd->touching_id != sd->bl.id ) + return 1; + + xs = nd->u.scr.xs; + ys = nd->u.scr.ys; + + if( sd->bl.m != nd->bl.m || + sd->bl.x < nd->bl.x - xs || sd->bl.x > nd->bl.x + xs || + sd->bl.y < nd->bl.y - ys || sd->bl.y > nd->bl.y + ys || + pc_ishiding(sd) || leavemap ) + { + char name[EVENT_NAME_LENGTH]; + + nd->touching_id = sd->touching_id = 0; + snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script_config.ontouch_name); + map_forcountinarea(npc_touch_areanpc_sub,nd->bl.m,nd->bl.x - xs,nd->bl.y - ys,nd->bl.x + xs,nd->bl.y + ys,1,BL_PC,sd->bl.id,name); + } + return 0; +} + +/*========================================== + * Exec OnTouch for player if in range of area event + *------------------------------------------*/ +int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y) +{ + int xs,ys; + int f = 1; + int i; + int j, found_warp = 0; + + nullpo_retr(1, sd); + + // Why not enqueue it? [Inkfish] + //if(sd->npc_id) + // return 1; + + for(i=0;i<map[m].npc_num;i++) + { + if (map[m].npc[i]->sc.option&OPTION_INVISIBLE) { + f=0; // a npc was found, but it is disabled; don't print warning + continue; + } + + switch(map[m].npc[i]->subtype) { + case WARP: + xs=map[m].npc[i]->u.warp.xs; + ys=map[m].npc[i]->u.warp.ys; + break; + case SCRIPT: + xs=map[m].npc[i]->u.scr.xs; + ys=map[m].npc[i]->u.scr.ys; + break; + default: + continue; + } + if( x >= map[m].npc[i]->bl.x-xs && x <= map[m].npc[i]->bl.x+xs + && y >= map[m].npc[i]->bl.y-ys && y <= map[m].npc[i]->bl.y+ys ) + break; + } + if( i == map[m].npc_num ) + { + if( f == 1 ) // no npc found + ShowError("npc_touch_areanpc : stray NPC cell/NPC not found in the block on coordinates '%s',%d,%d\n", map[m].name, x, y); + return 1; + } + switch(map[m].npc[i]->subtype) { + case WARP: + if( pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]) ) + break; // hidden chars cannot use warps + pc_setpos(sd,map[m].npc[i]->u.warp.mapindex,map[m].npc[i]->u.warp.x,map[m].npc[i]->u.warp.y,CLR_OUTSIGHT); + break; + case SCRIPT: + for (j = i; j < map[m].npc_num; j++) { + if (map[m].npc[j]->subtype != WARP) { + continue; + } + + if ((sd->bl.x >= (map[m].npc[j]->bl.x - map[m].npc[j]->u.warp.xs) && sd->bl.x <= (map[m].npc[j]->bl.x + map[m].npc[j]->u.warp.xs)) && + (sd->bl.y >= (map[m].npc[j]->bl.y - map[m].npc[j]->u.warp.ys) && sd->bl.y <= (map[m].npc[j]->bl.y + map[m].npc[j]->u.warp.ys))) { + if( pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]) ) + break; // hidden chars cannot use warps + pc_setpos(sd,map[m].npc[j]->u.warp.mapindex,map[m].npc[j]->u.warp.x,map[m].npc[j]->u.warp.y,CLR_OUTSIGHT); + found_warp = 1; + break; + } + } + + if (found_warp > 0) { + break; + } + + if( npc_ontouch_event(sd,map[m].npc[i]) > 0 && npc_ontouch2_event(sd,map[m].npc[i]) > 0 ) + { // failed to run OnTouch event, so just click the npc + struct unit_data *ud = unit_bl2ud(&sd->bl); + if( ud && ud->walkpath.path_pos < ud->walkpath.path_len ) + { // Since walktimer always == INVALID_TIMER at this time, we stop walking manually. [Inkfish] + clif_fixpos(&sd->bl); + ud->walkpath.path_pos = ud->walkpath.path_len; + } + sd->areanpc_id = map[m].npc[i]->bl.id; + npc_click(sd,map[m].npc[i]); + } + break; + } + return 0; +} + +// OnTouch NPC or Warp for Mobs +// Return 1 if Warped +int npc_touch_areanpc2(struct mob_data *md) +{ + int i, m = md->bl.m, x = md->bl.x, y = md->bl.y, id; + char eventname[EVENT_NAME_LENGTH]; + struct event_data* ev; + int xs, ys; + + for( i = 0; i < map[m].npc_num; i++ ) + { + if( map[m].npc[i]->sc.option&OPTION_INVISIBLE ) + continue; + + switch( map[m].npc[i]->subtype ) + { + case WARP: + if( !( battle_config.mob_warp&1 ) ) + continue; + xs = map[m].npc[i]->u.warp.xs; + ys = map[m].npc[i]->u.warp.ys; + break; + case SCRIPT: + xs = map[m].npc[i]->u.scr.xs; + ys = map[m].npc[i]->u.scr.ys; + break; + default: + continue; // Keep Searching + } + + if( x >= map[m].npc[i]->bl.x-xs && x <= map[m].npc[i]->bl.x+xs && y >= map[m].npc[i]->bl.y-ys && y <= map[m].npc[i]->bl.y+ys ) + { // In the npc touch area + switch( map[m].npc[i]->subtype ) + { + case WARP: + xs = map_mapindex2mapid(map[m].npc[i]->u.warp.mapindex); + if( m < 0 ) + break; // Cannot Warp between map servers + if( unit_warp(&md->bl, xs, map[m].npc[i]->u.warp.x, map[m].npc[i]->u.warp.y, CLR_OUTSIGHT) == 0 ) + return 1; // Warped + break; + case SCRIPT: + if( map[m].npc[i]->bl.id == md->areanpc_id ) + break; // Already touch this NPC + snprintf(eventname, ARRAYLENGTH(eventname), "%s::OnTouchNPC", map[m].npc[i]->exname); + if( (ev = (struct event_data*)strdb_get(ev_db, eventname)) == NULL || ev->nd == NULL ) + break; // No OnTouchNPC Event + md->areanpc_id = map[m].npc[i]->bl.id; + id = md->bl.id; // Stores Unique ID + run_script(ev->nd->u.scr.script, ev->pos, md->bl.id, ev->nd->bl.id); + if( map_id2md(id) == NULL ) return 1; // Not Warped, but killed + break; + } + + return 0; + } + } + + return 0; +} + +//Checks if there are any NPC on-touch objects on the given range. +//Flag determines the type of object to check for: +//&1: NPC Warps +//&2: NPCs with on-touch events. +int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range) +{ + int i; + int x0,y0,x1,y1; + int xs,ys; + + if (range < 0) return 0; + x0 = max(x-range, 0); + y0 = max(y-range, 0); + x1 = min(x+range, map[m].xs-1); + y1 = min(y+range, map[m].ys-1); + + //First check for npc_cells on the range given + i = 0; + for (ys = y0; ys <= y1 && !i; ys++) { + for(xs = x0; xs <= x1 && !i; xs++){ + if (map_getcell(m,xs,ys,CELL_CHKNPC)) + i = 1; + } + } + if (!i) return 0; //No NPC_CELLs. + + //Now check for the actual NPC on said range. + for(i=0;i<map[m].npc_num;i++) + { + if (map[m].npc[i]->sc.option&OPTION_INVISIBLE) + continue; + + switch(map[m].npc[i]->subtype) + { + case WARP: + if (!(flag&1)) + continue; + xs=map[m].npc[i]->u.warp.xs; + ys=map[m].npc[i]->u.warp.ys; + break; + case SCRIPT: + if (!(flag&2)) + continue; + xs=map[m].npc[i]->u.scr.xs; + ys=map[m].npc[i]->u.scr.ys; + break; + default: + continue; + } + + if( x1 >= map[m].npc[i]->bl.x-xs && x0 <= map[m].npc[i]->bl.x+xs + && y1 >= map[m].npc[i]->bl.y-ys && y0 <= map[m].npc[i]->bl.y+ys ) + break; // found a npc + } + if (i==map[m].npc_num) + return 0; + + return (map[m].npc[i]->bl.id); +} + +/*========================================== + * Chk if player not too far to access the npc. + * Returns npc_data (success) or NULL (fail). + *------------------------------------------*/ +struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* bl) +{ + struct npc_data *nd; + + nullpo_retr(NULL, sd); + if(bl == NULL) return NULL; + if(bl->type != BL_NPC) return NULL; + nd = (TBL_NPC*)bl; + + if(sd->state.using_fake_npc && sd->npc_id == bl->id) + return nd; + + if (nd->class_<0) //Class-less npc, enable click from anywhere. + return nd; + + if (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1) + return NULL; + + return nd; +} + +/*========================================== + * Make NPC talk in global chat (like npctalk) + *------------------------------------------*/ +int npc_globalmessage(const char* name, const char* mes) +{ + struct npc_data* nd = npc_name2id(name); + char temp[100]; + + if (!nd) + return 0; + + snprintf(temp, sizeof(temp), "%s : %s", name, mes); + clif_GlobalMessage(&nd->bl,temp); + + return 0; +} + +// MvP tomb [GreenBox] +void run_tomb(struct map_session_data* sd, struct npc_data* nd) +{ + char buffer[200]; + char time[10]; + + strftime(time, sizeof(time), "%H:%M", localtime(&nd->u.tomb.kill_time)); + + // TODO: Find exact color? + snprintf(buffer, sizeof(buffer), msg_txt(657), nd->u.tomb.md->db->name); + clif_scriptmes(sd, nd->bl.id, buffer); + + clif_scriptmes(sd, nd->bl.id, msg_txt(658)); + + snprintf(buffer, sizeof(buffer), msg_txt(659), time); + clif_scriptmes(sd, nd->bl.id, buffer); + + clif_scriptmes(sd, nd->bl.id, msg_txt(660)); + + snprintf(buffer, sizeof(buffer), msg_txt(661), nd->u.tomb.killer_name[0] ? nd->u.tomb.killer_name : "Unknown"); + clif_scriptmes(sd, nd->bl.id, buffer); + + clif_scriptclose(sd, nd->bl.id); +} + +/*========================================== + * NPC 1st call when clicking on npc + * Do specific action for NPC type (openshop, run scripts...) + *------------------------------------------*/ +int npc_click(struct map_session_data* sd, struct npc_data* nd) +{ + nullpo_retr(1, sd); + + if (sd->npc_id != 0) { + ShowError("npc_click: npc_id != 0\n"); + return 1; + } + + if(!nd) return 1; + if ((nd = npc_checknear(sd,&nd->bl)) == NULL) + return 1; + //Hidden/Disabled npc. + if (nd->class_ < 0 || nd->sc.option&(OPTION_INVISIBLE|OPTION_HIDE)) + return 1; + + switch(nd->subtype) { + case SHOP: + clif_npcbuysell(sd,nd->bl.id); + break; + case CASHSHOP: + clif_cashshop_show(sd,nd); + break; + case SCRIPT: + run_script(nd->u.scr.script,0,sd->bl.id,nd->bl.id); + break; + case TOMB: + run_tomb(sd,nd); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int npc_scriptcont(struct map_session_data* sd, int id) +{ + nullpo_retr(1, sd); + + if( id != sd->npc_id ){ + TBL_NPC* nd_sd=(TBL_NPC*)map_id2bl(sd->npc_id); + TBL_NPC* nd=(TBL_NPC*)map_id2bl(id); + ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n", + nd_sd?(char*)nd_sd->name:"'Unknown NPC'", (int)sd->npc_id, + nd?(char*)nd->name:"'Unknown NPC'", (int)id); + return 1; + } + + if(id != fake_nd->bl.id) { // Not item script + if ((npc_checknear(sd,map_id2bl(id))) == NULL){ + ShowWarning("npc_scriptcont: failed npc_checknear test.\n"); + return 1; + } + } + /** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + /** + * Update the last NPC iteration + **/ + sd->npc_idle_tick = gettick(); +#endif + + /** + * WPE can get to this point with a progressbar; we deny it. + **/ + if( sd->progressbar.npc_id && DIFF_TICK(sd->progressbar.timeout,gettick()) > 0 ) + return 1; + + run_script_main(sd->st); + + return 0; +} + +/*========================================== + * Chk if valid call then open buy or selling list + *------------------------------------------*/ +int npc_buysellsel(struct map_session_data* sd, int id, int type) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if ((nd = npc_checknear(sd,map_id2bl(id))) == NULL) + return 1; + + if (nd->subtype!=SHOP) { + ShowError("no such shop npc : %d\n",id); + if (sd->npc_id == id) + sd->npc_id=0; + return 1; + } + if (nd->sc.option & OPTION_INVISIBLE) // can't buy if npc is not visible (hack?) + return 1; + if( nd->class_ < 0 && !sd->state.callshop ) + {// not called through a script and is not a visible NPC so an invalid call + return 1; + } + + // reset the callshop state for future calls + sd->state.callshop = 0; + sd->npc_shopid = id; + + if (type==0) { + clif_buylist(sd,nd); + } else { + clif_selllist(sd); + } + return 0; +} +/*========================================== +* Cash Shop Buy List +*------------------------------------------*/ +int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list) +{ + int i, j, nameid, amount, new_, w, vt; + struct npc_data *nd = (struct npc_data *)map_id2bl(sd->npc_shopid); + + if( !nd || nd->subtype != CASHSHOP ) + return 1; + + if( sd->state.trading ) + return 4; + + new_ = 0; + w = 0; + vt = 0; // Global Value + + // Validating Process ---------------------------------------------------- + for( i = 0; i < count; i++ ) + { + nameid = item_list[i*2+1]; + amount = item_list[i*2+0]; + + if( !itemdb_exists(nameid) || amount <= 0 ) + return 5; + + ARR_FIND(0,nd->u.shop.count,j,nd->u.shop.shop_item[j].nameid == nameid); + if( j == nd->u.shop.count || nd->u.shop.shop_item[j].value <= 0 ) + return 5; + + if( !itemdb_isstackable(nameid) && amount > 1 ) + { + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + amount = item_list[i*2+0] = 1; + } + + switch( pc_checkadditem(sd,nameid,amount) ) + { + case ADDITEM_NEW: + new_++; + break; + case ADDITEM_OVERAMOUNT: + return 3; + } + + vt += nd->u.shop.shop_item[j].value * amount; + w += itemdb_weight(nameid) * amount; + } + + if( w + sd->weight > sd->max_weight ) + return 3; + if( pc_inventoryblank(sd) < new_ ) + return 3; + if( points > vt ) points = vt; + + // Payment Process ---------------------------------------------------- + if( sd->kafraPoints < points || sd->cashPoints < (vt - points) ) + return 6; + pc_paycash(sd,vt,points); + + // Delivery Process ---------------------------------------------------- + for( i = 0; i < count; i++ ) + { + struct item item_tmp; + + nameid = item_list[i*2+1]; + amount = item_list[i*2+0]; + + memset(&item_tmp,0,sizeof(item_tmp)); + + if( !pet_create_egg(sd,nameid) ) + { + item_tmp.nameid = nameid; + item_tmp.identify = 1; + pc_additem(sd,&item_tmp,amount,LOG_TYPE_NPC); + } + } + + return 0; +} + +//npc_buylist for script-controlled shops. +static int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd) +{ + char npc_ev[EVENT_NAME_LENGTH]; + int i; + int key_nameid = 0; + int key_amount = 0; + + // discard old contents + script_cleararray_pc(sd, "@bought_nameid", (void*)0); + script_cleararray_pc(sd, "@bought_quantity", (void*)0); + + // save list of bought items + for( i = 0; i < n; i++ ) + { + script_setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i*2+1], &key_nameid); + script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i*2], &key_amount); + } + + // invoke event + snprintf(npc_ev, ARRAYLENGTH(npc_ev), "%s::OnBuyItem", nd->exname); + npc_event(sd, npc_ev, 0); + + return 0; +} + +/*========================================== + * Cash Shop Buy + *------------------------------------------*/ +int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points) +{ + struct npc_data *nd = (struct npc_data *)map_id2bl(sd->npc_shopid); + struct item_data *item; + int i, price, w; + + if( amount <= 0 ) + return 5; + + if( points < 0 ) + return 6; + + if( !nd || nd->subtype != CASHSHOP ) + return 1; + + if( sd->state.trading ) + return 4; + + if( (item = itemdb_exists(nameid)) == NULL ) + return 5; // Invalid Item + + ARR_FIND(0, nd->u.shop.count, i, nd->u.shop.shop_item[i].nameid == nameid); + if( i == nd->u.shop.count ) + return 5; + if( nd->u.shop.shop_item[i].value <= 0 ) + return 5; + + if(!itemdb_isstackable(nameid) && amount > 1) + { + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + amount = 1; + } + + switch( pc_checkadditem(sd, nameid, amount) ) + { + case ADDITEM_NEW: + if( pc_inventoryblank(sd) == 0 ) + return 3; + break; + case ADDITEM_OVERAMOUNT: + return 3; + } + + w = item->weight * amount; + if( w + sd->weight > sd->max_weight ) + return 3; + + if( (double)nd->u.shop.shop_item[i].value * amount > INT_MAX ) + { + ShowWarning("npc_cashshop_buy: Item '%s' (%d) price overflow attempt!\n", item->name, nameid); + ShowDebug("(NPC:'%s' (%s,%d,%d), player:'%s' (%d/%d), value:%d, amount:%d)\n", + nd->exname, map[nd->bl.m].name, nd->bl.x, nd->bl.y, sd->status.name, sd->status.account_id, sd->status.char_id, nd->u.shop.shop_item[i].value, amount); + return 5; + } + + price = nd->u.shop.shop_item[i].value * amount; + if( points > price ) + points = price; + + if( (sd->kafraPoints < points) || (sd->cashPoints < price - points) ) + return 6; + + pc_paycash(sd, price, points); + + if( !pet_create_egg(sd, nameid) ) + { + struct item item_tmp; + memset(&item_tmp, 0, sizeof(struct item)); + item_tmp.nameid = nameid; + item_tmp.identify = 1; + + pc_additem(sd,&item_tmp, amount, LOG_TYPE_NPC); + } + + return 0; +} + +/// Player item purchase from npc shop. +/// +/// @param item_list 'n' pairs <amount,itemid> +/// @return result code for clif_parse_NpcBuyListSend +int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) +{ + struct npc_data* nd; + double z; + int i,j,w,skill,new_; + + nullpo_retr(3, sd); + nullpo_retr(3, item_list); + + nd = npc_checknear(sd,map_id2bl(sd->npc_shopid)); + if( nd == NULL ) + return 3; + if( nd->subtype != SHOP ) + return 3; + + z = 0; + w = 0; + new_ = 0; + // process entries in buy list, one by one + for( i = 0; i < n; ++i ) + { + int nameid, amount, value; + + // find this entry in the shop's sell list + ARR_FIND( 0, nd->u.shop.count, j, + item_list[i*2+1] == nd->u.shop.shop_item[j].nameid || //Normal items + item_list[i*2+1] == itemdb_viewid(nd->u.shop.shop_item[j].nameid) //item_avail replacement + ); + + if( j == nd->u.shop.count ) + return 3; // no such item in shop + + amount = item_list[i*2+0]; + nameid = item_list[i*2+1] = nd->u.shop.shop_item[j].nameid; //item_avail replacement + value = nd->u.shop.shop_item[j].value; + + if( !itemdb_exists(nameid) ) + return 3; // item no longer in itemdb + + if( !itemdb_isstackable(nameid) && amount > 1 ) + { //Exploit? You can't buy more than 1 of equipment types o.O + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + amount = item_list[i*2+0] = 1; + } + + if( nd->master_nd ) + {// Script-controlled shops decide by themselves, what can be bought and for what price. + continue; + } + + switch( pc_checkadditem(sd,nameid,amount) ) + { + case ADDITEM_EXIST: + break; + + case ADDITEM_NEW: + new_++; + break; + + case ADDITEM_OVERAMOUNT: + return 2; + } + + value = pc_modifybuyvalue(sd,value); + + z += (double)value * amount; + w += itemdb_weight(nameid) * amount; + } + + if( nd->master_nd != NULL ) //Script-based shops. + return npc_buylist_sub(sd,n,item_list,nd->master_nd); + + if( z > (double)sd->status.zeny ) + return 1; // Not enough Zeny + if( w + sd->weight > sd->max_weight ) + return 2; // Too heavy + if( pc_inventoryblank(sd) < new_ ) + return 3; // Not enough space to store items + + pc_payzeny(sd,(int)z,LOG_TYPE_NPC, NULL); + + for( i = 0; i < n; ++i ) + { + int nameid = item_list[i*2+1]; + int amount = item_list[i*2+0]; + struct item item_tmp; + + if (itemdb_type(nameid) == IT_PETEGG) + pet_create_egg(sd, nameid); + else + { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = nameid; + item_tmp.identify = 1; + + pc_additem(sd,&item_tmp,amount,LOG_TYPE_NPC); + } + } + + // custom merchant shop exp bonus + if( battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0 ) + { + if( sd->status.skill[MC_DISCOUNT].flag >= SKILL_FLAG_REPLACED_LV_0 ) + skill = sd->status.skill[MC_DISCOUNT].flag - SKILL_FLAG_REPLACED_LV_0; + + if( skill > 0 ) + { + z = z * (double)skill * (double)battle_config.shop_exp/10000.; + if( z < 1 ) + z = 1; + pc_gainexp(sd,NULL,0,(int)z, false); + } + } + + return 0; +} + + +/// npc_selllist for script-controlled shops +static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd) +{ + char npc_ev[EVENT_NAME_LENGTH]; + char card_slot[NAME_LENGTH]; + int i, j, idx; + int key_nameid = 0; + int key_amount = 0; + int key_refine = 0; + int key_attribute = 0; + int key_identify = 0; + int key_card[MAX_SLOTS]; + + // discard old contents + script_cleararray_pc(sd, "@sold_nameid", (void*)0); + script_cleararray_pc(sd, "@sold_quantity", (void*)0); + script_cleararray_pc(sd, "@sold_refine", (void*)0); + script_cleararray_pc(sd, "@sold_attribute", (void*)0); + script_cleararray_pc(sd, "@sold_identify", (void*)0); + + for( j = 0; j < MAX_SLOTS; j++ ) + {// clear each of the card slot entries + key_card[j] = 0; + snprintf(card_slot, sizeof(card_slot), "@sold_card%d", j + 1); + script_cleararray_pc(sd, card_slot, (void*)0); + } + + // save list of to be sold items + for( i = 0; i < n; i++ ) + { + idx = item_list[i*2]-2; + + script_setarray_pc(sd, "@sold_nameid", i, (void*)(intptr_t)sd->status.inventory[idx].nameid, &key_nameid); + script_setarray_pc(sd, "@sold_quantity", i, (void*)(intptr_t)item_list[i*2+1], &key_amount); + + if( itemdb_isequip(sd->status.inventory[idx].nameid) ) + {// process equipment based information into the arrays + script_setarray_pc(sd, "@sold_refine", i, (void*)(intptr_t)sd->status.inventory[idx].refine, &key_refine); + script_setarray_pc(sd, "@sold_attribute", i, (void*)(intptr_t)sd->status.inventory[idx].attribute, &key_attribute); + script_setarray_pc(sd, "@sold_identify", i, (void*)(intptr_t)sd->status.inventory[idx].identify, &key_identify); + + for( j = 0; j < MAX_SLOTS; j++ ) + {// store each of the cards from the equipment in the array + snprintf(card_slot, sizeof(card_slot), "@sold_card%d", j + 1); + script_setarray_pc(sd, card_slot, i, (void*)(intptr_t)sd->status.inventory[idx].card[j], &key_card[j]); + } + } + } + + // invoke event + snprintf(npc_ev, ARRAYLENGTH(npc_ev), "%s::OnSellItem", nd->exname); + npc_event(sd, npc_ev, 0); + return 0; +} + + +/// Player item selling to npc shop. +/// +/// @param item_list 'n' pairs <index,amount> +/// @return result code for clif_parse_NpcSellListSend +int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) +{ + double z; + int i,skill; + struct npc_data *nd; + + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + if( ( nd = npc_checknear(sd, map_id2bl(sd->npc_shopid)) ) == NULL || nd->subtype != SHOP ) + { + return 1; + } + + z = 0; + + // verify the sell list + for( i = 0; i < n; i++ ) + { + int nameid, amount, idx, value; + + idx = item_list[i*2]-2; + amount = item_list[i*2+1]; + + if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) + { + return 1; + } + + nameid = sd->status.inventory[idx].nameid; + + if( !nameid || !sd->inventory_data[idx] || sd->status.inventory[idx].amount < amount ) + { + return 1; + } + + if( nd->master_nd ) + {// Script-controlled shops decide by themselves, what can be sold and at what price. + continue; + } + + value = pc_modifysellvalue(sd, sd->inventory_data[idx]->value_sell); + + z+= (double)value*amount; + } + + if( nd->master_nd ) + {// Script-controlled shops + return npc_selllist_sub(sd, n, item_list, nd->master_nd); + } + + // delete items + for( i = 0; i < n; i++ ) + { + int amount, idx; + + idx = item_list[i*2]-2; + amount = item_list[i*2+1]; + + if( sd->inventory_data[idx]->type == IT_PETEGG && sd->status.inventory[idx].card[0] == CARD0_PET ) + { + if( search_petDB_index(sd->status.inventory[idx].nameid, PET_EGG) >= 0 ) + { + intif_delete_petdata(MakeDWord(sd->status.inventory[idx].card[1], sd->status.inventory[idx].card[2])); + } + } + + pc_delitem(sd, idx, amount, 0, 6, LOG_TYPE_NPC); + } + + if( z > MAX_ZENY ) + z = MAX_ZENY; + + pc_getzeny(sd, (int)z, LOG_TYPE_NPC, NULL); + + // custom merchant shop exp bonus + if( battle_config.shop_exp > 0 && z > 0 && ( skill = pc_checkskill(sd,MC_OVERCHARGE) ) > 0) + { + if( sd->status.skill[MC_OVERCHARGE].flag >= SKILL_FLAG_REPLACED_LV_0 ) + skill = sd->status.skill[MC_OVERCHARGE].flag - SKILL_FLAG_REPLACED_LV_0; + + if( skill > 0 ) + { + z = z * (double)skill * (double)battle_config.shop_exp/10000.; + if( z < 1 ) + z = 1; + pc_gainexp(sd, NULL, 0, (int)z, false); + } + } + + return 0; +} + +//Atempt to remove an npc from a map +//This doesn't remove it from map_db +int npc_remove_map(struct npc_data* nd) +{ + int16 m,i; + nullpo_retr(1, nd); + + if(nd->bl.prev == NULL || nd->bl.m < 0) + return 1; //Not assigned to a map. + m = nd->bl.m; + clif_clearunit_area(&nd->bl,CLR_RESPAWN); + npc_unsetcells(nd); + map_delblock(&nd->bl); + //Remove npc from map[].npc list. [Skotlex] + ARR_FIND( 0, map[m].npc_num, i, map[m].npc[i] == nd ); + if( i == map[m].npc_num ) return 2; //failed to find it? + + map[m].npc_num--; + map[m].npc[i] = map[m].npc[map[m].npc_num]; + map[m].npc[map[m].npc_num] = NULL; + return 0; +} + +/** + * @see DBApply + */ +static int npc_unload_ev(DBKey key, DBData *data, va_list ap) +{ + struct event_data* ev = db_data2ptr(data); + char* npcname = va_arg(ap, char *); + + if(strcmp(ev->nd->exname,npcname)==0){ + db_remove(ev_db, key); + return 1; + } + return 0; +} + +//Chk if npc matches src_id, then unload. +//Sub-function used to find duplicates. +static int npc_unload_dup_sub(struct npc_data* nd, va_list args) +{ + int src_id; + + src_id = va_arg(args, int); + if (nd->src_id == src_id) + npc_unload(nd, true); + return 0; +} + +//Removes all npcs that are duplicates of the passed one. [Skotlex] +void npc_unload_duplicates(struct npc_data* nd) +{ + map_foreachnpc(npc_unload_dup_sub,nd->bl.id); +} + +//Removes an npc from map and db. +//Single is to free name (for duplicates). +int npc_unload(struct npc_data* nd, bool single) { + nullpo_ret(nd); + + npc_remove_map(nd); + map_deliddb(&nd->bl); + if( single ) + strdb_remove(npcname_db, nd->exname); + + if (nd->chat_id) // remove npc chatroom object and kick users + chat_deletenpcchat(nd); + +#ifdef PCRE_SUPPORT + npc_chat_finalize(nd); // deallocate npc PCRE data structures +#endif + + if( single && nd->path ) { + struct npc_path_data* npd = NULL; + if( nd->path && nd->path != npc_last_ref ) { + npd = strdb_get(npc_path_db, nd->path); + } + + if( npd && --npd->references == 0 ) { + strdb_remove(npc_path_db, nd->path);/* remove from db */ + aFree(nd->path);/* remove now that no other instances exist */ + } + } + + if( (nd->subtype == SHOP || nd->subtype == CASHSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao] + aFree(nd->u.shop.shop_item); + else if( nd->subtype == SCRIPT ) { + struct s_mapiterator* iter; + struct block_list* bl; + + if( single ) + ev_db->foreach(ev_db,npc_unload_ev,nd->exname); //Clean up all events related + + iter = mapit_geteachpc(); + for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) { + struct map_session_data *sd = ((TBL_PC*)bl); + if( sd && sd->npc_timer_id != INVALID_TIMER ) { + const struct TimerData *td = get_timer(sd->npc_timer_id); + + if( td && td->id != nd->bl.id ) + continue; + + if( td && td->data ) + ers_free(timer_event_ers, (void*)td->data); + delete_timer(sd->npc_timer_id, npc_timerevent); + sd->npc_timer_id = INVALID_TIMER; + } + } + mapit_free(iter); + + if (nd->u.scr.timerid != INVALID_TIMER) { + const struct TimerData *td = NULL; + td = get_timer(nd->u.scr.timerid); + if (td && td->data) + ers_free(timer_event_ers, (void*)td->data); + delete_timer(nd->u.scr.timerid, npc_timerevent); + } + if (nd->u.scr.timer_event) + aFree(nd->u.scr.timer_event); + if (nd->src_id == 0) { + if(nd->u.scr.script) { + script_free_code(nd->u.scr.script); + nd->u.scr.script = NULL; + } + if (nd->u.scr.label_list) { + aFree(nd->u.scr.label_list); + nd->u.scr.label_list = NULL; + nd->u.scr.label_list_num = 0; + } + } + if( nd->u.scr.guild_id ) + guild_flag_remove(nd); + } + + script_stop_sleeptimers(nd->bl.id); + + aFree(nd); + + return 0; +} + +// +// NPC Source Files +// + +/// Clears the npc source file list +static void npc_clearsrcfile(void) +{ + struct npc_src_list* file = npc_src_files; + struct npc_src_list* file_tofree; + + while( file != NULL ) + { + file_tofree = file; + file = file->next; + aFree(file_tofree); + } + npc_src_files = NULL; +} + +/// Adds a npc source file (or removes all) +void npc_addsrcfile(const char* name) +{ + struct npc_src_list* file; + struct npc_src_list* file_prev = NULL; + + if( strcmpi(name, "clear") == 0 ) + { + npc_clearsrcfile(); + return; + } + + // prevent multiple insert of source files + file = npc_src_files; + while( file != NULL ) + { + if( strcmp(name, file->name) == 0 ) + return;// found the file, no need to insert it again + file_prev = file; + file = file->next; + } + + file = (struct npc_src_list*)aMalloc(sizeof(struct npc_src_list) + strlen(name)); + file->next = NULL; + strncpy(file->name, name, strlen(name) + 1); + if( file_prev == NULL ) + npc_src_files = file; + else + file_prev->next = file; +} + +/// Removes a npc source file (or all) +void npc_delsrcfile(const char* name) +{ + struct npc_src_list* file = npc_src_files; + struct npc_src_list* file_prev = NULL; + + if( strcmpi(name, "all") == 0 ) + { + npc_clearsrcfile(); + return; + } + + while( file != NULL ) + { + if( strcmp(file->name, name) == 0 ) + { + if( npc_src_files == file ) + npc_src_files = file->next; + else + file_prev->next = file->next; + aFree(file); + break; + } + file_prev = file; + file = file->next; + } +} + +/// Parses and sets the name and exname of a npc. +/// Assumes that m, x and y are already set in nd. +static void npc_parsename(struct npc_data* nd, const char* name, const char* start, const char* buffer, const char* filepath) +{ + const char* p; + struct npc_data* dnd;// duplicate npc + char newname[NAME_LENGTH]; + + // parse name + p = strstr(name,"::"); + if( p ) { // <Display name>::<Unique name> + size_t len = p-name; + if( len > NAME_LENGTH ) { + ShowWarning("npc_parsename: Display name of '%s' is too long (len=%u) in file '%s', line'%d'. Truncating to %u characters.\n", name, (unsigned int)len, filepath, strline(buffer,start-buffer), NAME_LENGTH); + safestrncpy(nd->name, name, sizeof(nd->name)); + } else { + memcpy(nd->name, name, len); + memset(nd->name+len, 0, sizeof(nd->name)-len); + } + len = strlen(p+2); + if( len > NAME_LENGTH ) + ShowWarning("npc_parsename: Unique name of '%s' is too long (len=%u) in file '%s', line'%d'. Truncating to %u characters.\n", name, (unsigned int)len, filepath, strline(buffer,start-buffer), NAME_LENGTH); + safestrncpy(nd->exname, p+2, sizeof(nd->exname)); + } else {// <Display name> + size_t len = strlen(name); + if( len > NAME_LENGTH ) + ShowWarning("npc_parsename: Name '%s' is too long (len=%u) in file '%s', line'%d'. Truncating to %u characters.\n", name, (unsigned int)len, filepath, strline(buffer,start-buffer), NAME_LENGTH); + safestrncpy(nd->name, name, sizeof(nd->name)); + safestrncpy(nd->exname, name, sizeof(nd->exname)); + } + + if( *nd->exname == '\0' || strstr(nd->exname,"::") != NULL ) {// invalid + snprintf(newname, ARRAYLENGTH(newname), "0_%d_%d_%d", nd->bl.m, nd->bl.x, nd->bl.y); + ShowWarning("npc_parsename: Invalid unique name in file '%s', line'%d'. Renaming '%s' to '%s'.\n", filepath, strline(buffer,start-buffer), nd->exname, newname); + safestrncpy(nd->exname, newname, sizeof(nd->exname)); + } + + if( (dnd=npc_name2id(nd->exname)) != NULL ) {// duplicate unique name, generate new one + char this_mapname[32]; + char other_mapname[32]; + int i = 0; + + do { + ++i; + snprintf(newname, ARRAYLENGTH(newname), "%d_%d_%d_%d", i, nd->bl.m, nd->bl.x, nd->bl.y); + } while( npc_name2id(newname) != NULL ); + + strcpy(this_mapname, (nd->bl.m==-1?"(not on a map)":mapindex_id2name(map[nd->bl.m].index))); + strcpy(other_mapname, (dnd->bl.m==-1?"(not on a map)":mapindex_id2name(map[dnd->bl.m].index))); + + ShowWarning("npc_parsename: Duplicate unique name in file '%s', line'%d'. Renaming '%s' to '%s'.\n", filepath, strline(buffer,start-buffer), nd->exname, newname); + ShowDebug("this npc:\n display name '%s'\n unique name '%s'\n map=%s, x=%d, y=%d\n", nd->name, nd->exname, this_mapname, nd->bl.x, nd->bl.y); + ShowDebug("other npc in '%s' :\n display name '%s'\n unique name '%s'\n map=%s, x=%d, y=%d\n",dnd->path, dnd->name, dnd->exname, other_mapname, dnd->bl.x, dnd->bl.y); + safestrncpy(nd->exname, newname, sizeof(nd->exname)); + } + + if( npc_last_path != filepath ) { + struct npc_path_data * npd = NULL; + + if( !(npd = strdb_get(npc_path_db,filepath) ) ) { + CREATE(npd, struct npc_path_data, 1); + strdb_put(npc_path_db, filepath, npd); + + CREATE(npd->path, char, strlen(filepath)+1); + safestrncpy(npd->path, filepath, strlen(filepath)+1); + + npd->references = 0; + } + + nd->path = npd->path; + npd->references++; + + npc_last_npd = npd; + npc_last_ref = npd->path; + npc_last_path = (char*) filepath; + } else { + nd->path = npc_last_ref; + if( npc_last_npd ) + npc_last_npd->references++; + } +} + +//Add then display an npc warp on map +struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y) +{ + int i, flag = 0; + struct npc_data *nd; + + CREATE(nd, struct npc_data, 1); + nd->bl.id = npc_get_new_npc_id(); + map_addnpc(from_mapid, nd); + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = from_mapid; + nd->bl.x = from_x; + nd->bl.y = from_y; + + safestrncpy(nd->exname, name, ARRAYLENGTH(nd->exname)); + if (npc_name2id(nd->exname) != NULL) + flag = 1; + + if (flag == 1) + snprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp_%d_%d_%d", from_mapid, from_x, from_y); + + for( i = 0; npc_name2id(nd->exname) != NULL; ++i ) + snprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp%d_%d_%d_%d", i, from_mapid, from_x, from_y); + safestrncpy(nd->name, nd->exname, ARRAYLENGTH(nd->name)); + + if( battle_config.warp_point_debug ) + nd->class_ = WARP_DEBUG_CLASS; + else + nd->class_ = WARP_CLASS; + nd->speed = 200; + + nd->u.warp.mapindex = to_mapindex; + nd->u.warp.x = to_x; + nd->u.warp.y = to_y; + nd->u.warp.xs = xs; + nd->u.warp.ys = xs; + nd->bl.type = BL_NPC; + nd->subtype = WARP; + npc_setcells(nd); + map_addblock(&nd->bl); + status_set_viewdata(&nd->bl, nd->class_); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl); + strdb_put(npcname_db, nd->exname, nd); + + return nd; +} + +/// Parses a warp npc. +static const char* npc_parse_warp(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + int x, y, xs, ys, to_x, to_y, m; + unsigned short i; + char mapname[32], to_mapname[32]; + struct npc_data *nd; + + // w1=<from map name>,<fromX>,<fromY>,<facing> + // w4=<spanx>,<spany>,<to map name>,<toX>,<toY> + if( sscanf(w1, "%31[^,],%d,%d", mapname, &x, &y) != 3 + || sscanf(w4, "%d,%d,%31[^,],%d,%d", &xs, &ys, to_mapname, &to_x, &to_y) != 5 ) + { + ShowError("npc_parse_warp: Invalid warp definition in file '%s', line '%d'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + + m = map_mapname2mapid(mapname); + i = mapindex_name2id(to_mapname); + if( i == 0 ) + { + ShowError("npc_parse_warp: Unknown destination map in file '%s', line '%d' : %s\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), to_mapname, w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + + CREATE(nd, struct npc_data, 1); + + nd->bl.id = npc_get_new_npc_id(); + map_addnpc(m, nd); + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + npc_parsename(nd, w3, start, buffer, filepath); + + if (!battle_config.warp_point_debug) + nd->class_ = WARP_CLASS; + else + nd->class_ = WARP_DEBUG_CLASS; + nd->speed = 200; + + nd->u.warp.mapindex = i; + nd->u.warp.x = to_x; + nd->u.warp.y = to_y; + nd->u.warp.xs = xs; + nd->u.warp.ys = ys; + npc_warp++; + nd->bl.type = BL_NPC; + nd->subtype = WARP; + npc_setcells(nd); + map_addblock(&nd->bl); + status_set_viewdata(&nd->bl, nd->class_); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl); + strdb_put(npcname_db, nd->exname, nd); + + return strchr(start,'\n');// continue +} + +/// Parses a shop/cashshop npc. +static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + //TODO: could be rewritten to NOT need this temp array [ultramage] + #define MAX_SHOPITEM 100 + struct npc_item_list items[MAX_SHOPITEM]; + char *p; + int x, y, dir, m, i; + struct npc_data *nd; + enum npc_subtype type; + + if( strcmp(w1,"-") == 0 ) + {// 'floating' shop? + x = y = dir = 0; + m = -1; + } + else + {// w1=<map name>,<x>,<y>,<facing> + char mapname[32]; + if( sscanf(w1, "%31[^,],%d,%d,%d", mapname, &x, &y, &dir) != 4 + || strchr(w4, ',') == NULL ) + { + ShowError("npc_parse_shop: Invalid shop definition in file '%s', line '%d'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + + m = map_mapname2mapid(mapname); + } + + if( !strcasecmp(w2,"cashshop") ) + type = CASHSHOP; + else + type = SHOP; + + p = strchr(w4,','); + for( i = 0; i < ARRAYLENGTH(items) && p; ++i ) + { + int nameid, value; + struct item_data* id; + if( sscanf(p, ",%d:%d", &nameid, &value) != 2 ) + { + ShowError("npc_parse_shop: Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + break; + } + + if( (id = itemdb_exists(nameid)) == NULL ) + { + ShowWarning("npc_parse_shop: Invalid sell item in file '%s', line '%d' (id '%d').\n", filepath, strline(buffer,start-buffer), nameid); + p = strchr(p+1,','); + continue; + } + + if( value < 0 ) + { + if( type == SHOP ) value = id->value_buy; + else value = 0; // Cashshop doesn't have a "buy price" in the item_db + } + + if( type == SHOP && value == 0 ) + { // NPC selling items for free! + ShowWarning("npc_parse_shop: Item %s [%d] is being sold for FREE in file '%s', line '%d'.\n", + id->name, nameid, filepath, strline(buffer,start-buffer)); + } + if( type == SHOP && value*0.75 < id->value_sell*1.24 ) + {// Exploit possible: you can buy and sell back with profit + ShowWarning("npc_parse_shop: Item %s [%d] discounted buying price (%d->%d) is less than overcharged selling price (%d->%d) at file '%s', line '%d'.\n", + id->name, nameid, value, (int)(value*0.75), id->value_sell, (int)(id->value_sell*1.24), filepath, strline(buffer,start-buffer)); + } + //for logs filters, atcommands and iteminfo script command + if( id->maxchance == 0 ) + id->maxchance = -1; // -1 would show that the item's sold in NPC Shop + + items[i].nameid = nameid; + items[i].value = value; + p = strchr(p+1,','); + } + if( i == 0 ) + { + ShowWarning("npc_parse_shop: Ignoring empty shop in file '%s', line '%d'.\n", filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// continue + } + + CREATE(nd, struct npc_data, 1); + CREATE(nd->u.shop.shop_item, struct npc_item_list, i); + memcpy(nd->u.shop.shop_item, items, sizeof(struct npc_item_list)*i); + nd->u.shop.count = i; + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + nd->bl.id = npc_get_new_npc_id(); + npc_parsename(nd, w3, start, buffer, filepath); + nd->class_ = m==-1?-1:atoi(w4); + nd->speed = 200; + + ++npc_shop; + nd->bl.type = BL_NPC; + nd->subtype = type; + if( m >= 0 ) + {// normal shop npc + map_addnpc(m,nd); + map_addblock(&nd->bl); + status_set_viewdata(&nd->bl, nd->class_); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + nd->ud.dir = dir; + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl); + } else + {// 'floating' shop? + map_addiddb(&nd->bl); + } + strdb_put(npcname_db, nd->exname, nd); + + return strchr(start,'\n');// continue +} + +/** + * NPC other label + * Not sure, seem to add label in a chainlink + * @see DBApply + */ +int npc_convertlabel_db(DBKey key, DBData *data, va_list ap) +{ + const char* lname = (const char*)key.str; + int lpos = db_data2i(data); + struct npc_label_list** label_list; + int* label_list_num; + const char* filepath; + struct npc_label_list* label; + const char *p; + int len; + + nullpo_ret(label_list = va_arg(ap,struct npc_label_list**)); + nullpo_ret(label_list_num = va_arg(ap,int*)); + nullpo_ret(filepath = va_arg(ap,const char*)); + + // In case of labels not terminated with ':', for user defined function support + p = lname; + while( ISALNUM(*p) || *p == '_' ) + ++p; + len = p-lname; + + // here we check if the label fit into the buffer + if( len > 23 ) + { + ShowError("npc_parse_script: label name longer than 23 chars! '%s'\n (%s)", lname, filepath); + return 0; + } + + if( *label_list == NULL ) + { + *label_list = (struct npc_label_list *) aCalloc (1, sizeof(struct npc_label_list)); + *label_list_num = 0; + } else + *label_list = (struct npc_label_list *) aRealloc (*label_list, sizeof(struct npc_label_list)*(*label_list_num+1)); + label = *label_list+*label_list_num; + + safestrncpy(label->name, lname, sizeof(label->name)); + label->pos = lpos; + ++(*label_list_num); + + return 0; +} + +// Skip the contents of a script. +static const char* npc_skip_script(const char* start, const char* buffer, const char* filepath) +{ + const char* p; + int curly_count; + + if( start == NULL ) + return NULL;// nothing to skip + + // initial bracket (assumes the previous part is ok) + p = strchr(start,'{'); + if( p == NULL ) + { + ShowError("npc_skip_script: Missing left curly in file '%s', line'%d'.", filepath, strline(buffer,start-buffer)); + return NULL;// can't continue + } + + // skip everything + for( curly_count = 1; curly_count > 0 ; ) + { + p = skip_space(p+1) ; + if( *p == '}' ) + {// right curly + --curly_count; + } + else if( *p == '{' ) + {// left curly + ++curly_count; + } + else if( *p == '"' ) + {// string + for( ++p; *p != '"' ; ++p ) + { + if( *p == '\\' && (unsigned char)p[-1] <= 0x7e ) + ++p;// escape sequence (not part of a multibyte character) + else if( *p == '\0' ) + { + script_error(buffer, filepath, 0, "Unexpected end of string.", p); + return NULL;// can't continue + } + else if( *p == '\n' ) + { + script_error(buffer, filepath, 0, "Unexpected newline at string.", p); + return NULL;// can't continue + } + } + } + else if( *p == '\0' ) + {// end of buffer + ShowError("Missing %d right curlys at file '%s', line '%d'.\n", curly_count, filepath, strline(buffer,p-buffer)); + return NULL;// can't continue + } + } + + return p+1;// return after the last '}' +} + +/// Parses a npc script. +/// +/// -%TAB%script%TAB%<NPC Name>%TAB%-1,{<code>} +/// <map name>,<x>,<y>,<facing>%TAB%script%TAB%<NPC Name>%TAB%<sprite id>,{<code>} +/// <map name>,<x>,<y>,<facing>%TAB%script%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>,{<code>} +static const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath, bool runOnInit) { + int x, y, dir = 0, m, xs = 0, ys = 0, class_ = 0; // [Valaris] thanks to fov + char mapname[32]; + struct script_code *script; + int i; + const char* end; + const char* script_start; + + struct npc_label_list* label_list; + int label_list_num; + struct npc_data* nd; + + if( strcmp(w1, "-") == 0 ) + {// floating npc + x = 0; + y = 0; + m = -1; + } + else + {// npc in a map + if( sscanf(w1, "%31[^,],%d,%d,%d", mapname, &x, &y, &dir) != 4 ) + { + ShowError("npc_parse_script: Invalid placement format for a script in file '%s', line '%d'. Skipping the rest of file...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return NULL;// unknown format, don't continue + } + m = map_mapname2mapid(mapname); + } + + script_start = strstr(start,",{"); + end = strchr(start,'\n'); + if( strstr(w4,",{") == NULL || script_start == NULL || (end != NULL && script_start > end) ) + { + ShowError("npc_parse_script: Missing left curly ',{' in file '%s', line '%d'. Skipping the rest of the file.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return NULL;// can't continue + } + ++script_start; + + end = npc_skip_script(script_start, buffer, filepath); + if( end == NULL ) + return NULL;// (simple) parse error, don't continue + + script = parse_script(script_start, filepath, strline(buffer,script_start-buffer), SCRIPT_USE_LABEL_DB); + label_list = NULL; + label_list_num = 0; + if( script ) + { + DBMap* label_db = script_get_label_db(); + label_db->foreach(label_db, npc_convertlabel_db, &label_list, &label_list_num, filepath); + db_clear(label_db); // not needed anymore, so clear the db + } + + CREATE(nd, struct npc_data, 1); + + if( sscanf(w4, "%d,%d,%d", &class_, &xs, &ys) == 3 ) + {// OnTouch area defined + nd->u.scr.xs = xs; + nd->u.scr.ys = ys; + } + else + {// no OnTouch area + class_ = atoi(w4); + nd->u.scr.xs = -1; + nd->u.scr.ys = -1; + } + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + npc_parsename(nd, w3, start, buffer, filepath); + nd->bl.id = npc_get_new_npc_id(); + nd->class_ = class_; + nd->speed = 200; + nd->u.scr.script = script; + nd->u.scr.label_list = label_list; + nd->u.scr.label_list_num = label_list_num; + + ++npc_script; + nd->bl.type = BL_NPC; + nd->subtype = SCRIPT; + + if( m >= 0 ) + { + map_addnpc(m, nd); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + nd->ud.dir = dir; + npc_setcells(nd); + map_addblock(&nd->bl); + if( class_ >= 0 ) + { + status_set_viewdata(&nd->bl, nd->class_); + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl); + } + } + else + { + // we skip map_addnpc, but still add it to the list of ID's + map_addiddb(&nd->bl); + } + strdb_put(npcname_db, nd->exname, nd); + + //----------------------------------------- + // Loop through labels to export them as necessary + for (i = 0; i < nd->u.scr.label_list_num; i++) { + if (npc_event_export(nd, i)) { + ShowWarning("npc_parse_script : duplicate event %s::%s (%s)\n", + nd->exname, nd->u.scr.label_list[i].name, filepath); + } + npc_timerevent_export(nd, i); + } + + nd->u.scr.timerid = INVALID_TIMER; + + if( runOnInit ) { + char evname[EVENT_NAME_LENGTH]; + struct event_data *ev; + + snprintf(evname, ARRAYLENGTH(evname), "%s::OnInit", nd->exname); + + if( ( ev = (struct event_data*)strdb_get(ev_db, evname) ) ) { + + //Execute OnInit + run_script(nd->u.scr.script,ev->pos,0,nd->bl.id); + + } + } + + return end; +} + +/// Duplicate a warp, shop, cashshop or script. [Orcao] +/// warp: <map name>,<x>,<y>,<facing>%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<spanx>,<spany> +/// shop/cashshop/npc: -%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id> +/// shop/cashshop/npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id> +/// npc: -%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY> +/// npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY> +const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + int x, y, dir, m, xs = -1, ys = -1, class_ = 0; + char mapname[32]; + char srcname[128]; + int i; + const char* end; + size_t length; + + int src_id; + int type; + struct npc_data* nd; + struct npc_data* dnd; + + end = strchr(start,'\n'); + length = strlen(w2); + + // get the npc being duplicated + if( w2[length-1] != ')' || length <= 11 || length-11 >= sizeof(srcname) ) + {// does not match 'duplicate(%127s)', name is empty or too long + ShowError("npc_parse_script: bad duplicate name in file '%s', line '%d' : %s\n", filepath, strline(buffer,start-buffer), w2); + return end;// next line, try to continue + } + safestrncpy(srcname, w2+10, length-10); + + dnd = npc_name2id(srcname); + if( dnd == NULL) { + ShowError("npc_parse_script: original npc not found for duplicate in file '%s', line '%d' : %s\n", filepath, strline(buffer,start-buffer), srcname); + return end;// next line, try to continue + } + src_id = dnd->bl.id; + type = dnd->subtype; + + // get placement + if( (type==SHOP || type==CASHSHOP || type==SCRIPT) && strcmp(w1, "-") == 0 ) + {// floating shop/chashshop/script + x = y = dir = 0; + m = -1; + } + else + { + if( sscanf(w1, "%31[^,],%d,%d,%d", mapname, &x, &y, &dir) != 4 )// <map name>,<x>,<y>,<facing> + { + ShowError("npc_parse_duplicate: Invalid placement format for duplicate in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return end;// next line, try to continue + } + m = map_mapname2mapid(mapname); + } + + if( type == WARP && sscanf(w4, "%d,%d", &xs, &ys) == 2 );// <spanx>,<spany> + else if( type == SCRIPT && sscanf(w4, "%d,%d,%d", &class_, &xs, &ys) == 3);// <sprite id>,<triggerX>,<triggerY> + else if( type != WARP ) class_ = atoi(w4);// <sprite id> + else + { + ShowError("npc_parse_duplicate: Invalid span format for duplicate warp in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return end;// next line, try to continue + } + + CREATE(nd, struct npc_data, 1); + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + npc_parsename(nd, w3, start, buffer, filepath); + nd->bl.id = npc_get_new_npc_id(); + nd->class_ = class_; + nd->speed = 200; + nd->src_id = src_id; + nd->bl.type = BL_NPC; + nd->subtype = (enum npc_subtype)type; + switch( type ) + { + case SCRIPT: + ++npc_script; + nd->u.scr.xs = xs; + nd->u.scr.ys = ys; + nd->u.scr.script = dnd->u.scr.script; + nd->u.scr.label_list = dnd->u.scr.label_list; + nd->u.scr.label_list_num = dnd->u.scr.label_list_num; + break; + + case SHOP: + case CASHSHOP: + ++npc_shop; + nd->u.shop.shop_item = dnd->u.shop.shop_item; + nd->u.shop.count = dnd->u.shop.count; + break; + + case WARP: + ++npc_warp; + if( !battle_config.warp_point_debug ) + nd->class_ = WARP_CLASS; + else + nd->class_ = WARP_DEBUG_CLASS; + nd->u.warp.xs = xs; + nd->u.warp.ys = ys; + nd->u.warp.mapindex = dnd->u.warp.mapindex; + nd->u.warp.x = dnd->u.warp.x; + nd->u.warp.y = dnd->u.warp.y; + break; + } + + //Add the npc to its location + if( m >= 0 ) + { + map_addnpc(m, nd); + status_change_init(&nd->bl); + unit_dataset(&nd->bl); + nd->ud.dir = dir; + npc_setcells(nd); + map_addblock(&nd->bl); + if( class_ >= 0 ) + { + status_set_viewdata(&nd->bl, nd->class_); + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl); + } + } + else + { + // we skip map_addnpc, but still add it to the list of ID's + map_addiddb(&nd->bl); + } + strdb_put(npcname_db, nd->exname, nd); + + if( type != SCRIPT ) + return end; + + //----------------------------------------- + // Loop through labels to export them as necessary + for (i = 0; i < nd->u.scr.label_list_num; i++) { + if (npc_event_export(nd, i)) { + ShowWarning("npc_parse_duplicate : duplicate event %s::%s (%s)\n", + nd->exname, nd->u.scr.label_list[i].name, filepath); + } + npc_timerevent_export(nd, i); + } + + nd->u.scr.timerid = INVALID_TIMER; + + return end; +} + +int npc_duplicate4instance(struct npc_data *snd, int16 m) { + char newname[NAME_LENGTH]; + + if( map[m].instance_id == 0 ) + return 1; + + snprintf(newname, ARRAYLENGTH(newname), "dup_%d_%d", map[m].instance_id, snd->bl.id); + if( npc_name2id(newname) != NULL ) + { // Name already in use + ShowError("npc_duplicate4instance: the npcname (%s) is already in use while trying to duplicate npc %s in instance %d.\n", newname, snd->exname, map[m].instance_id); + return 1; + } + + if( snd->subtype == WARP ) + { // Adjust destination, if instanced + struct npc_data *wnd = NULL; // New NPC + int dm = map_mapindex2mapid(snd->u.warp.mapindex), im; + if( dm < 0 ) return 1; + + im = instance_mapid2imapid(dm, map[m].instance_id); + if( im == -1 ) + { + ShowError("npc_duplicate4instance: warp (%s) leading to instanced map (%s), but instance map is not attached to current instance.\n", map[dm].name, snd->exname); + return 1; + } + + CREATE(wnd, struct npc_data, 1); + wnd->bl.id = npc_get_new_npc_id(); + map_addnpc(m, wnd); + wnd->bl.prev = wnd->bl.next = NULL; + wnd->bl.m = m; + wnd->bl.x = snd->bl.x; + wnd->bl.y = snd->bl.y; + safestrncpy(wnd->name, "", ARRAYLENGTH(wnd->name)); + safestrncpy(wnd->exname, newname, ARRAYLENGTH(wnd->exname)); + wnd->class_ = WARP_CLASS; + wnd->speed = 200; + wnd->u.warp.mapindex = map_id2index(im); + wnd->u.warp.x = snd->u.warp.x; + wnd->u.warp.y = snd->u.warp.y; + wnd->u.warp.xs = snd->u.warp.xs; + wnd->u.warp.ys = snd->u.warp.ys; + wnd->bl.type = BL_NPC; + wnd->subtype = WARP; + npc_setcells(wnd); + map_addblock(&wnd->bl); + status_set_viewdata(&wnd->bl, wnd->class_); + status_change_init(&wnd->bl); + unit_dataset(&wnd->bl); + if( map[wnd->bl.m].users ) + clif_spawn(&wnd->bl); + strdb_put(npcname_db, wnd->exname, wnd); + } + else + { + static char w1[50], w2[50], w3[50], w4[50]; + const char* stat_buf = "- call from instancing subsystem -\n"; + + snprintf(w1, sizeof(w1), "%s,%d,%d,%d", map[m].name, snd->bl.x, snd->bl.y, snd->ud.dir); + snprintf(w2, sizeof(w2), "duplicate(%s)", snd->exname); + snprintf(w3, sizeof(w3), "%s::%s", snd->name, newname); + + if( snd->u.scr.xs >= 0 && snd->u.scr.ys >= 0 ) + snprintf(w4, sizeof(w4), "%d,%d,%d", snd->class_, snd->u.scr.xs, snd->u.scr.ys); // Touch Area + else + snprintf(w4, sizeof(w4), "%d", snd->class_); + + npc_parse_duplicate(w1, w2, w3, w4, stat_buf, stat_buf, "INSTANCING"); + } + + return 0; +} + +//Set mapcell CELL_NPC to trigger event later +void npc_setcells(struct npc_data* nd) +{ + int16 m = nd->bl.m, x = nd->bl.x, y = nd->bl.y, xs, ys; + int i,j; + + switch(nd->subtype) + { + case WARP: + xs = nd->u.warp.xs; + ys = nd->u.warp.ys; + break; + case SCRIPT: + xs = nd->u.scr.xs; + ys = nd->u.scr.ys; + break; + default: + return; // Other types doesn't have touch area + } + + if (m < 0 || xs < 0 || ys < 0) //invalid range or map + return; + + for (i = y-ys; i <= y+ys; i++) { + for (j = x-xs; j <= x+xs; j++) { + if (map_getcell(m, j, i, CELL_CHKNOPASS)) + continue; + map_setcell(m, j, i, CELL_NPC, true); + } + } +} + +int npc_unsetcells_sub(struct block_list* bl, va_list ap) +{ + struct npc_data *nd = (struct npc_data*)bl; + int id = va_arg(ap,int); + if (nd->bl.id == id) return 0; + npc_setcells(nd); + return 1; +} + +void npc_unsetcells(struct npc_data* nd) +{ + int16 m = nd->bl.m, x = nd->bl.x, y = nd->bl.y, xs, ys; + int i,j, x0, x1, y0, y1; + + if (nd->subtype == WARP) { + xs = nd->u.warp.xs; + ys = nd->u.warp.ys; + } else { + xs = nd->u.scr.xs; + ys = nd->u.scr.ys; + } + + if (m < 0 || xs < 0 || ys < 0) + return; + + //Locate max range on which we can locate npc cells + //FIXME: does this really do what it's supposed to do? [ultramage] + for(x0 = x-xs; x0 > 0 && map_getcell(m, x0, y, CELL_CHKNPC); x0--); + for(x1 = x+xs; x1 < map[m].xs-1 && map_getcell(m, x1, y, CELL_CHKNPC); x1++); + for(y0 = y-ys; y0 > 0 && map_getcell(m, x, y0, CELL_CHKNPC); y0--); + for(y1 = y+ys; y1 < map[m].ys-1 && map_getcell(m, x, y1, CELL_CHKNPC); y1++); + + //Erase this npc's cells + for (i = y-ys; i <= y+ys; i++) + for (j = x-xs; j <= x+xs; j++) + map_setcell(m, j, i, CELL_NPC, false); + + //Re-deploy NPC cells for other nearby npcs. + map_foreachinarea( npc_unsetcells_sub, m, x0, y0, x1, y1, BL_NPC, nd->bl.id ); +} + +void npc_movenpc(struct npc_data* nd, int16 x, int16 y) +{ + const int16 m = nd->bl.m; + if (m < 0 || nd->bl.prev == NULL) return; //Not on a map. + + x = cap_value(x, 0, map[m].xs-1); + y = cap_value(y, 0, map[m].ys-1); + + map_foreachinrange(clif_outsight, &nd->bl, AREA_SIZE, BL_PC, &nd->bl); + map_moveblock(&nd->bl, x, y, gettick()); + map_foreachinrange(clif_insight, &nd->bl, AREA_SIZE, BL_PC, &nd->bl); +} + +/// Changes the display name of the npc. +/// +/// @param nd Target npc +/// @param newname New display name +void npc_setdisplayname(struct npc_data* nd, const char* newname) +{ + nullpo_retv(nd); + + safestrncpy(nd->name, newname, sizeof(nd->name)); + if( map[nd->bl.m].users ) + clif_charnameack(0, &nd->bl); +} + +/// Changes the display class of the npc. +/// +/// @param nd Target npc +/// @param class_ New display class +void npc_setclass(struct npc_data* nd, short class_) +{ + nullpo_retv(nd); + + if( nd->class_ == class_ ) + return; + + if( map[nd->bl.m].users ) + clif_clearunit_area(&nd->bl, CLR_OUTSIGHT);// fade out + nd->class_ = class_; + status_set_viewdata(&nd->bl, class_); + if( map[nd->bl.m].users ) + clif_spawn(&nd->bl);// fade in +} + +// @commands (script based) +int npc_do_atcmd_event(struct map_session_data* sd, const char* command, const char* message, const char* eventname) +{ + struct event_data* ev = (struct event_data*)strdb_get(ev_db, eventname); + struct npc_data *nd; + struct script_state *st; + int i = 0, j = 0, k = 0; + char *temp; + + nullpo_ret(sd); + + if( ev == NULL || (nd = ev->nd) == NULL ) { + ShowError("npc_event: event not found [%s]\n", eventname); + return 0; + } + + if( sd->npc_id != 0 ) { // Enqueue the event trigger. + int i; + ARR_FIND( 0, MAX_EVENTQUEUE, i, sd->eventqueue[i][0] == '\0' ); + if( i < MAX_EVENTQUEUE ) { + safestrncpy(sd->eventqueue[i],eventname,50); //Event enqueued. + return 0; + } + + ShowWarning("npc_event: player's event queue is full, can't add event '%s' !\n", eventname); + return 1; + } + + if( ev->nd->sc.option&OPTION_INVISIBLE ) { // Disabled npc, shouldn't trigger event. + npc_event_dequeue(sd); + return 2; + } + + st = script_alloc_state(ev->nd->u.scr.script, ev->pos, sd->bl.id, ev->nd->bl.id); + setd_sub(st, NULL, ".@atcmd_command$", 0, (void *)command, NULL); + + // split atcmd parameters based on spaces + i = 0; + j = 0; + + temp = (char*)aMalloc(strlen(message) + 1); + + while( message[i] != '\0' ) { + if( message[i] == ' ' && k < 127 ) { + temp[j] = '\0'; + setd_sub(st, NULL, ".@atcmd_parameters$", k++, (void *)temp, NULL); + j = 0; + ++i; + } else + temp[j++] = message[i++]; + } + + temp[j] = '\0'; + setd_sub(st, NULL, ".@atcmd_parameters$", k++, (void *)temp, NULL); + setd_sub(st, NULL, ".@atcmd_numparameters", 0, (void *)&k, NULL); + aFree(temp); + + run_script_main(st); + return 0; +} + +/// Parses a function. +/// function%TAB%script%TAB%<function name>%TAB%{<code>} +static const char* npc_parse_function(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + DBMap* func_db; + DBData old_data; + struct script_code *script; + const char* end; + const char* script_start; + + script_start = strstr(start,"\t{"); + end = strchr(start,'\n'); + if( *w4 != '{' || script_start == NULL || (end != NULL && script_start > end) ) + { + ShowError("npc_parse_function: Missing left curly '%%TAB%%{' in file '%s', line '%d'. Skipping the rest of the file.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return NULL;// can't continue + } + ++script_start; + + end = npc_skip_script(script_start,buffer,filepath); + if( end == NULL ) + return NULL;// (simple) parse error, don't continue + + script = parse_script(script_start, filepath, strline(buffer,start-buffer), SCRIPT_RETURN_EMPTY_SCRIPT); + if( script == NULL )// parse error, continue + return end; + + func_db = script_get_userfunc_db(); + if (func_db->put(func_db, db_str2key(w3), db_ptr2data(script), &old_data)) + { + struct script_code *oldscript = (struct script_code*)db_data2ptr(&old_data); + ShowInfo("npc_parse_function: Overwriting user function [%s] (%s:%d)\n", w3, filepath, strline(buffer,start-buffer)); + script_free_vars(oldscript->script_vars); + aFree(oldscript->script_buf); + aFree(oldscript); + } + + return end; +} + + +/*========================================== + * Parse Mob 1 - Parse mob list into each map + * Parse Mob 2 - Actually Spawns Mob + * [Wizputer] + *------------------------------------------*/ +void npc_parse_mob2(struct spawn_data* mob) +{ + int i; + + for( i = mob->active; i < mob->num; ++i ) + { + struct mob_data* md = mob_spawn_dataset(mob); + md->spawn = mob; + md->spawn->active++; + mob_spawn(md); + } +} + +static const char* npc_parse_mob(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + int num, class_, m,x,y,xs,ys, i,j; + int mob_lv = -1, ai = -1, size = -1; + char mapname[32], mobname[NAME_LENGTH]; + struct spawn_data mob, *data; + struct mob_db* db; + + memset(&mob, 0, sizeof(struct spawn_data)); + + mob.state.boss = !strcmpi(w2,"boss_monster"); + + // w1=<map name>,<x>,<y>,<xs>,<ys> + // w3=<mob name>{,<mob level>} + // w4=<mob id>,<amount>,<delay1>,<delay2>,<event>{,<mob size>,<mob ai>} + if( sscanf(w1, "%31[^,],%d,%d,%d,%d", mapname, &x, &y, &xs, &ys) < 3 + || sscanf(w3, "%23[^,],%d", mobname, &mob_lv) < 1 + || sscanf(w4, "%d,%d,%u,%u,%127[^,],%d,%d[^\t\r\n]", &class_, &num, &mob.delay1, &mob.delay2, mob.eventname, &size, &ai) < 2 ) + { + ShowError("npc_parse_mob: Invalid mob definition in file '%s', line '%d'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + if( mapindex_name2id(mapname) == 0 ) + { + ShowError("npc_parse_mob: Unknown map '%s' in file '%s', line '%d'.\n", mapname, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + m = map_mapname2mapid(mapname); + if( m < 0 )//Not loaded on this map-server instance. + return strchr(start,'\n');// skip and continue + mob.m = (unsigned short)m; + + if( x < 0 || x >= map[mob.m].xs || y < 0 || y >= map[mob.m].ys ) + { + ShowError("npc_parse_mob: Spawn coordinates out of range: %s (%d,%d), map size is (%d,%d) - %s %s (file '%s', line '%d').\n", map[mob.m].name, x, y, (map[mob.m].xs-1), (map[mob.m].ys-1), w1, w3, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + + // check monster ID if exists! + if( mobdb_checkid(class_) == 0 ) + { + ShowError("npc_parse_mob: Unknown mob ID %d (file '%s', line '%d').\n", class_, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + + if( num < 1 || num > 1000 ) + { + ShowError("npc_parse_mob: Invalid number of monsters %d, must be inside the range [1,1000] (file '%s', line '%d').\n", num, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + + if( (mob.state.size < 0 || mob.state.size > 2) && size != -1 ) + { + ShowError("npc_parse_mob: Invalid size number %d for mob ID %d (file '%s', line '%d').\n", mob.state.size, class_, filepath, strline(buffer, start - buffer)); + return strchr(start, '\n'); + } + + if( (mob.state.ai < 0 || mob.state.ai > 4) && ai != -1 ) + { + ShowError("npc_parse_mob: Invalid ai %d for mob ID %d (file '%s', line '%d').\n", mob.state.ai, class_, filepath, strline(buffer, start - buffer)); + return strchr(start, '\n'); + } + + if( (mob_lv == 0 || mob_lv > MAX_LEVEL) && mob_lv != -1 ) + { + ShowError("npc_parse_mob: Invalid level %d for mob ID %d (file '%s', line '%d').\n", mob_lv, class_, filepath, strline(buffer, start - buffer)); + return strchr(start, '\n'); + } + + mob.num = (unsigned short)num; + mob.active = 0; + mob.class_ = (short) class_; + mob.x = (unsigned short)x; + mob.y = (unsigned short)y; + mob.xs = (signed short)xs; + mob.ys = (signed short)ys; + if (mob_lv > 0 && mob_lv <= MAX_LEVEL) + mob.level = mob_lv; + if (size > 0 && size <= 2) + mob.state.size = size; + if (ai > 0 && ai <= 4) + mob.state.ai = ai; + + if (mob.num > 1 && battle_config.mob_count_rate != 100) { + if ((mob.num = mob.num * battle_config.mob_count_rate / 100) < 1) + mob.num = 1; + } + + if (battle_config.force_random_spawn || (mob.x == 0 && mob.y == 0)) + { //Force a random spawn anywhere on the map. + mob.x = mob.y = 0; + mob.xs = mob.ys = -1; + } + + if(mob.delay1>0xfffffff || mob.delay2>0xfffffff) { + ShowError("npc_parse_mob: Invalid spawn delays %u %u (file '%s', line '%d').\n", mob.delay1, mob.delay2, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + + //Use db names instead of the spawn file ones. + if(battle_config.override_mob_names==1) + strcpy(mob.name,"--en--"); + else if (battle_config.override_mob_names==2) + strcpy(mob.name,"--ja--"); + else + safestrncpy(mob.name, mobname, sizeof(mob.name)); + + //Verify dataset. + if( !mob_parse_dataset(&mob) ) + { + ShowError("npc_parse_mob: Invalid dataset for monster ID %d (file '%s', line '%d').\n", class_, filepath, strline(buffer,start-buffer)); + return strchr(start,'\n');// skip and continue + } + + //Update mob spawn lookup database + db = mob_db(class_); + for( i = 0; i < ARRAYLENGTH(db->spawn); ++i ) + { + if (map[mob.m].index == db->spawn[i].mapindex) + { //Update total + db->spawn[i].qty += mob.num; + //Re-sort list + for( j = i; j > 0 && db->spawn[j-1].qty < db->spawn[i].qty; --j ); + if( j != i ) + { + xs = db->spawn[i].mapindex; + ys = db->spawn[i].qty; + memmove(&db->spawn[j+1], &db->spawn[j], (i-j)*sizeof(db->spawn[0])); + db->spawn[j].mapindex = xs; + db->spawn[j].qty = ys; + } + break; + } + if (mob.num > db->spawn[i].qty) + { //Insert into list + memmove(&db->spawn[i+1], &db->spawn[i], sizeof(db->spawn) -(i+1)*sizeof(db->spawn[0])); + db->spawn[i].mapindex = map[mob.m].index; + db->spawn[i].qty = mob.num; + break; + } + } + + //Now that all has been validated. We allocate the actual memory that the re-spawn data will use. + data = (struct spawn_data*)aMalloc(sizeof(struct spawn_data)); + memcpy(data, &mob, sizeof(struct spawn_data)); + + // spawn / cache the new mobs + if( battle_config.dynamic_mobs && map_addmobtolist(data->m, data) >= 0 ) + { + data->state.dynamic = true; + npc_cache_mob += data->num; + + // check if target map has players + // (usually shouldn't occur when map server is just starting, + // but not the case when we do @reloadscript + if( map[data->m].users > 0 ) + npc_parse_mob2(data); + } + else + { + data->state.dynamic = false; + npc_parse_mob2(data); + npc_delay_mob += data->num; + } + + npc_mob++; + + return strchr(start,'\n');// continue +} + +/*========================================== + * Set or disable mapflag on map + * eg : bat_c01 mapflag battleground 2 + * also chking if mapflag conflict with another + *------------------------------------------*/ +static const char* npc_parse_mapflag(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) +{ + int16 m; + char mapname[32]; + int state = 1; + + // w1=<mapname> + if( sscanf(w1, "%31[^,]", mapname) != 1 ) + { + ShowError("npc_parse_mapflag: Invalid mapflag definition in file '%s', line '%d'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + m = map_mapname2mapid(mapname); + if( m < 0 ) + { + ShowWarning("npc_parse_mapflag: Unknown map in file '%s', line '%d' : %s\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", mapname, filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + return strchr(start,'\n');// skip and continue + } + + if (w4 && !strcmpi(w4, "off")) + state = 0; //Disable mapflag rather than enable it. [Skotlex] + + if (!strcmpi(w3, "nosave")) { + char savemap[32]; + int savex, savey; + if (state == 0) + ; //Map flag disabled. + else if (!strcmpi(w4, "SavePoint")) { + map[m].save.map = 0; + map[m].save.x = -1; + map[m].save.y = -1; + } else if (sscanf(w4, "%31[^,],%d,%d", savemap, &savex, &savey) == 3) { + map[m].save.map = mapindex_name2id(savemap); + map[m].save.x = savex; + map[m].save.y = savey; + if (!map[m].save.map) { + ShowWarning("npc_parse_mapflag: Specified save point map '%s' for mapflag 'nosave' not found (file '%s', line '%d'), using 'SavePoint'.\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", savemap, filepath, strline(buffer,start-buffer), w1, w2, w3, w4); + map[m].save.x = -1; + map[m].save.y = -1; + } + } + map[m].flag.nosave = state; + } + else if (!strcmpi(w3,"autotrade")) + map[m].flag.autotrade=state; + else if (!strcmpi(w3,"allowks")) + map[m].flag.allowks=state; // [Kill Steal Protection] + else if (!strcmpi(w3,"town")) + map[m].flag.town=state; + else if (!strcmpi(w3,"nomemo")) + map[m].flag.nomemo=state; + else if (!strcmpi(w3,"noteleport")) + map[m].flag.noteleport=state; + else if (!strcmpi(w3,"nowarp")) + map[m].flag.nowarp=state; + else if (!strcmpi(w3,"nowarpto")) + map[m].flag.nowarpto=state; + else if (!strcmpi(w3,"noreturn")) + map[m].flag.noreturn=state; + else if (!strcmpi(w3,"monster_noteleport")) + map[m].flag.monster_noteleport=state; + else if (!strcmpi(w3,"nobranch")) + map[m].flag.nobranch=state; + else if (!strcmpi(w3,"nopenalty")) { + map[m].flag.noexppenalty=state; + map[m].flag.nozenypenalty=state; + } + else if (!strcmpi(w3,"pvp")) { + map[m].flag.pvp = state; + if( state && (map[m].flag.gvg || map[m].flag.gvg_dungeon || map[m].flag.gvg_castle) ) + { + map[m].flag.gvg = 0; + map[m].flag.gvg_dungeon = 0; + map[m].flag.gvg_castle = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing GvG flags from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + if( state && map[m].flag.battleground ) + { + map[m].flag.battleground = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and BattleGround flags for the same map! Removing BattleGround flag from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + } + else if (!strcmpi(w3,"pvp_noparty")) + map[m].flag.pvp_noparty=state; + else if (!strcmpi(w3,"pvp_noguild")) + map[m].flag.pvp_noguild=state; + else if (!strcmpi(w3, "pvp_nightmaredrop")) { + char drop_arg1[16], drop_arg2[16]; + int drop_id = 0, drop_type = 0, drop_per = 0; + if (sscanf(w4, "%[^,],%[^,],%d", drop_arg1, drop_arg2, &drop_per) == 3) { + int i; + if (!strcmpi(drop_arg1, "random")) + drop_id = -1; + else if (itemdb_exists((drop_id = atoi(drop_arg1))) == NULL) + drop_id = 0; + if (!strcmpi(drop_arg2, "inventory")) + drop_type = 1; + else if (!strcmpi(drop_arg2,"equip")) + drop_type = 2; + else if (!strcmpi(drop_arg2,"all")) + drop_type = 3; + + if (drop_id != 0){ + for (i = 0; i < MAX_DROP_PER_MAP; i++) { + if (map[m].drop_list[i].drop_id == 0){ + map[m].drop_list[i].drop_id = drop_id; + map[m].drop_list[i].drop_type = drop_type; + map[m].drop_list[i].drop_per = drop_per; + break; + } + } + map[m].flag.pvp_nightmaredrop = 1; + } + } else if (!state) //Disable + map[m].flag.pvp_nightmaredrop = 0; + } + else if (!strcmpi(w3,"pvp_nocalcrank")) + map[m].flag.pvp_nocalcrank=state; + else if (!strcmpi(w3,"gvg")) { + map[m].flag.gvg = state; + if( state && map[m].flag.pvp ) + { + map[m].flag.pvp = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing PvP flag from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + if( state && map[m].flag.battleground ) + { + map[m].flag.battleground = 0; + ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing BattleGround flag from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + } + else if (!strcmpi(w3,"gvg_noparty")) + map[m].flag.gvg_noparty=state; + else if (!strcmpi(w3,"gvg_dungeon")) { + map[m].flag.gvg_dungeon=state; + if (state) map[m].flag.pvp=0; + } + else if (!strcmpi(w3,"gvg_castle")) { + map[m].flag.gvg_castle=state; + if (state) map[m].flag.pvp=0; + } + else if (!strcmpi(w3,"battleground")) { + if( state ) + { + if( sscanf(w4, "%d", &state) == 1 ) + map[m].flag.battleground = state; + else + map[m].flag.battleground = 1; // Default value + } + else + map[m].flag.battleground = 0; + + if( map[m].flag.battleground && map[m].flag.pvp ) + { + map[m].flag.pvp = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and BattleGround flags for the same map! Removing PvP flag from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + if( map[m].flag.battleground && (map[m].flag.gvg || map[m].flag.gvg_dungeon || map[m].flag.gvg_castle) ) + { + map[m].flag.gvg = 0; + map[m].flag.gvg_dungeon = 0; + map[m].flag.gvg_castle = 0; + ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing GvG flag from %s (file '%s', line '%d').\n", map[m].name, filepath, strline(buffer,start-buffer)); + } + } + else if (!strcmpi(w3,"noexppenalty")) + map[m].flag.noexppenalty=state; + else if (!strcmpi(w3,"nozenypenalty")) + map[m].flag.nozenypenalty=state; + else if (!strcmpi(w3,"notrade")) + map[m].flag.notrade=state; + else if (!strcmpi(w3,"novending")) + map[m].flag.novending=state; + else if (!strcmpi(w3,"nodrop")) + map[m].flag.nodrop=state; + else if (!strcmpi(w3,"noskill")) + map[m].flag.noskill=state; + else if (!strcmpi(w3,"noicewall")) + map[m].flag.noicewall=state; + else if (!strcmpi(w3,"snow")) + map[m].flag.snow=state; + else if (!strcmpi(w3,"clouds")) + map[m].flag.clouds=state; + else if (!strcmpi(w3,"clouds2")) + map[m].flag.clouds2=state; + else if (!strcmpi(w3,"fog")) + map[m].flag.fog=state; + else if (!strcmpi(w3,"fireworks")) + map[m].flag.fireworks=state; + else if (!strcmpi(w3,"sakura")) + map[m].flag.sakura=state; + else if (!strcmpi(w3,"leaves")) + map[m].flag.leaves=state; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //else if (!strcmpi(w3,"rain")) + // map[m].flag.rain=state; + else if (!strcmpi(w3,"nightenabled")) + map[m].flag.nightenabled=state; + else if (!strcmpi(w3,"nogo")) + map[m].flag.nogo=state; + else if (!strcmpi(w3,"noexp")) { + map[m].flag.nobaseexp=state; + map[m].flag.nojobexp=state; + } + else if (!strcmpi(w3,"nobaseexp")) + map[m].flag.nobaseexp=state; + else if (!strcmpi(w3,"nojobexp")) + map[m].flag.nojobexp=state; + else if (!strcmpi(w3,"noloot")) { + map[m].flag.nomobloot=state; + map[m].flag.nomvploot=state; + } + else if (!strcmpi(w3,"nomobloot")) + map[m].flag.nomobloot=state; + else if (!strcmpi(w3,"nomvploot")) + map[m].flag.nomvploot=state; + else if (!strcmpi(w3,"nocommand")) { + if (state) { + if (sscanf(w4, "%d", &state) == 1) + map[m].nocommand =state; + else //No level specified, block everyone. + map[m].nocommand =100; + } else + map[m].nocommand=0; + } + else if (!strcmpi(w3,"restricted")) { + if (state) { + map[m].flag.restricted=1; + sscanf(w4, "%d", &state); + map[m].zone |= 1<<(state+1); + } else { + map[m].flag.restricted=0; + map[m].zone = 0; + } + } + else if (!strcmpi(w3,"jexp")) { + map[m].jexp = (state) ? atoi(w4) : 100; + if( map[m].jexp < 0 ) map[m].jexp = 100; + map[m].flag.nojobexp = (map[m].jexp==0)?1:0; + } + else if (!strcmpi(w3,"bexp")) { + map[m].bexp = (state) ? atoi(w4) : 100; + if( map[m].bexp < 0 ) map[m].bexp = 100; + map[m].flag.nobaseexp = (map[m].bexp==0)?1:0; + } + else if (!strcmpi(w3,"loadevent")) + map[m].flag.loadevent=state; + else if (!strcmpi(w3,"nochat")) + map[m].flag.nochat=state; + else if (!strcmpi(w3,"partylock")) + map[m].flag.partylock=state; + else if (!strcmpi(w3,"guildlock")) + map[m].flag.guildlock=state; + else if (!strcmpi(w3,"reset")) + map[m].flag.reset=state; + else + ShowError("npc_parse_mapflag: unrecognized mapflag '%s' (file '%s', line '%d').\n", w3, filepath, strline(buffer,start-buffer)); + + return strchr(start,'\n');// continue +} + +//Read file and create npc/func/mapflag/monster... accordingly. +//@runOnInit should we exec OnInit when it's done ? +void npc_parsesrcfile(const char* filepath, bool runOnInit) +{ + int16 m, x, y; + int lines = 0; + FILE* fp; + size_t len; + char* buffer; + const char* p; + + // read whole file to buffer + fp = fopen(filepath, "rb"); + if( fp == NULL ) + { + ShowError("npc_parsesrcfile: File not found '%s'.\n", filepath); + return; + } + fseek(fp, 0, SEEK_END); + len = ftell(fp); + buffer = (char*)aMalloc(len+1); + fseek(fp, 0, SEEK_SET); + len = fread(buffer, sizeof(char), len, fp); + buffer[len] = '\0'; + if( ferror(fp) ) + { + ShowError("npc_parsesrcfile: Failed to read file '%s' - %s\n", filepath, strerror(errno)); + aFree(buffer); + fclose(fp); + return; + } + fclose(fp); + + // parse buffer + for( p = skip_space(buffer); p && *p ; p = skip_space(p) ) + { + int pos[9]; + char w1[2048], w2[2048], w3[2048], w4[2048]; + int i, count; + lines++; + + // w1<TAB>w2<TAB>w3<TAB>w4 + count = sv_parse(p, len+buffer-p, 0, '\t', pos, ARRAYLENGTH(pos), (e_svopt)(SV_TERMINATE_LF|SV_TERMINATE_CRLF)); + if( count < 0 ) + { + ShowError("npc_parsesrcfile: Parse error in file '%s', line '%d'. Stopping...\n", filepath, strline(buffer,p-buffer)); + break; + } + // fill w1 + if( pos[3]-pos[2] > ARRAYLENGTH(w1)-1 ) + ShowWarning("npc_parsesrcfile: w1 truncated, too much data (%d) in file '%s', line '%d'.\n", pos[3]-pos[2], filepath, strline(buffer,p-buffer)); + i = min(pos[3]-pos[2], ARRAYLENGTH(w1)-1); + memcpy(w1, p+pos[2], i*sizeof(char)); + w1[i] = '\0'; + // fill w2 + if( pos[5]-pos[4] > ARRAYLENGTH(w2)-1 ) + ShowWarning("npc_parsesrcfile: w2 truncated, too much data (%d) in file '%s', line '%d'.\n", pos[5]-pos[4], filepath, strline(buffer,p-buffer)); + i = min(pos[5]-pos[4], ARRAYLENGTH(w2)-1); + memcpy(w2, p+pos[4], i*sizeof(char)); + w2[i] = '\0'; + // fill w3 + if( pos[7]-pos[6] > ARRAYLENGTH(w3)-1 ) + ShowWarning("npc_parsesrcfile: w3 truncated, too much data (%d) in file '%s', line '%d'.\n", pos[7]-pos[6], filepath, strline(buffer,p-buffer)); + i = min(pos[7]-pos[6], ARRAYLENGTH(w3)-1); + memcpy(w3, p+pos[6], i*sizeof(char)); + w3[i] = '\0'; + // fill w4 (to end of line) + if( pos[1]-pos[8] > ARRAYLENGTH(w4)-1 ) + ShowWarning("npc_parsesrcfile: w4 truncated, too much data (%d) in file '%s', line '%d'.\n", pos[1]-pos[8], filepath, strline(buffer,p-buffer)); + if( pos[8] != -1 ) + { + i = min(pos[1]-pos[8], ARRAYLENGTH(w4)-1); + memcpy(w4, p+pos[8], i*sizeof(char)); + w4[i] = '\0'; + } + else + w4[0] = '\0'; + + if( count < 3 ) + {// Unknown syntax + ShowError("npc_parsesrcfile: Unknown syntax in file '%s', line '%d'. Stopping...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,p-buffer), w1, w2, w3, w4); + break; + } + + if( strcmp(w1,"-") !=0 && strcasecmp(w1,"function") != 0 ) + {// w1 = <map name>,<x>,<y>,<facing> + char mapname[MAP_NAME_LENGTH*2]; + x = y = 0; + sscanf(w1,"%23[^,],%hd,%hd[^,]",mapname,&x,&y); + if( !mapindex_name2id(mapname) ) + {// Incorrect map, we must skip the script info... + ShowError("npc_parsesrcfile: Unknown map '%s' in file '%s', line '%d'. Skipping line...\n", mapname, filepath, strline(buffer,p-buffer)); + if( strcasecmp(w2,"script") == 0 && count > 3 ) + { + if((p = npc_skip_script(p,buffer,filepath)) == NULL) + { + break; + } + } + p = strchr(p,'\n');// next line + continue; + } + m = map_mapname2mapid(mapname); + if( m < 0 ) + {// "mapname" is not assigned to this server, we must skip the script info... + if( strcasecmp(w2,"script") == 0 && count > 3 ) + { + if((p = npc_skip_script(p,buffer,filepath)) == NULL) + { + break; + } + } + p = strchr(p,'\n');// next line + continue; + } + if (x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys) { + ShowError("npc_parsesrcfile: Unknown coordinates ('%d', '%d') for map '%s' in file '%s', line '%d'. Skipping line...\n", x, y, mapname, filepath, strline(buffer,p-buffer)); + if( strcasecmp(w2,"script") == 0 && count > 3 ) + { + if((p = npc_skip_script(p,buffer,filepath)) == NULL) + { + break; + } + } + p = strchr(p,'\n');// next line + continue; + } + } + + if( strcasecmp(w2,"warp") == 0 && count > 3 ) + { + p = npc_parse_warp(w1,w2,w3,w4, p, buffer, filepath); + } + else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop")) && count > 3 ) + { + p = npc_parse_shop(w1,w2,w3,w4, p, buffer, filepath); + } + else if( strcasecmp(w2,"script") == 0 && count > 3 ) + { + if( strcasecmp(w1,"function") == 0 ) + p = npc_parse_function(w1, w2, w3, w4, p, buffer, filepath); + else + p = npc_parse_script(w1,w2,w3,w4, p, buffer, filepath,runOnInit); + } + else if( (i=0, sscanf(w2,"duplicate%n",&i), (i > 0 && w2[i] == '(')) && count > 3 ) + { + p = npc_parse_duplicate(w1,w2,w3,w4, p, buffer, filepath); + } + else if( (strcmpi(w2,"monster") == 0 || strcmpi(w2,"boss_monster") == 0) && count > 3 ) + { + p = npc_parse_mob(w1, w2, w3, w4, p, buffer, filepath); + } + else if( strcmpi(w2,"mapflag") == 0 && count >= 3 ) + { + p = npc_parse_mapflag(w1, w2, trim(w3), trim(w4), p, buffer, filepath); + } + else + { + ShowError("npc_parsesrcfile: Unable to parse, probably a missing or extra TAB in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,p-buffer), w1, w2, w3, w4); + p = strchr(p,'\n');// skip and continue + } + } + aFree(buffer); + + return; +} + +int npc_script_event(struct map_session_data* sd, enum npce_event type) +{ + int i; + if (type == NPCE_MAX) + return 0; + if (!sd) { + ShowError("npc_script_event: NULL sd. Event Type %d\n", type); + return 0; + } + for (i = 0; i<script_event[type].event_count; i++) + npc_event_sub(sd,script_event[type].event[i],script_event[type].event_name[i]); + return i; +} + +void npc_read_event_script(void) +{ + int i; + struct { + char *name; + const char *event_name; + } config[] = { + {"Login Event",script_config.login_event_name}, + {"Logout Event",script_config.logout_event_name}, + {"Load Map Event",script_config.loadmap_event_name}, + {"Base LV Up Event",script_config.baselvup_event_name}, + {"Job LV Up Event",script_config.joblvup_event_name}, + {"Die Event",script_config.die_event_name}, + {"Kill PC Event",script_config.kill_pc_event_name}, + {"Kill NPC Event",script_config.kill_mob_event_name}, + }; + + for (i = 0; i < NPCE_MAX; i++) + { + DBIterator* iter; + DBKey key; + DBData *data; + + char name[64]="::"; + strncpy(name+2,config[i].event_name,62); + + script_event[i].event_count = 0; + iter = db_iterator(ev_db); + for( data = iter->first(iter,&key); iter->exists(iter); data = iter->next(iter,&key) ) + { + const char* p = key.str; + struct event_data* ed = db_data2ptr(data); + unsigned char count = script_event[i].event_count; + + if( count >= ARRAYLENGTH(script_event[i].event) ) + { + ShowWarning("npc_read_event_script: too many occurences of event '%s'!\n", config[i].event_name); + break; + } + + if( (p=strchr(p,':')) && p && strcmpi(name,p)==0 ) + { + script_event[i].event[count] = ed; + script_event[i].event_name[count] = key.str; + script_event[i].event_count++; + } + } + dbi_destroy(iter); + } + + if (battle_config.etc_log) { + //Print summary. + for (i = 0; i < NPCE_MAX; i++) + ShowInfo("%s: %d '%s' events.\n", config[i].name, script_event[i].event_count, config[i].event_name); + } +} + +void npc_clear_pathlist(void) { + struct npc_path_data *npd = NULL; + DBIterator *path_list = db_iterator(npc_path_db); + + /* free all npc_path_data filepaths */ + for( npd = dbi_first(path_list); dbi_exists(path_list); npd = dbi_next(path_list) ) { + if( npd->path ) + aFree(npd->path); + } + + dbi_destroy(path_list); +} + +//Clear then reload npcs files +int npc_reload(void) { + struct npc_src_list *nsl; + int16 m, i; + int npc_new_min = npc_id; + struct s_mapiterator* iter; + struct block_list* bl; + + /* clear guild flag cache */ + guild_flags_clear(); + + npc_clear_pathlist(); + + db_clear(npc_path_db); + + db_clear(npcname_db); + db_clear(ev_db); + + //Remove all npcs/mobs. [Skotlex] + + iter = mapit_geteachiddb(); + for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) { + switch(bl->type) { + case BL_NPC: + if( bl->id != fake_nd->bl.id )// don't remove fake_nd + npc_unload((struct npc_data *)bl, false); + break; + case BL_MOB: + unit_free(bl,CLR_OUTSIGHT); + break; + } + } + mapit_free(iter); + + if(battle_config.dynamic_mobs) + {// dynamic check by [random] + for (m = 0; m < map_num; m++) { + for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) { + if (map[m].moblist[i] != NULL) { + aFree(map[m].moblist[i]); + map[m].moblist[i] = NULL; + } + if( map[m].mob_delete_timer != INVALID_TIMER ) + { // Mobs were removed anyway,so delete the timer [Inkfish] + delete_timer(map[m].mob_delete_timer, map_removemobs_timer); + map[m].mob_delete_timer = INVALID_TIMER; + } + } + } + if (map[m].npc_num > 0) + ShowWarning("npc_reload: %d npcs weren't removed at map %s!\n", map[m].npc_num, map[m].name); + } + + // clear mob spawn lookup index + mob_clear_spawninfo(); + + npc_warp = npc_shop = npc_script = 0; + npc_mob = npc_cache_mob = npc_delay_mob = 0; + + // reset mapflags + map_flags_init(); + + //TODO: the following code is copy-pasted from do_init_npc(); clean it up + // Reloading npcs now + for (nsl = npc_src_files; nsl; nsl = nsl->next) { + ShowStatus("Loading NPC file: %s"CL_CLL"\r", nsl->name); + npc_parsesrcfile(nsl->name,false); + } + ShowInfo ("Done loading '"CL_WHITE"%d"CL_RESET"' NPCs:"CL_CLL"\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Warps\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Shops\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Scripts\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Spawn sets\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Cached\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Not Cached\n", + npc_id - npc_new_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); + + do_final_instance(); + + for( i = 0; i < ARRAYLENGTH(instance); ++i ) + instance_init(instance[i].instance_id); + + //Re-read the NPC Script Events cache. + npc_read_event_script(); + + /* refresh guild castle flags on both woe setups */ + npc_event_doall("OnAgitInit"); + npc_event_doall("OnAgitInit2"); + + //Execute the OnInit event for freshly loaded npcs. [Skotlex] + ShowStatus("Event '"CL_WHITE"OnInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",npc_event_doall("OnInit")); + + // Execute rest of the startup events if connected to char-server. [Lance] + if(!CheckForCharServer()){ + ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInit")); + ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInitOnce")); + } + return 0; +} + +//Unload all npc in the given file +bool npc_unloadfile( const char* path ) { + DBIterator * iter = db_iterator(npcname_db); + struct npc_data* nd = NULL; + bool found = false; + + for( nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter) ) { + if( nd->path && strcasecmp(nd->path,path) == 0 ) { + found = true; + npc_unload_duplicates(nd);/* unload any npcs which could duplicate this but be in a different file */ + npc_unload(nd, true); + } + } + + dbi_destroy(iter); + + if( found ) /* refresh event cache */ + npc_read_event_script(); + + return found; +} + +void do_clear_npc(void) { + db_clear(npcname_db); + db_clear(ev_db); +} + +/*========================================== + * Destructor + *------------------------------------------*/ +int do_final_npc(void) { + npc_clear_pathlist(); + ev_db->destroy(ev_db, NULL); + npcname_db->destroy(npcname_db, NULL); + npc_path_db->destroy(npc_path_db, NULL); + ers_destroy(timer_event_ers); + npc_clearsrcfile(); + + return 0; +} + +static void npc_debug_warps_sub(struct npc_data* nd) +{ + int16 m; + if (nd->bl.type != BL_NPC || nd->subtype != WARP || nd->bl.m < 0) + return; + + m = map_mapindex2mapid(nd->u.warp.mapindex); + if (m < 0) return; //Warps to another map, nothing to do about it. + if (nd->u.warp.x == 0 && nd->u.warp.y == 0) return; // random warp + + if (map_getcell(m, nd->u.warp.x, nd->u.warp.y, CELL_CHKNPC)) { + ShowWarning("Warp %s at %s(%d,%d) warps directly on top of an area npc at %s(%d,%d)\n", + nd->name, + map[nd->bl.m].name, nd->bl.x, nd->bl.y, + map[m].name, nd->u.warp.x, nd->u.warp.y + ); + } + if (map_getcell(m, nd->u.warp.x, nd->u.warp.y, CELL_CHKNOPASS)) { + ShowWarning("Warp %s at %s(%d,%d) warps to a non-walkable tile at %s(%d,%d)\n", + nd->name, + map[nd->bl.m].name, nd->bl.x, nd->bl.y, + map[m].name, nd->u.warp.x, nd->u.warp.y + ); + } +} + +static void npc_debug_warps(void) +{ + int16 m, i; + for (m = 0; m < map_num; m++) + for (i = 0; i < map[m].npc_num; i++) + npc_debug_warps_sub(map[m].npc[i]); +} + +/*========================================== + * npc initialization + *------------------------------------------*/ +int do_init_npc(void) +{ + struct npc_src_list *file; + int i; + + //Stock view data for normal npcs. + memset(&npc_viewdb, 0, sizeof(npc_viewdb)); + npc_viewdb[0].class_ = INVISIBLE_CLASS; //Invisible class is stored here. + for( i = 1; i < MAX_NPC_CLASS; i++ ) + npc_viewdb[i].class_ = i; + + ev_db = strdb_alloc((DBOptions)(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA),2*NAME_LENGTH+2+1); + npcname_db = strdb_alloc(DB_OPT_BASE,NAME_LENGTH); + npc_path_db = strdb_alloc(DB_OPT_BASE|DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA,80); + + timer_event_ers = ers_new(sizeof(struct timer_event_data),"clif.c::timer_event_ers",ERS_OPT_NONE); + + // process all npc files + ShowStatus("Loading NPCs...\r"); + for( file = npc_src_files; file != NULL; file = file->next ) { + ShowStatus("Loading NPC file: %s"CL_CLL"\r", file->name); + npc_parsesrcfile(file->name,false); + } + ShowInfo ("Done loading '"CL_WHITE"%d"CL_RESET"' NPCs:"CL_CLL"\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Warps\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Shops\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Scripts\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Spawn sets\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Cached\n" + "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Not Cached\n", + npc_id - START_NPC_NUM, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); + + // set up the events cache + memset(script_event, 0, sizeof(script_event)); + npc_read_event_script(); + + //Debug function to locate all endless loop warps. + if (battle_config.warp_point_debug) + npc_debug_warps(); + + add_timer_func_list(npc_event_do_clock,"npc_event_do_clock"); + add_timer_func_list(npc_timerevent,"npc_timerevent"); + + // Init dummy NPC + fake_nd = (struct npc_data *)aCalloc(1,sizeof(struct npc_data)); + fake_nd->bl.m = -1; + fake_nd->bl.id = npc_get_new_npc_id(); + fake_nd->class_ = -1; + fake_nd->speed = 200; + strcpy(fake_nd->name,"FAKE_NPC"); + memcpy(fake_nd->exname, fake_nd->name, 9); + + npc_script++; + fake_nd->bl.type = BL_NPC; + fake_nd->subtype = SCRIPT; + + strdb_put(npcname_db, fake_nd->exname, fake_nd); + fake_nd->u.scr.timerid = INVALID_TIMER; + map_addiddb(&fake_nd->bl); + // End of initialization + + return 0; +} diff --git a/src/map/npc.h b/src/map/npc.h new file mode 100644 index 000000000..ee88da08c --- /dev/null +++ b/src/map/npc.h @@ -0,0 +1,184 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _NPC_H_ +#define _NPC_H_ + +#include "map.h" // struct block_list +#include "status.h" // struct status_change +#include "unit.h" // struct unit_data +struct block_list; +struct npc_data; +struct view_data; + + +struct npc_timerevent_list { + int timer,pos; +}; +struct npc_label_list { + char name[NAME_LENGTH]; + int pos; +}; +struct npc_item_list { + unsigned int nameid,value; +}; + +struct npc_data { + struct block_list bl; + struct unit_data ud; //Because they need to be able to move.... + struct view_data *vd; + struct status_change sc; //They can't have status changes, but.. they want the visual opt values. + struct npc_data *master_nd; + short class_; + short speed; + char name[NAME_LENGTH+1];// display name + char exname[NAME_LENGTH+1];// unique npc name + int chat_id; + int touching_id; + unsigned int next_walktime; + + unsigned size : 2; + + struct status_data status; + unsigned int level; + unsigned int stat_point; + + void* chatdb; // pointer to a npc_parse struct (see npc_chat.c) + char* path;/* path dir */ + enum npc_subtype subtype; + int src_id; + union { + struct { + struct script_code *script; + short xs,ys; // OnTouch area radius + int guild_id; + int timer,timerid,timeramount,rid; + unsigned int timertick; + struct npc_timerevent_list *timer_event; + int label_list_num; + struct npc_label_list *label_list; + } scr; + struct { + struct npc_item_list* shop_item; + int count; + } shop; + struct { + short xs,ys; // OnTouch area radius + short x,y; // destination coords + unsigned short mapindex; // destination map + } warp; + struct { + struct mob_data *md; + time_t kill_time; + char killer_name[NAME_LENGTH]; + } tomb; + } u; +}; + + + +#define START_NPC_NUM 110000000 + +enum actor_classes +{ + WARP_CLASS = 45, + HIDDEN_WARP_CLASS = 139, + WARP_DEBUG_CLASS = 722, + FLAG_CLASS = 722, + INVISIBLE_CLASS = 32767, +}; + +#define MAX_NPC_CLASS 1000 +//Checks if a given id is a valid npc id. [Skotlex] +//Since new npcs are added all the time, the max valid value is the one before the first mob (Scorpion = 1001) +#define npcdb_checkid(id) ( ( (id) >= 46 && (id) <= 125) || (id) == HIDDEN_WARP_CLASS || ( (id) > 400 && (id) < MAX_NPC_CLASS ) || (id) == INVISIBLE_CLASS ) + +#ifdef PCRE_SUPPORT +void npc_chat_finalize(struct npc_data* nd); +#endif + +//Script NPC events. +enum npce_event { + NPCE_LOGIN, + NPCE_LOGOUT, + NPCE_LOADMAP, + NPCE_BASELVUP, + NPCE_JOBLVUP, + NPCE_DIE, + NPCE_KILLPC, + NPCE_KILLNPC, + NPCE_MAX +}; +struct view_data* npc_get_viewdata(int class_); +int npc_chat_sub(struct block_list* bl, va_list ap); +int npc_event_dequeue(struct map_session_data* sd); +int npc_event(struct map_session_data* sd, const char* eventname, int ontouch); +int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y); +int npc_touch_areanpc2(struct mob_data *md); // [Skotlex] +int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range); +int npc_touchnext_areanpc(struct map_session_data* sd,bool leavemap); +int npc_click(struct map_session_data* sd, struct npc_data* nd); +int npc_scriptcont(struct map_session_data* sd, int id); +struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* bl); +int npc_buysellsel(struct map_session_data* sd, int id, int type); +int npc_buylist(struct map_session_data* sd,int n, unsigned short* item_list); +int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list); +void npc_parse_mob2(struct spawn_data* mob); +struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y); +int npc_globalmessage(const char* name,const char* mes); + +void npc_setcells(struct npc_data* nd); +void npc_unsetcells(struct npc_data* nd); +void npc_movenpc(struct npc_data* nd, int16 x, int16 y); +int npc_enable(const char* name, int flag); +void npc_setdisplayname(struct npc_data* nd, const char* newname); +void npc_setclass(struct npc_data* nd, short class_); +struct npc_data* npc_name2id(const char* name); + +int npc_get_new_npc_id(void); + +void npc_addsrcfile(const char* name); +void npc_delsrcfile(const char* name); +void npc_parsesrcfile(const char* filepath, bool runOnInit); +void do_clear_npc(void); +int do_final_npc(void); +int do_init_npc(void); +void npc_event_do_oninit(void); +int npc_do_ontimer(int npc_id, int option); + +int npc_event_do(const char* name); +int npc_event_doall(const char* name); +int npc_event_doall_id(const char* name, int rid); + +int npc_timerevent_start(struct npc_data* nd, int rid); +int npc_timerevent_stop(struct npc_data* nd); +void npc_timerevent_quit(struct map_session_data* sd); +int npc_gettimerevent_tick(struct npc_data* nd); +int npc_settimerevent_tick(struct npc_data* nd, int newtimer); +int npc_remove_map(struct npc_data* nd); +void npc_unload_duplicates (struct npc_data* nd); +int npc_unload(struct npc_data* nd, bool single); +int npc_reload(void); +void npc_read_event_script(void); +int npc_script_event(struct map_session_data* sd, enum npce_event type); + +int npc_duplicate4instance(struct npc_data *snd, int16 m); +int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points); + +extern struct npc_data* fake_nd; + +int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list); + +/** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data); +#endif + +// @commands (script-based) +int npc_do_atcmd_event(struct map_session_data* sd, const char* command, const char* message, const char* eventname); + +bool npc_unloadfile( const char* path ); + +#endif /* _NPC_H_ */ diff --git a/src/map/npc_chat.c b/src/map/npc_chat.c new file mode 100644 index 000000000..39a3a8584 --- /dev/null +++ b/src/map/npc_chat.c @@ -0,0 +1,450 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifdef PCRE_SUPPORT + +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" + +#include "mob.h" // struct mob_data +#include "npc.h" // struct npc_data +#include "pc.h" // struct map_session_data +#include "script.h" // set_var() + +#include "../../3rdparty/pcre/include/pcre.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + + +/** + * Written by MouseJstr in a vision... (2/21/2005) + * + * This allows you to make npc listen for spoken text (global + * messages) and pattern match against that spoken text using perl + * regular expressions. + * + * Please feel free to copy this code into your own personal ragnarok + * servers or distributions but please leave my name. Also, please + * wait until I've put it into the main eA branch which means I + * believe it is ready for distribution. + * + * So, how do people use this? + * + * The first and most important function is defpattern + * + * defpattern 1, "[^:]+: (.*) loves (.*)", "label"; + * + * this defines a new pattern in set 1 using perl syntax + * (http://www.troubleshooters.com/codecorn/littperl/perlreg.htm) + * and tells it to jump to the supplied label when the pattern + * is matched. + * + * each of the matched Groups will result in a variable being + * set ($@p1$ through $@p9$ with $@p0$ being the entire string) + * before the script gets executed. + * + * activatepset 1; + * + * This activates a set of patterns.. You can have many pattern + * sets defined and many active all at once. This feature allows + * you to set up "conversations" and ever changing expectations of + * the pattern matcher + * + * deactivatepset 1; + * + * turns off a pattern set; + * + * deactivatepset -1; + * + * turns off ALL pattern sets; + * + * deletepset 1; + * + * deletes a pset + */ + +/* Structure containing all info associated with a single pattern block */ +struct pcrematch_entry { + struct pcrematch_entry* next; + char* pattern; + pcre* pcre_; + pcre_extra* pcre_extra_; + char* label; +}; + +/* A set of patterns that can be activated and deactived with a single command */ +struct pcrematch_set { + struct pcrematch_set* prev; + struct pcrematch_set* next; + struct pcrematch_entry* head; + int setid; +}; + +/* + * Entire data structure hung off a NPC + * + * The reason I have done it this way (a void * in npc_data and then + * this) was to reduce the number of patches that needed to be applied + * to a ragnarok distribution to bring this code online. I + * also wanted people to be able to grab this one file to get updates + * without having to do a large number of changes. + */ +struct npc_parse { + struct pcrematch_set* active; + struct pcrematch_set* inactive; +}; + + +/** + * delete everythign associated with a entry + * + * This does NOT do the list management + */ +void finalize_pcrematch_entry(struct pcrematch_entry* e) +{ + pcre_free(e->pcre_); + pcre_free(e->pcre_extra_); + aFree(e->pattern); + aFree(e->label); +} + +/** + * Lookup (and possibly create) a new set of patterns by the set id + */ +static struct pcrematch_set* lookup_pcreset(struct npc_data* nd, int setid) +{ + struct pcrematch_set *pcreset; + struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; + if (npcParse == NULL) + nd->chatdb = npcParse = (struct npc_parse *) aCalloc(sizeof(struct npc_parse), 1); + + pcreset = npcParse->active; + + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + if (pcreset == NULL) + pcreset = npcParse->inactive; + + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + + if (pcreset == NULL) { + pcreset = (struct pcrematch_set *) aCalloc(sizeof(struct pcrematch_set), 1); + pcreset->next = npcParse->inactive; + if (pcreset->next != NULL) + pcreset->next->prev = pcreset; + pcreset->prev = 0; + npcParse->inactive = pcreset; + pcreset->setid = setid; + } + + return pcreset; +} + +/** + * activate a set of patterns. + * + * if the setid does not exist, this will silently return + */ +static void activate_pcreset(struct npc_data* nd, int setid) +{ + struct pcrematch_set *pcreset; + struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; + if (npcParse == NULL) + return; // Nothing to activate... + pcreset = npcParse->inactive; + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + if (pcreset == NULL) + return; // not in inactive list + if (pcreset->next != NULL) + pcreset->next->prev = pcreset->prev; + if (pcreset->prev != NULL) + pcreset->prev->next = pcreset->next; + else + npcParse->inactive = pcreset->next; + + pcreset->prev = NULL; + pcreset->next = npcParse->active; + if (pcreset->next != NULL) + pcreset->next->prev = pcreset; + npcParse->active = pcreset; +} + +/** + * deactivate a set of patterns. + * + * if the setid does not exist, this will silently return + */ +static void deactivate_pcreset(struct npc_data* nd, int setid) +{ + struct pcrematch_set *pcreset; + struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; + if (npcParse == NULL) + return; // Nothing to deactivate... + if (setid == -1) { + while(npcParse->active != NULL) + deactivate_pcreset(nd, npcParse->active->setid); + return; + } + pcreset = npcParse->active; + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + if (pcreset == NULL) + return; // not in active list + if (pcreset->next != NULL) + pcreset->next->prev = pcreset->prev; + if (pcreset->prev != NULL) + pcreset->prev->next = pcreset->next; + else + npcParse->active = pcreset->next; + + pcreset->prev = NULL; + pcreset->next = npcParse->inactive; + if (pcreset->next != NULL) + pcreset->next->prev = pcreset; + npcParse->inactive = pcreset; +} + +/** + * delete a set of patterns. + */ +static void delete_pcreset(struct npc_data* nd, int setid) +{ + int active = 1; + struct pcrematch_set *pcreset; + struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; + if (npcParse == NULL) + return; // Nothing to deactivate... + pcreset = npcParse->active; + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + if (pcreset == NULL) { + active = 0; + pcreset = npcParse->inactive; + while (pcreset != NULL) { + if (pcreset->setid == setid) + break; + pcreset = pcreset->next; + } + } + if (pcreset == NULL) + return; + + if (pcreset->next != NULL) + pcreset->next->prev = pcreset->prev; + if (pcreset->prev != NULL) + pcreset->prev->next = pcreset->next; + + if(active) + npcParse->active = pcreset->next; + else + npcParse->inactive = pcreset->next; + + pcreset->prev = NULL; + pcreset->next = NULL; + + while (pcreset->head) { + struct pcrematch_entry* n = pcreset->head->next; + finalize_pcrematch_entry(pcreset->head); + aFree(pcreset->head); // Cleanin' the last ones.. [Lance] + pcreset->head = n; + } + + aFree(pcreset); +} + +/** + * create a new pattern entry + */ +static struct pcrematch_entry* create_pcrematch_entry(struct pcrematch_set* set) +{ + struct pcrematch_entry * e = (struct pcrematch_entry *) aCalloc(sizeof(struct pcrematch_entry), 1); + struct pcrematch_entry * last = set->head; + + // Normally we would have just stuck it at the end of the list but + // this doesn't sink up with peoples usage pattern. They wanted + // the items defined first to have a higher priority then the + // items defined later. as a result, we have to do some work up front. + + /* if we are the first pattern, stick us at the end */ + if (last == NULL) { + set->head = e; + return e; + } + + /* Look for the last entry */ + while (last->next != NULL) + last = last->next; + + last->next = e; + e->next = NULL; + + return e; +} + +/** + * define/compile a new pattern + */ +void npc_chat_def_pattern(struct npc_data* nd, int setid, const char* pattern, const char* label) +{ + const char *err; + int erroff; + + struct pcrematch_set * s = lookup_pcreset(nd, setid); + struct pcrematch_entry *e = create_pcrematch_entry(s); + e->pattern = aStrdup(pattern); + e->label = aStrdup(label); + e->pcre_ = pcre_compile(pattern, PCRE_CASELESS, &err, &erroff, NULL); + e->pcre_extra_ = pcre_study(e->pcre_, 0, &err); +} + +/** + * Delete everything associated with a NPC concerning the pattern + * matching code + * + * this could be more efficent but.. how often do you do this? + */ +void npc_chat_finalize(struct npc_data* nd) +{ + struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; + if (npcParse == NULL) + return; + + while(npcParse->active) + delete_pcreset(nd, npcParse->active->setid); + + while(npcParse->inactive) + delete_pcreset(nd, npcParse->inactive->setid); + + // Additional cleaning up [Lance] + aFree(npcParse); +} + +/** + * Handler called whenever a global message is spoken in a NPC's area + */ +int npc_chat_sub(struct block_list* bl, va_list ap) +{ + struct npc_data* nd = (struct npc_data *) bl; + struct npc_parse* npcParse = (struct npc_parse *) nd->chatdb; + char* msg; + int len, i; + struct map_session_data* sd; + struct npc_label_list* lst; + struct pcrematch_set* pcreset; + struct pcrematch_entry* e; + + // Not interested in anything you might have to say... + if (npcParse == NULL || npcParse->active == NULL) + return 0; + + msg = va_arg(ap,char*); + len = va_arg(ap,int); + sd = va_arg(ap,struct map_session_data *); + + // iterate across all active sets + for (pcreset = npcParse->active; pcreset != NULL; pcreset = pcreset->next) + { + // interate across all patterns in that set + for (e = pcreset->head; e != NULL; e = e->next) + { + int offsets[2*10 + 10]; // 1/3 reserved for temp space requred by pcre_exec + + // perform pattern match + int r = pcre_exec(e->pcre_, e->pcre_extra_, msg, len, 0, 0, offsets, ARRAYLENGTH(offsets)); + if (r > 0) + { + // save out the matched strings + for (i = 0; i < r; i++) + { + char var[6], val[255]; + snprintf(var, sizeof(var), "$@p%i$", i); + pcre_copy_substring(msg, offsets, r, i, val, sizeof(val)); + set_var(sd, var, val); + } + + // find the target label.. this sucks.. + lst = nd->u.scr.label_list; + ARR_FIND(0, nd->u.scr.label_list_num, i, strncmp(lst[i].name, e->label, sizeof(lst[i].name)) == 0); + if (i == nd->u.scr.label_list_num) { + ShowWarning("Unable to find label: %s\n", e->label); + return 0; + } + + // run the npc script + run_script(nd->u.scr.script,lst[i].pos,sd->bl.id,nd->bl.id); + return 0; + } + } + } + + return 0; +} + +// Various script builtins used to support these functions + +int buildin_defpattern(struct script_state* st) +{ + int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); + const char* pattern = conv_str(st,& (st->stack->stack_data[st->start+3])); + const char* label = conv_str(st,& (st->stack->stack_data[st->start+4])); + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + + npc_chat_def_pattern(nd, setid, pattern, label); + + return 0; +} + +int buildin_activatepset(struct script_state* st) +{ + int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + + activate_pcreset(nd, setid); + + return 0; +} + +int buildin_deactivatepset(struct script_state* st) +{ + int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + + deactivate_pcreset(nd, setid); + + return 0; +} + +int buildin_deletepset(struct script_state* st) +{ + int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + + delete_pcreset(nd, setid); + + return 0; +} + +#endif //PCRE_SUPPORT diff --git a/src/map/party.c b/src/map/party.c new file mode 100644 index 000000000..f6b711791 --- /dev/null +++ b/src/map/party.c @@ -0,0 +1,1205 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/socket.h" // last_tick +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "../common/strlib.h" + +#include "party.h" +#include "atcommand.h" //msg_txt() +#include "pc.h" +#include "map.h" +#include "instance.h" +#include "battle.h" +#include "intif.h" +#include "clif.h" +#include "log.h" +#include "skill.h" +#include "status.h" +#include "itemdb.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +static DBMap* party_db; // int party_id -> struct party_data* (releases data) +static DBMap* party_booking_db; // int char_id -> struct party_booking_ad_info* (releases data) // Party Booking [Spiria] +static unsigned long party_booking_nextid = 1; + +int party_send_xy_timer(int tid, unsigned int tick, int id, intptr_t data); + +/*========================================== + * Fills the given party_member structure according to the sd provided. + * Used when creating/adding people to a party. [Skotlex] + *------------------------------------------*/ +static void party_fill_member(struct party_member* member, struct map_session_data* sd, unsigned int leader) +{ + member->account_id = sd->status.account_id; + member->char_id = sd->status.char_id; + safestrncpy(member->name, sd->status.name, NAME_LENGTH); + member->class_ = sd->status.class_; + member->map = sd->mapindex; + member->lv = sd->status.base_level; + member->online = 1; + member->leader = leader; +} + + +/// Get the member_id of a party member. +/// Return -1 if not in party. +int party_getmemberid(struct party_data* p, struct map_session_data* sd) +{ + int member_id; + nullpo_retr(-1, p); + if( sd == NULL ) + return -1;// no player + ARR_FIND(0, MAX_PARTY, member_id, + p->party.member[member_id].account_id == sd->status.account_id && + p->party.member[member_id].char_id == sd->status.char_id); + if( member_id == MAX_PARTY ) + return -1;// not found + return member_id; +} + + +/*========================================== + * Request an available sd of this party + *------------------------------------------*/ +struct map_session_data* party_getavailablesd(struct party_data *p) +{ + int i; + nullpo_retr(NULL, p); + ARR_FIND(0, MAX_PARTY, i, p->data[i].sd != NULL); + return( i < MAX_PARTY ) ? p->data[i].sd : NULL; +} + +/*========================================== + * Retrieves and validates the sd pointer for this party member [Skotlex] + *------------------------------------------*/ + +static TBL_PC* party_sd_check(int party_id, int account_id, int char_id) +{ + TBL_PC* sd = map_id2sd(account_id); + + if (!(sd && sd->status.char_id == char_id)) + return NULL; + + if( sd->status.party_id == 0 ) + sd->status.party_id = party_id;// auto-join if not in a party + if (sd->status.party_id != party_id) + { //If player belongs to a different party, kick him out. + intif_party_leave(party_id,account_id,char_id); + return NULL; + } + + return sd; +} + +/*========================================== + * Destructor + * Called in map shutdown, cleanup var + *------------------------------------------*/ +void do_final_party(void) +{ + party_db->destroy(party_db,NULL); + party_booking_db->destroy(party_booking_db,NULL); // Party Booking [Spiria] +} +// Constructor, init vars +void do_init_party(void) +{ + party_db = idb_alloc(DB_OPT_RELEASE_DATA); + party_booking_db = idb_alloc(DB_OPT_RELEASE_DATA); // Party Booking [Spiria] + add_timer_func_list(party_send_xy_timer, "party_send_xy_timer"); + add_timer_interval(gettick()+battle_config.party_update_interval, party_send_xy_timer, 0, 0, battle_config.party_update_interval); +} + +/// Party data lookup using party id. +struct party_data* party_search(int party_id) +{ + if(!party_id) + return NULL; + return (struct party_data*)idb_get(party_db,party_id); +} + +/// Party data lookup using party name. +struct party_data* party_searchname(const char* str) +{ + struct party_data* p; + + DBIterator *iter = db_iterator(party_db); + for( p = dbi_first(iter); dbi_exists(iter); p = dbi_next(iter) ) + { + if( strncmpi(p->party.name,str,NAME_LENGTH) == 0 ) + break; + } + dbi_destroy(iter); + + return p; +} + +int party_create(struct map_session_data *sd,char *name,int item,int item2) +{ + struct party_member leader; + char tname[NAME_LENGTH]; + + safestrncpy(tname, name, NAME_LENGTH); + trim(tname); + + if( !tname[0] ) + {// empty name + return 0; + } + + if( sd->status.party_id > 0 || sd->party_joining || sd->party_creating ) + {// already associated with a party + clif_party_created(sd,2); + return 0; + } + + sd->party_creating = true; + + party_fill_member(&leader, sd, 1); + + intif_create_party(&leader,name,item,item2); + return 0; +} + + +void party_created(int account_id,int char_id,int fail,int party_id,char *name) +{ + struct map_session_data *sd; + sd=map_id2sd(account_id); + + if (!sd || sd->status.char_id != char_id || !sd->party_creating ) + { //Character logged off before creation ack? + if (!fail) //break up party since player could not be added to it. + intif_party_leave(party_id,account_id,char_id); + return; + } + + sd->party_creating = false; + + if( !fail ) { + sd->status.party_id = party_id; + clif_party_created(sd,0); //Success message + //We don't do any further work here because the char-server sends a party info packet right after creating the party. + } else { + clif_party_created(sd,1); // "party name already exists" + } + +} + +int party_request_info(int party_id, int char_id) +{ + return intif_request_partyinfo(party_id, char_id); +} + +/// Invoked (from char-server) when the party info is not found. +int party_recv_noinfo(int party_id, int char_id) +{ + struct map_session_data* sd; + + party_broken(party_id); + if( char_id != 0 )// requester + { + sd = map_charid2sd(char_id); + if( sd && sd->status.party_id == party_id ) + sd->status.party_id = 0; + } + return 0; +} + +static void party_check_state(struct party_data *p) +{ + int i; + memset(&p->state, 0, sizeof(p->state)); + for (i = 0; i < MAX_PARTY; i ++) + { + if (!p->party.member[i].online) continue; //Those not online shouldn't aport to skill usage and all that. + switch (p->party.member[i].class_) { + case JOB_MONK: + case JOB_BABY_MONK: + case JOB_CHAMPION: + p->state.monk = 1; + break; + case JOB_STAR_GLADIATOR: + p->state.sg = 1; + break; + case JOB_SUPER_NOVICE: + case JOB_SUPER_BABY: + p->state.snovice = 1; + break; + case JOB_TAEKWON: + p->state.tk = 1; + break; + } + } +} + +int party_recv_info(struct party* sp, int char_id) +{ + struct party_data* p; + struct party_member* member; + struct map_session_data* sd; + int removed[MAX_PARTY];// member_id in old data + int removed_count = 0; + int added[MAX_PARTY];// member_id in new data + int added_count = 0; + int i; + int member_id; + + nullpo_ret(sp); + + p = (struct party_data*)idb_get(party_db, sp->party_id); + if( p != NULL )// diff members + { + for( member_id = 0; member_id < MAX_PARTY; ++member_id ) + { + member = &p->party.member[member_id]; + if( member->char_id == 0 ) + continue;// empty + ARR_FIND(0, MAX_PARTY, i, + sp->member[i].account_id == member->account_id && + sp->member[i].char_id == member->char_id); + if( i == MAX_PARTY ) + removed[removed_count++] = member_id; + } + for( member_id = 0; member_id < MAX_PARTY; ++member_id ) + { + member = &sp->member[member_id]; + if( member->char_id == 0 ) + continue;// empty + ARR_FIND(0, MAX_PARTY, i, + p->party.member[i].account_id == member->account_id && + p->party.member[i].char_id == member->char_id); + if( i == MAX_PARTY ) + added[added_count++] = member_id; + } + } + else + { + for( member_id = 0; member_id < MAX_PARTY; ++member_id ) + if( sp->member[member_id].char_id != 0 ) + added[added_count++] = member_id; + CREATE(p, struct party_data, 1); + idb_put(party_db, sp->party_id, p); + } + while( removed_count > 0 )// no longer in party + { + member_id = removed[--removed_count]; + sd = p->data[member_id].sd; + if( sd == NULL ) + continue;// not online + party_member_withdraw(sp->party_id, sd->status.account_id, sd->status.char_id); + } + memcpy(&p->party, sp, sizeof(struct party)); + memset(&p->state, 0, sizeof(p->state)); + memset(&p->data, 0, sizeof(p->data)); + for( member_id = 0; member_id < MAX_PARTY; member_id++ ) + { + member = &p->party.member[member_id]; + if ( member->char_id == 0 ) + continue;// empty + p->data[member_id].sd = party_sd_check(sp->party_id, member->account_id, member->char_id); + } + party_check_state(p); + while( added_count > 0 )// new in party + { + member_id = added[--added_count]; + sd = p->data[member_id].sd; + if( sd == NULL ) + continue;// not online + clif_charnameupdate(sd); //Update other people's display. [Skotlex] + clif_party_member_info(p,sd); + clif_party_option(p,sd,0x100); + clif_party_info(p,NULL); + if( p->instance_id != 0 ) + clif_instance_join(sd->fd, p->instance_id); + } + if( char_id != 0 )// requester + { + sd = map_charid2sd(char_id); + if( sd && sd->status.party_id == sp->party_id && party_getmemberid(p,sd) == -1 ) + sd->status.party_id = 0;// was not in the party + } + return 0; +} + +int party_invite(struct map_session_data *sd,struct map_session_data *tsd) +{ + struct party_data *p; + int i; + + nullpo_ret(sd); + + if( ( p = party_search(sd->status.party_id) ) == NULL ) + return 0; + + // confirm if this player is a party leader + ARR_FIND(0, MAX_PARTY, i, p->data[i].sd == sd); + + if( i == MAX_PARTY || !p->party.member[i].leader ) { + clif_displaymessage(sd->fd, msg_txt(282)); + return 0; + } + + // confirm if there is an open slot in the party + ARR_FIND(0, MAX_PARTY, i, p->party.member[i].account_id == 0); + + if( i == MAX_PARTY ) { + clif_party_inviteack(sd, (tsd?tsd->status.name:""), 3); + return 0; + } + + // confirm whether the account has the ability to invite before checking the player + if( !pc_has_permission(sd, PC_PERM_PARTY) || (tsd && !pc_has_permission(tsd, PC_PERM_PARTY)) ) { + clif_displaymessage(sd->fd, msg_txt(81)); // "Your GM level doesn't authorize you to preform this action on the specified player." + return 0; + } + + if( tsd == NULL) { + clif_party_inviteack(sd, "", 7); + return 0; + } + + if(!battle_config.invite_request_check) { + if (tsd->guild_invite>0 || tsd->trade_partner || tsd->adopt_invite) { + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + } + + if (!tsd->fd) { //You can't invite someone who has already disconnected. + clif_party_inviteack(sd,tsd->status.name,1); + return 0; + } + + if( tsd->status.party_id > 0 || tsd->party_invite > 0 ) + {// already associated with a party + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + + tsd->party_invite=sd->status.party_id; + tsd->party_invite_account=sd->status.account_id; + + clif_party_invite(sd,tsd); + return 1; +} + +void party_reply_invite(struct map_session_data *sd,int party_id,int flag) +{ + struct map_session_data* tsd; + struct party_member member; + + if( sd->party_invite != party_id ) + {// forged + sd->party_invite = 0; + sd->party_invite_account = 0; + return; + } + tsd = map_id2sd(sd->party_invite_account); + + if( flag == 1 && !sd->party_creating && !sd->party_joining ) + {// accepted and allowed + sd->party_joining = true; + party_fill_member(&member, sd, 0); + intif_party_addmember(sd->party_invite, &member); + } + else + {// rejected or failure + sd->party_invite = 0; + sd->party_invite_account = 0; + if( tsd != NULL ) + clif_party_inviteack(tsd,sd->status.name,1); + } +} + +//Invoked when a player joins: +//- Loads up party data if not in server +//- Sets up the pointer to him +//- Player must be authed/active and belong to a party before calling this method +void party_member_joined(struct map_session_data *sd) +{ + struct party_data* p = party_search(sd->status.party_id); + int i; + if (!p) + { + party_request_info(sd->status.party_id, sd->status.char_id); + return; + } + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == sd->status.account_id && p->party.member[i].char_id == sd->status.char_id ); + if (i < MAX_PARTY) + { + p->data[i].sd = sd; + if( p->instance_id ) + clif_instance_join(sd->fd,p->instance_id); + } + else + sd->status.party_id = 0; //He does not belongs to the party really? +} + +/// Invoked (from char-server) when a new member is added to the party. +/// flag: 0-success, 1-failure +int party_member_added(int party_id,int account_id,int char_id, int flag) +{ + struct map_session_data *sd = map_id2sd(account_id),*sd2; + struct party_data *p = party_search(party_id); + int i; + + if(sd == NULL || sd->status.char_id != char_id || !sd->party_joining ) { + if (!flag) //Char logged off before being accepted into party. + intif_party_leave(party_id,account_id,char_id); + return 0; + } + + sd2 = map_id2sd(sd->party_invite_account); + + sd->party_joining = false; + sd->party_invite = 0; + sd->party_invite_account = 0; + + if (!p) { + ShowError("party_member_added: party %d not found.\n",party_id); + intif_party_leave(party_id,account_id,char_id); + return 0; + } + + if( flag ) + {// failed + if( sd2 != NULL ) + clif_party_inviteack(sd2,sd->status.name,3); + return 0; + } + + sd->status.party_id = party_id; + + clif_party_member_info(p,sd); + clif_party_option(p,sd,0x100); + clif_party_info(p,sd); + + if( sd2 != NULL ) + clif_party_inviteack(sd2,sd->status.name,2); + + for( i = 0; i < ARRAYLENGTH(p->data); ++i ) + {// hp of the other party members + sd2 = p->data[i].sd; + if( sd2 && sd2->status.account_id != account_id && sd2->status.char_id != char_id ) + clif_hpmeter_single(sd->fd, sd2->bl.id, sd2->battle_status.hp, sd2->battle_status.max_hp); + } + clif_party_hp(sd); + clif_party_xy(sd); + clif_charnameupdate(sd); //Update char name's display [Skotlex] + + if( p->instance_id ) + clif_instance_join(sd->fd, p->instance_id); + + return 0; +} + +/// Party member 'sd' requesting kick of member with <account_id, name>. +int party_removemember(struct map_session_data* sd, int account_id, char* name) +{ + struct party_data *p; + int i; + + p = party_search(sd->status.party_id); + if( p == NULL ) + return 0; + + // check the requesting char's party membership + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == sd->status.account_id && p->party.member[i].char_id == sd->status.char_id ); + if( i == MAX_PARTY ) + return 0; // request from someone not in party? o.O + if( !p->party.member[i].leader ) + return 0; // only party leader may remove members + + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == account_id && strncmp(p->party.member[i].name,name,NAME_LENGTH) == 0 ); + if( i == MAX_PARTY ) + return 0; // no such char in party + + intif_party_leave(p->party.party_id,account_id,p->party.member[i].char_id); + return 1; +} + +/// Party member 'sd' requesting exit from party. +int party_leave(struct map_session_data *sd) +{ + struct party_data *p; + int i; + + p = party_search(sd->status.party_id); + if( p == NULL ) + return 0; + + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == sd->status.account_id && p->party.member[i].char_id == sd->status.char_id ); + if( i == MAX_PARTY ) + return 0; + + intif_party_leave(p->party.party_id,sd->status.account_id,sd->status.char_id); + return 1; +} + +/// Invoked (from char-server) when a party member leaves the party. +int party_member_withdraw(int party_id, int account_id, int char_id) +{ + struct map_session_data* sd = map_id2sd(account_id); + struct party_data* p = party_search(party_id); + + if( p ) + { + int i; + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == account_id && p->party.member[i].char_id == char_id ); + if( i < MAX_PARTY ) + { + clif_party_withdraw(p,sd,account_id,p->party.member[i].name,0x0); + memset(&p->party.member[i], 0, sizeof(p->party.member[0])); + memset(&p->data[i], 0, sizeof(p->data[0])); + p->party.count--; + party_check_state(p); + } + } + + if( sd && sd->status.party_id == party_id && sd->status.char_id == char_id ) + { + sd->status.party_id = 0; + clif_charnameupdate(sd); //Update name display [Skotlex] + //TODO: hp bars should be cleared too + if( p->instance_id ) + instance_check_kick(sd); + } + + return 0; +} + +/// Invoked (from char-server) when a party is disbanded. +int party_broken(int party_id) +{ + struct party_data* p; + int i; + + p = party_search(party_id); + if( p == NULL ) + return 0; + + if( p->instance_id ) + { + instance[p->instance_id].party_id = 0; + instance_destroy( p->instance_id ); + } + + for( i = 0; i < MAX_PARTY; i++ ) + { + if( p->data[i].sd!=NULL ) + { + clif_party_withdraw(p,p->data[i].sd,p->party.member[i].account_id,p->party.member[i].name,0x10); + p->data[i].sd->status.party_id=0; + } + } + + idb_remove(party_db,party_id); + return 0; +} + +int party_changeoption(struct map_session_data *sd,int exp,int item) +{ + nullpo_ret(sd); + + if( sd->status.party_id==0) + return 0; + intif_party_changeoption(sd->status.party_id,sd->status.account_id,exp,item); + return 0; +} + +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag) +{ + struct party_data *p; + struct map_session_data *sd=map_id2sd(account_id); + if( (p=party_search(party_id))==NULL) + return 0; + + //Flag&1: Exp change denied. Flag&2: Item change denied. + if(!(flag&0x01) && p->party.exp != exp) + p->party.exp=exp; + if(!(flag&0x10) && p->party.item != item) { + p->party.item=item; + } + + clif_party_option(p,sd,flag); + return 0; +} + +bool party_changeleader(struct map_session_data *sd, struct map_session_data *tsd) +{ + struct party_data *p; + int mi, tmi; + + if (!sd || !sd->status.party_id) + return false; + + if (!tsd || tsd->status.party_id != sd->status.party_id) { + clif_displaymessage(sd->fd, msg_txt(283)); + return false; + } + + if( map[sd->bl.m].flag.partylock ) + { + clif_displaymessage(sd->fd, msg_txt(287)); + return false; + } + + if ((p = party_search(sd->status.party_id)) == NULL) + return false; + + ARR_FIND( 0, MAX_PARTY, mi, p->data[mi].sd == sd ); + if (mi == MAX_PARTY) + return false; //Shouldn't happen + + if (!p->party.member[mi].leader) + { //Need to be a party leader. + clif_displaymessage(sd->fd, msg_txt(282)); + return false; + } + + ARR_FIND( 0, MAX_PARTY, tmi, p->data[tmi].sd == tsd); + if (tmi == MAX_PARTY) + return false; //Shouldn't happen + + //Change leadership. + p->party.member[mi].leader = 0; + if (p->data[mi].sd->fd) + clif_displaymessage(p->data[mi].sd->fd, msg_txt(284)); + + p->party.member[tmi].leader = 1; + if (p->data[tmi].sd->fd) + clif_displaymessage(p->data[tmi].sd->fd, msg_txt(285)); + + //Update info. + intif_party_leaderchange(p->party.party_id,p->party.member[tmi].account_id,p->party.member[tmi].char_id); + clif_party_info(p,NULL); + return true; +} + +/// Invoked (from char-server) when a party member +/// - changes maps +/// - logs in or out +/// - gains a level (disabled) +int party_recv_movemap(int party_id,int account_id,int char_id, unsigned short map,int online,int lv) +{ + struct party_member* m; + struct party_data* p; + int i; + + p = party_search(party_id); + if( p == NULL ) + return 0; + + ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == account_id && p->party.member[i].char_id == char_id ); + if( i == MAX_PARTY ) + { + ShowError("party_recv_movemap: char %d/%d not found in party %s (id:%d)",account_id,char_id,p->party.name,party_id); + return 0; + } + + m = &p->party.member[i]; + m->map = map; + m->online = online; + m->lv = lv; + //Check if they still exist on this map server + p->data[i].sd = party_sd_check(party_id, account_id, char_id); + + clif_party_info(p,NULL); + return 0; +} + +void party_send_movemap(struct map_session_data *sd) +{ + int i; + struct party_data *p; + + if( sd->status.party_id==0 ) + return; + + intif_party_changemap(sd,1); + + p=party_search(sd->status.party_id); + if (!p) return; + + if(sd->state.connect_new) { + //Note that this works because this function is invoked before connect_new is cleared. + clif_party_option(p,sd,0x100); + clif_party_info(p,sd); + clif_party_member_info(p,sd); + } + + if (sd->fd) { // synchronize minimap positions with the rest of the party + for(i=0; i < MAX_PARTY; i++) { + if (p->data[i].sd && + p->data[i].sd != sd && + p->data[i].sd->bl.m == sd->bl.m) + { + clif_party_xy_single(sd->fd, p->data[i].sd); + clif_party_xy_single(p->data[i].sd->fd, sd); + } + } + } + return; +} + +void party_send_levelup(struct map_session_data *sd) +{ + intif_party_changemap(sd,1); +} + +int party_send_logout(struct map_session_data *sd) +{ + struct party_data *p; + int i; + + if(!sd->status.party_id) + return 0; + + intif_party_changemap(sd,0); + p=party_search(sd->status.party_id); + if(!p) return 0; + + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); + if( i < MAX_PARTY ) + memset(&p->data[i], 0, sizeof(p->data[0])); + else + ShowError("party_send_logout: Failed to locate member %d:%d in party %d!\n", sd->status.account_id, sd->status.char_id, p->party.party_id); + + return 1; +} + +int party_send_message(struct map_session_data *sd,const char *mes,int len) +{ + if(sd->status.party_id==0) + return 0; + intif_party_message(sd->status.party_id,sd->status.account_id,mes,len); + party_recv_message(sd->status.party_id,sd->status.account_id,mes,len); + + // Chat logging type 'P' / Party Chat + log_chat(LOG_CHAT_PARTY, sd->status.party_id, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, mes); + + return 0; +} + +int party_recv_message(int party_id,int account_id,const char *mes,int len) +{ + struct party_data *p; + if( (p=party_search(party_id))==NULL) + return 0; + clif_party_message(p,account_id,mes,len); + return 0; +} + +int party_skill_check(struct map_session_data *sd, int party_id, uint16 skill_id, uint16 skill_lv) +{ + struct party_data *p; + struct map_session_data *p_sd; + int i; + + if(!party_id || (p=party_search(party_id))==NULL) + return 0; + switch(skill_id) { + case TK_COUNTER: //Increase Triple Attack rate of Monks. + if (!p->state.monk) return 0; + break; + case MO_COMBOFINISH: //Increase Counter rate of Star Gladiators + if (!p->state.sg) return 0; + break; + case AM_TWILIGHT2: //Twilight Pharmacy, requires Super Novice + return p->state.snovice; + case AM_TWILIGHT3: //Twilight Pharmacy, Requires Taekwon + return p->state.tk; + default: + return 0; //Unknown case? + } + + for(i=0;i<MAX_PARTY;i++){ + if ((p_sd = p->data[i].sd) == NULL) + continue; + if (sd->bl.m != p_sd->bl.m) + continue; + switch(skill_id) { + case TK_COUNTER: //Increase Triple Attack rate of Monks. + if((p_sd->class_&MAPID_UPPERMASK) == MAPID_MONK + && pc_checkskill(p_sd,MO_TRIPLEATTACK)) { + sc_start4(&p_sd->bl,SC_SKILLRATE_UP,100,MO_TRIPLEATTACK, + 50+50*skill_lv, //+100/150/200% rate + 0,0,skill_get_time(SG_FRIEND, 1)); + } + break; + case MO_COMBOFINISH: //Increase Counter rate of Star Gladiators + if((p_sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR + && sd->sc.data[SC_READYCOUNTER] + && pc_checkskill(p_sd,SG_FRIEND)) { + sc_start4(&p_sd->bl,SC_SKILLRATE_UP,100,TK_COUNTER, + 50+50*pc_checkskill(p_sd,SG_FRIEND), //+100/150/200% rate + 0,0,skill_get_time(SG_FRIEND, 1)); + } + break; + } + } + return 0; +} + +int party_send_xy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct party_data* p; + + DBIterator *iter = db_iterator(party_db); + // for each existing party, + for( p = dbi_first(iter); dbi_exists(iter); p = dbi_next(iter) ) + { + int i; + + if( !p->party.count ) + {// no online party members so do not iterate + continue; + } + + // for each member of this party, + for( i = 0; i < MAX_PARTY; i++ ) + { + struct map_session_data* sd = p->data[i].sd; + if( !sd ) continue; + + if( p->data[i].x != sd->bl.x || p->data[i].y != sd->bl.y ) + {// perform position update + clif_party_xy(sd); + p->data[i].x = sd->bl.x; + p->data[i].y = sd->bl.y; + } + if (battle_config.party_hp_mode && p->data[i].hp != sd->battle_status.hp) + {// perform hp update + clif_party_hp(sd); + p->data[i].hp = sd->battle_status.hp; + } + } + } + dbi_destroy(iter); + + return 0; +} + +int party_send_xy_clear(struct party_data *p) +{ + int i; + + nullpo_ret(p); + + for(i=0;i<MAX_PARTY;i++){ + if(!p->data[i].sd) continue; + p->data[i].hp = 0; + p->data[i].x = 0; + p->data[i].y = 0; + } + return 0; +} + +// exp share and added zeny share [Valaris] +int party_exp_share(struct party_data* p, struct block_list* src, unsigned int base_exp, unsigned int job_exp, int zeny) +{ + struct map_session_data* sd[MAX_PARTY]; + unsigned int i, c; + + nullpo_ret(p); + + // count the number of players eligible for exp sharing + for (i = c = 0; i < MAX_PARTY; i++) { + if( (sd[c] = p->data[i].sd) == NULL || sd[c]->bl.m != src->m || pc_isdead(sd[c]) || (battle_config.idle_no_share && pc_isidle(sd[c])) ) + continue; + c++; + } + if (c < 1) + return 0; + + base_exp/=c; + job_exp/=c; + zeny/=c; + + if (battle_config.party_even_share_bonus && c > 1) + { + double bonus = 100 + battle_config.party_even_share_bonus*(c-1); + if (base_exp) + base_exp = (unsigned int) cap_value(base_exp * bonus/100, 0, UINT_MAX); + if (job_exp) + job_exp = (unsigned int) cap_value(job_exp * bonus/100, 0, UINT_MAX); + if (zeny) + zeny = (unsigned int) cap_value(zeny * bonus/100, INT_MIN, INT_MAX); + } + + for (i = 0; i < c; i++) { +#ifdef RENEWAL_EXP + if( !(src && src->type == BL_MOB && ((TBL_MOB*)src)->db->mexp) ){ + int rate = pc_level_penalty_mod(sd[i], (TBL_MOB*)src, 1); + base_exp = (unsigned int)cap_value(base_exp * rate / 100, 1, UINT_MAX); + job_exp = (unsigned int)cap_value(job_exp * rate / 100, 1, UINT_MAX); + } +#endif + pc_gainexp(sd[i], src, base_exp, job_exp, false); + + if (zeny) // zeny from mobs [Valaris] + pc_getzeny(sd[i],zeny,LOG_TYPE_PICKDROP_MONSTER,NULL); + } + return 0; +} + +//Does party loot. first_charid holds the charid of the player who has time priority to take the item. +int party_share_loot(struct party_data* p, struct map_session_data* sd, struct item* item_data, int first_charid) +{ + TBL_PC* target = NULL; + int i; + if (p && p->party.item&2 && (first_charid || !(battle_config.party_share_type&1))) + { + //item distribution to party members. + if (battle_config.party_share_type&2) + { //Round Robin + TBL_PC* psd; + i = p->itemc; + do { + i++; + if (i >= MAX_PARTY) + i = 0; // reset counter to 1st person in party so it'll stop when it reaches "itemc" + + if( (psd = p->data[i].sd) == NULL || sd->bl.m != psd->bl.m || pc_isdead(psd) || (battle_config.idle_no_share && pc_isidle(psd)) ) + continue; + + if (pc_additem(psd,item_data,item_data->amount,LOG_TYPE_PICKDROP_PLAYER)) + continue; //Chosen char can't pick up loot. + + //Successful pick. + p->itemc = i; + target = psd; + break; + } while (i != p->itemc); + } + else + { //Random pick + TBL_PC* psd[MAX_PARTY]; + int count = 0; + //Collect pick candidates + for (i = 0; i < MAX_PARTY; i++) { + if( (psd[count] = p->data[i].sd) == NULL || psd[count]->bl.m != sd->bl.m || pc_isdead(psd[count]) || (battle_config.idle_no_share && pc_isidle(psd[count])) ) + continue; + + count++; + } + while (count > 0) { //Pick a random member. + i = rnd()%count; + if (pc_additem(psd[i],item_data,item_data->amount,LOG_TYPE_PICKDROP_PLAYER)) + { //Discard this receiver. + psd[i] = psd[count-1]; + count--; + } else { //Successful pick. + target = psd[i]; + break; + } + } + } + } + + if (!target) { + target = sd; //Give it to the char that picked it up + if ((i=pc_additem(sd,item_data,item_data->amount,LOG_TYPE_PICKDROP_PLAYER))) + return i; + } + + if( p && battle_config.party_show_share_picker && battle_config.show_picker_item_type&(1<<itemdb_type(item_data->nameid)) ) + clif_party_show_picker(target, item_data); + + return 0; +} + +int party_send_dot_remove(struct map_session_data *sd) +{ + if (sd->status.party_id) + clif_party_xy_remove(sd); + return 0; +} + +// To use for Taekwon's "Fighting Chant" +// int c = 0; +// party_foreachsamemap(party_sub_count, sd, 0, &c); +int party_sub_count(struct block_list *bl, va_list ap) +{ + struct map_session_data *sd = (TBL_PC *)bl; + + if (sd->state.autotrade) + return 0; + + if (battle_config.idle_no_share && pc_isidle(sd)) + return 0; + + return 1; +} + +/// Executes 'func' for each party member on the same map and in range (0:whole map) +int party_foreachsamemap(int (*func)(struct block_list*,va_list),struct map_session_data *sd,int range,...) +{ + struct party_data *p; + int i; + int x0,y0,x1,y1; + struct block_list *list[MAX_PARTY]; + int blockcount=0; + int total = 0; //Return value. + + nullpo_ret(sd); + + if((p=party_search(sd->status.party_id))==NULL) + return 0; + + x0=sd->bl.x-range; + y0=sd->bl.y-range; + x1=sd->bl.x+range; + y1=sd->bl.y+range; + + for(i=0;i<MAX_PARTY;i++) + { + struct map_session_data *psd = p->data[i].sd; + if(!psd) continue; + if(psd->bl.m!=sd->bl.m || !psd->bl.prev) + continue; + if(range && + (psd->bl.x<x0 || psd->bl.y<y0 || + psd->bl.x>x1 || psd->bl.y>y1 ) ) + continue; + list[blockcount++]=&psd->bl; + } + + map_freeblock_lock(); + + for(i=0;i<blockcount;i++) + { + va_list ap; + va_start(ap, range); + total += func(list[i], ap); + va_end(ap); + } + + map_freeblock_unlock(); + + return total; +} + +/*========================================== + * Party Booking in KRO [Spiria] + *------------------------------------------*/ + +static struct party_booking_ad_info* create_party_booking_data(void) +{ + struct party_booking_ad_info *pb_ad; + CREATE(pb_ad, struct party_booking_ad_info, 1); + pb_ad->index = party_booking_nextid++; + return pb_ad; +} + +void party_booking_register(struct map_session_data *sd, short level, short mapid, short* job) +{ + struct party_booking_ad_info *pb_ad; + int i; + + pb_ad = (struct party_booking_ad_info*)idb_get(party_booking_db, sd->status.char_id); + + if( pb_ad == NULL ) + { + pb_ad = create_party_booking_data(); + idb_put(party_booking_db, sd->status.char_id, pb_ad); + } + else + {// already registered + clif_PartyBookingRegisterAck(sd, 2); + return; + } + + memcpy(pb_ad->charname,sd->status.name,NAME_LENGTH); + pb_ad->starttime = (int)time(NULL); + pb_ad->p_detail.level = level; + pb_ad->p_detail.mapid = mapid; + + for(i=0;i<PARTY_BOOKING_JOBS;i++) + if(job[i] != 0xFF) + pb_ad->p_detail.job[i] = job[i]; + else pb_ad->p_detail.job[i] = -1; + + clif_PartyBookingRegisterAck(sd, 0); + clif_PartyBookingInsertNotify(sd, pb_ad); // Notice +} + +void party_booking_update(struct map_session_data *sd, short* job) +{ + int i; + struct party_booking_ad_info *pb_ad; + + pb_ad = (struct party_booking_ad_info*)idb_get(party_booking_db, sd->status.char_id); + + if( pb_ad == NULL ) + return; + + pb_ad->starttime = (int)time(NULL);// Update time. + + for(i=0;i<PARTY_BOOKING_JOBS;i++) + if(job[i] != 0xFF) + pb_ad->p_detail.job[i] = job[i]; + else pb_ad->p_detail.job[i] = -1; + + clif_PartyBookingUpdateNotify(sd, pb_ad); +} + +void party_booking_search(struct map_session_data *sd, short level, short mapid, short job, unsigned long lastindex, short resultcount) +{ + struct party_booking_ad_info *pb_ad; + int i, count=0; + struct party_booking_ad_info* result_list[PARTY_BOOKING_RESULTS]; + bool more_result = false; + DBIterator* iter = db_iterator(party_booking_db); + + memset(result_list, 0, sizeof(result_list)); + + for( pb_ad = dbi_first(iter); dbi_exists(iter); pb_ad = dbi_next(iter) ) + { + if (pb_ad->index < lastindex || (level && (pb_ad->p_detail.level < level-15 || pb_ad->p_detail.level > level))) + continue; + if (count >= PARTY_BOOKING_RESULTS){ + more_result = true; + break; + } + if (mapid == 0 && job == -1) + result_list[count] = pb_ad; + else if (mapid == 0) { + for(i=0; i<PARTY_BOOKING_JOBS; i++) + if (pb_ad->p_detail.job[i] == job && job != -1) + result_list[count] = pb_ad; + } else if (job == -1){ + if (pb_ad->p_detail.mapid == mapid) + result_list[count] = pb_ad; + } + if( result_list[count] ) + { + count++; + } + } + dbi_destroy(iter); + clif_PartyBookingSearchAck(sd->fd, result_list, count, more_result); +} + +bool party_booking_delete(struct map_session_data *sd) +{ + struct party_booking_ad_info* pb_ad; + + if((pb_ad = (struct party_booking_ad_info*)idb_get(party_booking_db, sd->status.char_id))!=NULL) + { + clif_PartyBookingDeleteNotify(sd, pb_ad->index); + idb_remove(party_booking_db,sd->status.char_id); + } + return true; +} diff --git a/src/map/party.h b/src/map/party.h new file mode 100644 index 000000000..12fe7a2bc --- /dev/null +++ b/src/map/party.h @@ -0,0 +1,95 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PARTY_H_ +#define _PARTY_H_ + +#include "../common/mmo.h" // struct party +struct block_list; +struct map_session_data; +struct party; +struct item; + +#include <stdarg.h> + +#define PARTY_BOOKING_JOBS 6 +#define PARTY_BOOKING_RESULTS 10 + +struct party_member_data { + struct map_session_data *sd; + unsigned int hp; //For HP,x,y refreshing. + unsigned short x, y; +}; + +struct party_data { + struct party party; + struct party_member_data data[MAX_PARTY]; + uint8 itemc; //For item distribution, position of last picker in party + unsigned int instance_id; + struct { + unsigned monk : 1; //There's at least one monk in party? + unsigned sg : 1; //There's at least one Star Gladiator in party? + unsigned snovice :1; //There's a Super Novice + unsigned tk : 1; //There's a taekwon + } state; +}; + +struct party_booking_detail { + short level; + short mapid; + short job[PARTY_BOOKING_JOBS]; +}; + +struct party_booking_ad_info { + unsigned long index; + char charname[NAME_LENGTH]; + long starttime; + struct party_booking_detail p_detail; +}; + +void do_init_party(void); +void do_final_party(void); +struct party_data* party_search(int party_id); +struct party_data* party_searchname(const char* str); +int party_getmemberid(struct party_data* p, struct map_session_data* sd); +struct map_session_data* party_getavailablesd(struct party_data *p); + +int party_create(struct map_session_data *sd,char *name, int item, int item2); +void party_created(int account_id,int char_id,int fail,int party_id,char *name); +int party_request_info(int party_id, int char_id); +int party_invite(struct map_session_data *sd,struct map_session_data *tsd); +void party_member_joined(struct map_session_data *sd); +int party_member_added(int party_id,int account_id,int char_id,int flag); +int party_leave(struct map_session_data *sd); +int party_removemember(struct map_session_data *sd,int account_id,char *name); +int party_member_withdraw(int party_id,int account_id,int char_id); +void party_reply_invite(struct map_session_data *sd,int party_id,int flag); +int party_recv_noinfo(int party_id, int char_id); +int party_recv_info(struct party* sp, int char_id); +int party_recv_movemap(int party_id,int account_id,int char_id, unsigned short map,int online,int lv); +int party_broken(int party_id); +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag); +int party_changeoption(struct map_session_data *sd,int exp,int item); +bool party_changeleader(struct map_session_data *sd, struct map_session_data *t_sd); +void party_send_movemap(struct map_session_data *sd); +void party_send_levelup(struct map_session_data *sd); +int party_send_logout(struct map_session_data *sd); +int party_send_message(struct map_session_data *sd,const char *mes,int len); +int party_recv_message(int party_id,int account_id,const char *mes,int len); +int party_skill_check(struct map_session_data *sd, int party_id, uint16 skill_id, uint16 skill_lv); +int party_send_xy_clear(struct party_data *p); +int party_exp_share(struct party_data *p,struct block_list *src,unsigned int base_exp,unsigned int job_exp,int zeny); +int party_share_loot(struct party_data* p, struct map_session_data* sd, struct item* item_data, int first_charid); +int party_send_dot_remove(struct map_session_data *sd); +int party_sub_count(struct block_list *bl, va_list ap); +int party_foreachsamemap(int (*func)(struct block_list *,va_list),struct map_session_data *sd,int range,...); + +/*========================================== + * Party Booking in KRO [Spiria] + *------------------------------------------*/ +void party_booking_register(struct map_session_data *sd, short level, short mapid, short* job); +void party_booking_update(struct map_session_data *sd, short* job); +void party_booking_search(struct map_session_data *sd, short level, short mapid, short job, unsigned long lastindex, short resultcount); +bool party_booking_delete(struct map_session_data *sd); + +#endif /* _PARTY_H_ */ diff --git a/src/map/path.c b/src/map/path.c new file mode 100644 index 000000000..3bbd8d20b --- /dev/null +++ b/src/map/path.c @@ -0,0 +1,464 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "map.h" +#include "battle.h" +#include "path.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +#define MAX_HEAP 150 + +struct tmp_path { short x,y,dist,before,cost,flag;}; +#define calc_index(x,y) (((x)+(y)*MAX_WALKPATH) & (MAX_WALKPATH*MAX_WALKPATH-1)) + +const char walk_choices [3][3] = +{ + {1,0,7}, + {2,-1,6}, + {3,4,5}, +}; + +/*========================================== + * heap push (helper function) + *------------------------------------------*/ +static void push_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + h = heap[0]; + heap[0]++; + + for( i = (h-1)/2; h > 0 && tp[index].cost < tp[heap[i+1]].cost; i = (h-1)/2 ) + heap[h+1] = heap[i+1], h = i; + + heap[h+1] = index; +} + +/*========================================== + * heap update (helper function) + * Move toward the root because cost has decreased. + *------------------------------------------*/ +static void update_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + ARR_FIND( 0, heap[0], h, heap[h+1] == index ); + if( h == heap[0] ) + { + ShowError("update_heap_path bug\n"); + exit(EXIT_FAILURE); + } + + for( i = (h-1)/2; h > 0 && tp[index].cost < tp[heap[i+1]].cost; i = (h-1)/2 ) + heap[h+1] = heap[i+1], h = i; + + heap[h+1] = index; +} + +/*========================================== + * heap pop (helper function) + *------------------------------------------*/ +static int pop_heap_path(int *heap,struct tmp_path *tp) +{ + int i,h,k; + int ret,last; + + if( heap[0] <= 0 ) + return -1; + ret = heap[1]; + last = heap[heap[0]]; + heap[0]--; + + for( h = 0, k = 2; k < heap[0]; k = k*2+2 ) + { + if( tp[heap[k+1]].cost > tp[heap[k]].cost ) + k--; + heap[h+1] = heap[k+1], h = k; + } + + if( k == heap[0] ) + heap[h+1] = heap[k], h = k-1; + + for( i = (h-1)/2; h > 0 && tp[heap[i+1]].cost > tp[last].cost; i = (h-1)/2 ) + heap[h+1] = heap[i+1], h = i; + + heap[h+1]=last; + + return ret; +} + +/*========================================== + * calculate cost for the specified position + *------------------------------------------*/ +static int calc_cost(struct tmp_path *p,int16 x1,int16 y1) +{ + int xd = abs(x1 - p->x); + int yd = abs(y1 - p->y); + return (xd + yd)*10 + p->dist; +} + +/*========================================== + * attach/adjust path if neccessary + *------------------------------------------*/ +static int add_path(int *heap,struct tmp_path *tp,int16 x,int16 y,int dist,int before,int cost) +{ + int i; + + i = calc_index(x,y); + + if( tp[i].x == x && tp[i].y == y ) + { + if( tp[i].dist > dist ) + { + tp[i].dist = dist; + tp[i].before = before; + tp[i].cost = cost; + if( tp[i].flag ) + push_heap_path(heap,tp,i); + else + update_heap_path(heap,tp,i); + tp[i].flag = 0; + } + return 0; + } + + if( tp[i].x || tp[i].y ) + return 1; + + tp[i].x = x; + tp[i].y = y; + tp[i].dist = dist; + tp[i].before = before; + tp[i].cost = cost; + tp[i].flag = 0; + push_heap_path(heap,tp,i); + + return 0; +} + +/*========================================== + * Find the closest reachable cell, 'count' cells away from (x0,y0) in direction (dx,dy). + * Income after the coordinates of the blow + *------------------------------------------*/ +int path_blownpos(int16 m,int16 x0,int16 y0,int16 dx,int16 dy,int count) +{ + struct map_data *md; + + if( !map[m].cell ) + return -1; + md = &map[m]; + + if( count>25 ){ //Cap to prevent too much processing...? + ShowWarning("path_blownpos: count too many %d !\n",count); + count=25; + } + if( dx > 1 || dx < -1 || dy > 1 || dy < -1 ){ + ShowError("path_blownpos: illegal dx=%d or dy=%d !\n",dx,dy); + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + } + + while( count > 0 && (dx != 0 || dy != 0) ) + { + if( !map_getcellp(md,x0+dx,y0+dy,CELL_CHKPASS) ) + {// attempt partial movement + int fx = ( dx != 0 && map_getcellp(md,x0+dx,y0,CELL_CHKPASS) ); + int fy = ( dy != 0 && map_getcellp(md,x0,y0+dy,CELL_CHKPASS) ); + if( fx && fy ) + { + if(rnd()&1) + dx=0; + else + dy=0; + } + if( !fx ) + dx=0; + if( !fy ) + dy=0; + } + + x0 += dx; + y0 += dy; + count--; + } + + return (x0<<16)|y0; //TODO: use 'struct point' here instead? +} + +/*========================================== + * is ranged attack from (x0,y0) to (x1,y1) possible? + *------------------------------------------*/ +bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16 x1,int16 y1,cell_chk cell) +{ + int dx, dy; + int wx = 0, wy = 0; + int weight; + struct map_data *md; + struct shootpath_data s_spd; + + if( spd == NULL ) + spd = &s_spd; // use dummy output variable + + if (!map[m].cell) + return false; + md = &map[m]; + + dx = (x1 - x0); + if (dx < 0) { + swap(x0, x1); + swap(y0, y1); + dx = -dx; + } + dy = (y1 - y0); + + spd->rx = spd->ry = 0; + spd->len = 1; + spd->x[0] = x0; + spd->y[0] = y0; + + if (map_getcellp(md,x1,y1,cell)) + return false; + + if (dx > abs(dy)) { + weight = dx; + spd->ry = 1; + } else { + weight = abs(y1 - y0); + spd->rx = 1; + } + + while (x0 != x1 || y0 != y1) + { + if (map_getcellp(md,x0,y0,cell)) + return false; + wx += dx; + wy += dy; + if (wx >= weight) { + wx -= weight; + x0++; + } + if (wy >= weight) { + wy -= weight; + y0++; + } else if (wy < 0) { + wy += weight; + y0--; + } + if( spd->len<MAX_WALKPATH ) + { + spd->x[spd->len] = x0; + spd->y[spd->len] = y0; + spd->len++; + } + } + + return true; +} + +/*========================================== + * path search (x0,y0)->(x1,y1) + * wpd: path info will be written here + * flag: &1 = easy path search only + * cell: type of obstruction to check for + *------------------------------------------*/ +bool path_search(struct walkpath_data *wpd,int16 m,int16 x0,int16 y0,int16 x1,int16 y1,int flag,cell_chk cell) +{ + int heap[MAX_HEAP+1]; + struct tmp_path tp[MAX_WALKPATH*MAX_WALKPATH]; + register int i,j,len,x,y,dx,dy; + int rp,xs,ys; + struct map_data *md; + struct walkpath_data s_wpd; + + if( wpd == NULL ) + wpd = &s_wpd; // use dummy output variable + + if( !map[m].cell ) + return false; + md = &map[m]; + +#ifdef CELL_NOSTACK + //Do not check starting cell as that would get you stuck. + if( x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys ) +#else + if( x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys /*|| map_getcellp(md,x0,y0,cell)*/ ) +#endif + return false; + if( x1 < 0 || x1 >= md->xs || y1 < 0 || y1 >= md->ys || map_getcellp(md,x1,y1,cell) ) + return false; + + // calculate (sgn(x1-x0), sgn(y1-y0)) + dx = ((dx = x1-x0)) ? ((dx<0) ? -1 : 1) : 0; + dy = ((dy = y1-y0)) ? ((dy<0) ? -1 : 1) : 0; + + // try finding direct path to target + x = x0; + y = y0; + i = 0; + while( i < ARRAYLENGTH(wpd->path) ) + { + wpd->path[i] = walk_choices[-dy + 1][dx + 1]; + i++; + + x += dx; + y += dy; + + if( x == x1 ) dx = 0; + if( y == y1 ) dy = 0; + + if( dx == 0 && dy == 0 ) + break; // success + if( map_getcellp(md,x,y,cell) ) + break; // obstacle = failure + } + + if( x == x1 && y == y1 ) + { //easy path successful. + wpd->path_len = i; + wpd->path_pos = 0; + return true; + } + + if( flag&1 ) + return false; + + memset(tp,0,sizeof(tp)); + + i=calc_index(x0,y0); + tp[i].x=x0; + tp[i].y=y0; + tp[i].dist=0; + tp[i].before=0; + tp[i].cost=calc_cost(&tp[i],x1,y1); + tp[i].flag=0; + heap[0]=0; + push_heap_path(heap,tp,calc_index(x0,y0)); + xs = md->xs - 1; // Place by subtracting a pre- + ys = md->ys-1; + + for(;;) + { + int e=0,f=0,dist,cost,dc[4]={0,0,0,0}; + + if(heap[0]==0) + return false; + rp = pop_heap_path(heap,tp); + x = tp[rp].x; + y = tp[rp].y; + dist = tp[rp].dist + 10; + cost = tp[rp].cost; + + if(x==x1 && y==y1) + break; + + // dc[0] : y++ Incremental cost at the time + // dc[1] : x-- + // dc[2] : y-- + // dc[3] : x++ + + if(y < ys && !map_getcellp(md,x ,y+1,cell)) { + f |= 1; dc[0] = (y >= y1 ? 20 : 0); + e+=add_path(heap,tp,x ,y+1,dist,rp,cost+dc[0]); // (x, y+1) + } + if(x > 0 && !map_getcellp(md,x-1,y ,cell)) { + f |= 2; dc[1] = (x <= x1 ? 20 : 0); + e+=add_path(heap,tp,x-1,y ,dist,rp,cost+dc[1]); // (x-1, y ) + } + if(y > 0 && !map_getcellp(md,x ,y-1,cell)) { + f |= 4; dc[2] = (y <= y1 ? 20 : 0); + e+=add_path(heap,tp,x ,y-1,dist,rp,cost+dc[2]); // (x , y-1) + } + if(x < xs && !map_getcellp(md,x+1,y ,cell)) { + f |= 8; dc[3] = (x >= x1 ? 20 : 0); + e+=add_path(heap,tp,x+1,y ,dist,rp,cost+dc[3]); // (x+1, y ) + } + if( (f & (2+1)) == (2+1) && !map_getcellp(md,x-1,y+1,cell)) + e+=add_path(heap,tp,x-1,y+1,dist+4,rp,cost+dc[1]+dc[0]-6); // (x-1, y+1) + if( (f & (2+4)) == (2+4) && !map_getcellp(md,x-1,y-1,cell)) + e+=add_path(heap,tp,x-1,y-1,dist+4,rp,cost+dc[1]+dc[2]-6); // (x-1, y-1) + if( (f & (8+4)) == (8+4) && !map_getcellp(md,x+1,y-1,cell)) + e+=add_path(heap,tp,x+1,y-1,dist+4,rp,cost+dc[3]+dc[2]-6); // (x+1, y-1) + if( (f & (8+1)) == (8+1) && !map_getcellp(md,x+1,y+1,cell)) + e+=add_path(heap,tp,x+1,y+1,dist+4,rp,cost+dc[3]+dc[0]-6); // (x+1, y+1) + tp[rp].flag=1; + if(e || heap[0]>=MAX_HEAP-5) + return false; + } + + if( !(x==x1 && y==y1) ) // will never happen... + return false; + + for(len=0,i=rp;len<100 && i!=calc_index(x0,y0);i=tp[i].before,len++); + if(len==100 || len>=sizeof(wpd->path)) + return false; + + wpd->path_len = len; + wpd->path_pos = 0; + for(i=rp,j=len-1;j>=0;i=tp[i].before,j--) { + int dx = tp[i].x - tp[tp[i].before].x; + int dy = tp[i].y - tp[tp[i].before].y; + uint8 dir; + if( dx == 0 ) { + dir = (dy > 0 ? 0 : 4); + } else if( dx > 0 ) { + dir = (dy == 0 ? 6 : (dy < 0 ? 5 : 7) ); + } else { + dir = (dy == 0 ? 2 : (dy > 0 ? 1 : 3) ); + } + wpd->path[j] = dir; + } + + return true; +} + + +//Distance functions, taken from http://www.flipcode.com/articles/article_fastdistance.shtml +int check_distance(int dx, int dy, int distance) +{ +#ifdef CIRCULAR_AREA + //In this case, we just do a square comparison. Add 1 tile grace for diagonal range checks. + return (dx*dx + dy*dy <= distance*distance + (dx&&dy?1:0)); +#else + if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + return ((dx<dy?dy:dx) <= distance); +#endif +} + +unsigned int distance(int dx, int dy) +{ +#ifdef CIRCULAR_AREA + unsigned int min, max; + + if ( dx < 0 ) dx = -dx; + if ( dy < 0 ) dy = -dy; + //There appears to be something wrong with the aproximation below when either dx/dy is 0! [Skotlex] + if ( dx == 0 ) return dy; + if ( dy == 0 ) return dx; + + if ( dx < dy ) + { + min = dx; + max = dy; + } else { + min = dy; + max = dx; + } + // coefficients equivalent to ( 123/128 * max ) and ( 51/128 * min ) + return ((( max << 8 ) + ( max << 3 ) - ( max << 4 ) - ( max << 1 ) + + ( min << 7 ) - ( min << 5 ) + ( min << 3 ) - ( min << 1 )) >> 8 ); +#else + if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + return (dx<dy?dy:dx); +#endif +} diff --git a/src/map/path.h b/src/map/path.h new file mode 100644 index 000000000..b1ca71955 --- /dev/null +++ b/src/map/path.h @@ -0,0 +1,43 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PATH_H_ +#define _PATH_H_ + +#include "map.h" // enum cell_chk + +#define MAX_WALKPATH 32 + +struct walkpath_data { + unsigned char path_len,path_pos; + unsigned char path[MAX_WALKPATH]; +}; + +struct shootpath_data { + int rx,ry,len; + int x[MAX_WALKPATH]; + int y[MAX_WALKPATH]; +}; + +// calculates destination cell for knockback +int path_blownpos(int16 m,int16 x0,int16 y0,int16 dx,int16 dy,int count); + +// tries to find a walkable path +bool path_search(struct walkpath_data *wpd,int16 m,int16 x0,int16 y0,int16 x1,int16 y1,int flag,cell_chk cell); + +// tries to find a shootable path +bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16 x1,int16 y1,cell_chk cell); + + +// distance related functions +int check_distance(int dx, int dy, int distance); +#define check_distance_bl(bl1, bl2, distance) check_distance((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y, distance) +#define check_distance_blxy(bl, x1, y1, distance) check_distance((bl)->x-(x1), (bl)->y-(y1), distance) +#define check_distance_xy(x0, y0, x1, y1, distance) check_distance((x0)-(x1), (y0)-(y1), distance) + +unsigned int distance(int dx, int dy); +#define distance_bl(bl1, bl2) distance((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y) +#define distance_blxy(bl, x1, y1) distance((bl)->x-(x1), (bl)->y-(y1)) +#define distance_xy(x0, y0, x1, y1) distance((x0)-(x1), (y0)-(y1)) + +#endif /* _PATH_H_ */ diff --git a/src/map/pc.c b/src/map/pc.c new file mode 100644 index 000000000..f06d48779 --- /dev/null +++ b/src/map/pc.c @@ -0,0 +1,9720 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/core.h" // get_svn_revision() +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/socket.h" // session[] +#include "../common/strlib.h" // safestrncpy() +#include "../common/timer.h" +#include "../common/utils.h" +#include "../common/mmo.h" //NAME_LENGTH + +#include "atcommand.h" // get_atcommand_level() +#include "battle.h" // battle_config +#include "battleground.h" +#include "chrif.h" +#include "clif.h" +#include "date.h" // is_day_of_*() +#include "duel.h" +#include "intif.h" +#include "itemdb.h" +#include "log.h" +#include "mail.h" +#include "map.h" +#include "path.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "npc.h" // fake_nd +#include "pet.h" // pet_unlocktarget() +#include "party.h" // party_search() +#include "guild.h" // guild_search(), guild_request_info() +#include "script.h" // script_config +#include "skill.h" +#include "status.h" // struct status_data +#include "pc.h" +#include "pc_groups.h" +#include "quest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + + +#define PVP_CALCRANK_INTERVAL 1000 // PVP calculation interval +static unsigned int exp_table[CLASS_COUNT][2][MAX_LEVEL]; +static unsigned int max_level[CLASS_COUNT][2]; +static unsigned int statp[MAX_LEVEL+1]; +#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) +static unsigned int level_penalty[3][RC_MAX][MAX_LEVEL*2+1]; +#endif + +// h-files are for declarations, not for implementations... [Shinomori] +struct skill_tree_entry skill_tree[CLASS_COUNT][MAX_SKILL_TREE]; +// timer for night.day implementation +int day_timer_tid; +int night_timer_tid; + +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]; + +static unsigned short equip_pos[EQI_MAX]={EQP_ACC_L,EQP_ACC_R,EQP_SHOES,EQP_GARMENT,EQP_HEAD_LOW,EQP_HEAD_MID,EQP_HEAD_TOP,EQP_ARMOR,EQP_HAND_L,EQP_HAND_R,EQP_COSTUME_HEAD_TOP,EQP_COSTUME_HEAD_MID,EQP_COSTUME_HEAD_LOW,EQP_AMMO}; + +#define MOTD_LINE_SIZE 128 +static char motd_text[MOTD_LINE_SIZE][CHAT_SIZE_MAX]; // Message of the day buffer [Valaris] + +//Links related info to the sd->hate_mob[]/sd->feel_map[] entries +const struct sg_data sg_info[MAX_PC_FEELHATE] = { + { SG_SUN_ANGER, SG_SUN_BLESS, SG_SUN_COMFORT, "PC_FEEL_SUN", "PC_HATE_MOB_SUN", is_day_of_sun }, + { SG_MOON_ANGER, SG_MOON_BLESS, SG_MOON_COMFORT, "PC_FEEL_MOON", "PC_HATE_MOB_MOON", is_day_of_moon }, + { SG_STAR_ANGER, SG_STAR_BLESS, SG_STAR_COMFORT, "PC_FEEL_STAR", "PC_HATE_MOB_STAR", is_day_of_star } + }; + +/** + * Item Cool Down Delay Saving + * Struct item_cd is not a member of struct map_session_data + * to keep cooldowns in memory between player log-ins. + * All cooldowns are reset when server is restarted. + **/ +DBMap* itemcd_db = NULL; // char_id -> struct skill_cd +struct item_cd { + unsigned int tick[MAX_ITEMDELAYS];//tick + short nameid[MAX_ITEMDELAYS];//skill id +}; + +//Converts a class to its array index for CLASS_COUNT defined arrays. +//Note that it does not do a validity check for speed purposes, where parsing +//player input make sure to use a pcdb_checkid first! +int pc_class2idx(int class_) { + if (class_ >= JOB_NOVICE_HIGH) + return class_- JOB_NOVICE_HIGH+JOB_MAX_BASIC; + return class_; +} + +inline int pc_get_group_id(struct map_session_data *sd) { + return sd->group_id; +} + +inline int pc_get_group_level(struct map_session_data *sd) { + return sd->group_level; +} + +static int pc_invincible_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if(sd->invincible_timer != tid){ + ShowError("invincible_timer %d != %d\n",sd->invincible_timer,tid); + return 0; + } + sd->invincible_timer = INVALID_TIMER; + skill_unit_move(&sd->bl,tick,1); + + return 0; +} + +void pc_setinvincibletimer(struct map_session_data* sd, int val) +{ + nullpo_retv(sd); + + if( sd->invincible_timer != INVALID_TIMER ) + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = add_timer(gettick()+val,pc_invincible_timer,sd->bl.id,0); +} + +void pc_delinvincibletimer(struct map_session_data* sd) +{ + nullpo_retv(sd); + + if( sd->invincible_timer != INVALID_TIMER ) + { + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = INVALID_TIMER; + skill_unit_move(&sd->bl,gettick(),1); + } +} + +static int pc_spiritball_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + int i; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if( sd->spiritball <= 0 ) + { + ShowError("pc_spiritball_timer: %d spiritball's available. (aid=%d cid=%d tid=%d)\n", sd->spiritball, sd->status.account_id, sd->status.char_id, tid); + sd->spiritball = 0; + return 0; + } + + ARR_FIND(0, sd->spiritball, i, sd->spirit_timer[i] == tid); + if( i == sd->spiritball ) + { + ShowError("pc_spiritball_timer: timer not found (aid=%d cid=%d tid=%d)\n", sd->status.account_id, sd->status.char_id, tid); + return 0; + } + + sd->spiritball--; + if( i != sd->spiritball ) + memmove(sd->spirit_timer+i, sd->spirit_timer+i+1, (sd->spiritball-i)*sizeof(int)); + sd->spirit_timer[sd->spiritball] = INVALID_TIMER; + + clif_spiritball(&sd->bl); + + return 0; +} + +int pc_addspiritball(struct map_session_data *sd,int interval,int max) +{ + int tid, i; + + nullpo_ret(sd); + + if(max > MAX_SKILL_LEVEL) + max = MAX_SKILL_LEVEL; + if(sd->spiritball < 0) + sd->spiritball = 0; + + if( sd->spiritball && sd->spiritball >= max ) + { + if(sd->spirit_timer[0] != INVALID_TIMER) + delete_timer(sd->spirit_timer[0],pc_spiritball_timer); + sd->spiritball--; + if( sd->spiritball != 0 ) + memmove(sd->spirit_timer+0, sd->spirit_timer+1, (sd->spiritball)*sizeof(int)); + sd->spirit_timer[sd->spiritball] = INVALID_TIMER; + } + + tid = add_timer(gettick()+interval, pc_spiritball_timer, sd->bl.id, 0); + ARR_FIND(0, sd->spiritball, i, sd->spirit_timer[i] == INVALID_TIMER || DIFF_TICK(get_timer(tid)->tick, get_timer(sd->spirit_timer[i])->tick) < 0); + if( i != sd->spiritball ) + memmove(sd->spirit_timer+i+1, sd->spirit_timer+i, (sd->spiritball-i)*sizeof(int)); + sd->spirit_timer[i] = tid; + sd->spiritball++; + if( (sd->class_&MAPID_THIRDMASK) == MAPID_ROYAL_GUARD ) + clif_millenniumshield(sd,sd->spiritball); + else + clif_spiritball(&sd->bl); + + return 0; +} + +int pc_delspiritball(struct map_session_data *sd,int count,int type) +{ + int i; + + nullpo_ret(sd); + + if(sd->spiritball <= 0) { + sd->spiritball = 0; + return 0; + } + + if(count <= 0) + return 0; + if(count > sd->spiritball) + count = sd->spiritball; + sd->spiritball -= count; + if(count > MAX_SKILL_LEVEL) + count = MAX_SKILL_LEVEL; + + for(i=0;i<count;i++) { + if(sd->spirit_timer[i] != INVALID_TIMER) { + delete_timer(sd->spirit_timer[i],pc_spiritball_timer); + sd->spirit_timer[i] = INVALID_TIMER; + } + } + for(i=count;i<MAX_SKILL_LEVEL;i++) { + sd->spirit_timer[i-count] = sd->spirit_timer[i]; + sd->spirit_timer[i] = INVALID_TIMER; + } + + if(!type) { + if( (sd->class_&MAPID_THIRDMASK) == MAPID_ROYAL_GUARD ) + clif_millenniumshield(sd,sd->spiritball); + else + clif_spiritball(&sd->bl); + } + return 0; +} +static int pc_check_banding( struct block_list *bl, va_list ap ) { + int *c, *b_sd; + struct block_list *src; + struct map_session_data *tsd; + struct status_change *sc; + + nullpo_ret(bl); + nullpo_ret(tsd = (struct map_session_data*)bl); + nullpo_ret(src = va_arg(ap,struct block_list *)); + c = va_arg(ap,int *); + b_sd = va_arg(ap, int *); + + if(pc_isdead(tsd)) + return 0; + + sc = status_get_sc(bl); + + if( bl == src ) + return 0; + + if( sc && sc->data[SC_BANDING] ) + { + b_sd[(*c)++] = tsd->bl.id; + return 1; + } + + return 0; +} +int pc_banding(struct map_session_data *sd, uint16 skill_lv) { + int c; + int b_sd[MAX_PARTY]; // In case of a full Royal Guard party. + int i, j, hp, extra_hp = 0, tmp_qty = 0, tmp_hp; + struct map_session_data *bsd; + struct status_change *sc; + int range = skill_get_splash(LG_BANDING,skill_lv); + + nullpo_ret(sd); + + c = 0; + memset(b_sd, 0, sizeof(b_sd)); + i = party_foreachsamemap(pc_check_banding,sd,range,&sd->bl,&c,&b_sd); + + if( c < 1 ) //just recalc status no need to recalc hp + { // No more Royal Guards in Banding found. + if( (sc = status_get_sc(&sd->bl)) != NULL && sc->data[SC_BANDING] ) + { + sc->data[SC_BANDING]->val2 = 0; // Reset the counter + status_calc_bl(&sd->bl, status_sc2scb_flag(SC_BANDING)); + } + return 0; + } + + //Add yourself + hp = status_get_hp(&sd->bl); + i++; + + // Get total HP of all Royal Guards in party. + for( j = 0; j < i; j++ ) + { + bsd = map_id2sd(b_sd[j]); + if( bsd != NULL ) + hp += status_get_hp(&bsd->bl); + } + + // Set average HP. + hp = hp / i; + + // If a Royal Guard have full HP, give more HP to others that haven't full HP. + for( j = 0; j < i; j++ ) + { + bsd = map_id2sd(b_sd[j]); + if( bsd != NULL && (tmp_hp = hp - status_get_max_hp(&bsd->bl)) > 0 ) + { + extra_hp += tmp_hp; + tmp_qty++; + } + } + + if( extra_hp > 0 && tmp_qty > 0 ) + hp += extra_hp / tmp_qty; + + for( j = 0; j < i; j++ ) + { + bsd = map_id2sd(b_sd[j]); + if( bsd != NULL ) + { + status_set_hp(&bsd->bl,hp,0); // Set hp + if( (sc = status_get_sc(&bsd->bl)) != NULL && sc->data[SC_BANDING] ) + { + sc->data[SC_BANDING]->val2 = c; // Set the counter. It doesn't count your self. + status_calc_bl(&bsd->bl, status_sc2scb_flag(SC_BANDING)); // Set atk and def. + } + } + } + + return c; +} + +// Increases a player's fame points and displays a notice to him +void pc_addfame(struct map_session_data *sd,int count) +{ + nullpo_retv(sd); + sd->status.fame += count; + if(sd->status.fame > MAX_FAME) + sd->status.fame = MAX_FAME; + switch(sd->class_&MAPID_UPPERMASK){ + case MAPID_BLACKSMITH: // Blacksmith + clif_fame_blacksmith(sd,count); + break; + case MAPID_ALCHEMIST: // Alchemist + clif_fame_alchemist(sd,count); + break; + case MAPID_TAEKWON: // Taekwon + clif_fame_taekwon(sd,count); + break; + } + chrif_updatefamelist(sd); +} + +// Check whether a player ID is in the fame rankers' list of its job, returns his/her position if so, 0 else +unsigned char pc_famerank(int char_id, int job) +{ + int i; + + switch(job){ + case MAPID_BLACKSMITH: // Blacksmith + for(i = 0; i < MAX_FAME_LIST; i++){ + if(smith_fame_list[i].id == char_id) + return i + 1; + } + break; + case MAPID_ALCHEMIST: // Alchemist + for(i = 0; i < MAX_FAME_LIST; i++){ + if(chemist_fame_list[i].id == char_id) + return i + 1; + } + break; + case MAPID_TAEKWON: // Taekwon + for(i = 0; i < MAX_FAME_LIST; i++){ + if(taekwon_fame_list[i].id == char_id) + return i + 1; + } + break; + } + + return 0; +} + +int pc_setrestartvalue(struct map_session_data *sd,int type) { + struct status_data *status, *b_status; + nullpo_ret(sd); + + b_status = &sd->base_status; + status = &sd->battle_status; + + if (type&1) { //Normal resurrection + status->hp = 1; //Otherwise status_heal may fail if dead. + status_heal(&sd->bl, b_status->hp, 0, 1); + if( status->sp < b_status->sp ) + status_set_sp(&sd->bl, b_status->sp, 1); + } else { //Just for saving on the char-server (with values as if respawned) + sd->status.hp = b_status->hp; + sd->status.sp = (status->sp < b_status->sp)?b_status->sp:status->sp; + } + return 0; +} + +/*========================================== + Rental System + *------------------------------------------*/ +static int pc_inventory_rental_end(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = map_id2sd(id); + if( sd == NULL ) + return 0; + if( tid != sd->rental_timer ) + { + ShowError("pc_inventory_rental_end: invalid timer id.\n"); + return 0; + } + + pc_inventory_rentals(sd); + return 1; +} + +int pc_inventory_rental_clear(struct map_session_data *sd) +{ + if( sd->rental_timer != INVALID_TIMER ) + { + delete_timer(sd->rental_timer, pc_inventory_rental_end); + sd->rental_timer = INVALID_TIMER; + } + + return 1; +} + +void pc_inventory_rentals(struct map_session_data *sd) +{ + int i, c = 0; + unsigned int expire_tick, next_tick = UINT_MAX; + + for( i = 0; i < MAX_INVENTORY; i++ ) + { // Check for Rentals on Inventory + if( sd->status.inventory[i].nameid == 0 ) + continue; // Nothing here + if( sd->status.inventory[i].expire_time == 0 ) + continue; + + if( sd->status.inventory[i].expire_time <= time(NULL) ) { + if( sd->status.inventory[i].nameid == ITEMID_REINS_OF_MOUNT + && sd->sc.option&OPTION_MOUNTING ) { + pc_setoption(sd, sd->sc.option&~OPTION_MOUNTING); + } + clif_rental_expired(sd->fd, i, sd->status.inventory[i].nameid); + pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_OTHER); + } else { + expire_tick = (unsigned int)(sd->status.inventory[i].expire_time - time(NULL)) * 1000; + clif_rental_time(sd->fd, sd->status.inventory[i].nameid, (int)(expire_tick / 1000)); + next_tick = min(expire_tick, next_tick); + c++; + } + } + + if( c > 0 ) // min(next_tick,3600000) 1 hour each timer to keep announcing to the owner, and to avoid a but with rental time > 15 days + sd->rental_timer = add_timer(gettick() + min(next_tick,3600000), pc_inventory_rental_end, sd->bl.id, 0); + else + sd->rental_timer = INVALID_TIMER; +} + +void pc_inventory_rental_add(struct map_session_data *sd, int seconds) +{ + const struct TimerData * td; + int tick = seconds * 1000; + + if( sd == NULL ) + return; + + if( sd->rental_timer != INVALID_TIMER ) + { + td = get_timer(sd->rental_timer); + if( DIFF_TICK(td->tick, gettick()) > tick ) + { // Update Timer as this one ends first than the current one + pc_inventory_rental_clear(sd); + sd->rental_timer = add_timer(gettick() + tick, pc_inventory_rental_end, sd->bl.id, 0); + } + } + else + sd->rental_timer = add_timer(gettick() + min(tick,3600000), pc_inventory_rental_end, sd->bl.id, 0); +} + +/** + * Determines if player can give / drop / trade / vend items + */ +bool pc_can_give_items(struct map_session_data *sd) +{ + return pc_has_permission(sd, PC_PERM_TRADE); +} + +/*========================================== + * prepares character for saving. + *------------------------------------------*/ +int pc_makesavestatus(struct map_session_data *sd) +{ + nullpo_ret(sd); + + if(!battle_config.save_clothcolor) + sd->status.clothes_color=0; + + //Only copy the Cart/Peco/Falcon options, the rest are handled via + //status change load/saving. [Skotlex] +#ifdef NEW_CARTS + sd->status.option = sd->sc.option&(OPTION_FALCON|OPTION_RIDING|OPTION_DRAGON|OPTION_WUG|OPTION_WUGRIDER|OPTION_MADOGEAR|OPTION_MOUNTING); +#else + sd->status.option = sd->sc.option&(OPTION_CART|OPTION_FALCON|OPTION_RIDING|OPTION_DRAGON|OPTION_WUG|OPTION_WUGRIDER|OPTION_MADOGEAR|OPTION_MOUNTING); +#endif + if (sd->sc.data[SC_JAILED]) + { //When Jailed, do not move last point. + if(pc_isdead(sd)){ + pc_setrestartvalue(sd,0); + } else { + sd->status.hp = sd->battle_status.hp; + sd->status.sp = sd->battle_status.sp; + } + sd->status.last_point.map = sd->mapindex; + sd->status.last_point.x = sd->bl.x; + sd->status.last_point.y = sd->bl.y; + return 0; + } + + if(pc_isdead(sd)){ + pc_setrestartvalue(sd,0); + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + } else { + sd->status.hp = sd->battle_status.hp; + sd->status.sp = sd->battle_status.sp; + sd->status.last_point.map = sd->mapindex; + sd->status.last_point.x = sd->bl.x; + sd->status.last_point.y = sd->bl.y; + } + + if(map[sd->bl.m].flag.nosave){ + struct map_data *m=&map[sd->bl.m]; + if(m->save.map) + memcpy(&sd->status.last_point,&m->save,sizeof(sd->status.last_point)); + else + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + } + + return 0; +} + +/*========================================== + * Off init ? Connection? + *------------------------------------------*/ +int pc_setnewpc(struct map_session_data *sd, int account_id, int char_id, int login_id1, unsigned int client_tick, int sex, int fd) +{ + nullpo_ret(sd); + + sd->bl.id = account_id; + sd->status.account_id = account_id; + sd->status.char_id = char_id; + sd->status.sex = sex; + sd->login_id1 = login_id1; + sd->login_id2 = 0; // at this point, we can not know the value :( + sd->client_tick = client_tick; + sd->state.active = 0; //to be set to 1 after player is fully authed and loaded. + sd->bl.type = BL_PC; + sd->canlog_tick = gettick(); + //Required to prevent homunculus copuing a base speed of 0. + sd->battle_status.speed = sd->base_status.speed = DEFAULT_WALK_SPEED; + return 0; +} + +int pc_equippoint(struct map_session_data *sd,int n) +{ + int ep = 0; + + nullpo_ret(sd); + + if(!sd->inventory_data[n]) + return 0; + + if (!itemdb_isequip2(sd->inventory_data[n])) + return 0; //Not equippable by players. + + ep = sd->inventory_data[n]->equip; + if(sd->inventory_data[n]->look == W_DAGGER || + sd->inventory_data[n]->look == W_1HSWORD || + sd->inventory_data[n]->look == W_1HAXE) { + if(ep == EQP_HAND_R && (pc_checkskill(sd,AS_LEFT) > 0 || (sd->class_&MAPID_UPPERMASK) == MAPID_ASSASSIN || + (sd->class_&MAPID_UPPERMASK) == MAPID_KAGEROUOBORO))//Kagerou and Oboro can dual wield daggers. [Rytech] + return EQP_ARMS; + } + return ep; +} + +int pc_setinventorydata(struct map_session_data *sd) +{ + int i,id; + + nullpo_ret(sd); + + for(i=0;i<MAX_INVENTORY;i++) { + id = sd->status.inventory[i].nameid; + sd->inventory_data[i] = id?itemdb_search(id):NULL; + } + return 0; +} + +int pc_calcweapontype(struct map_session_data *sd) +{ + nullpo_ret(sd); + + // single-hand + if(sd->weapontype2 == W_FIST) { + sd->status.weapon = sd->weapontype1; + return 1; + } + if(sd->weapontype1 == W_FIST) { + sd->status.weapon = sd->weapontype2; + return 1; + } + // dual-wield + sd->status.weapon = 0; + switch (sd->weapontype1){ + case W_DAGGER: + switch (sd->weapontype2) { + case W_DAGGER: sd->status.weapon = W_DOUBLE_DD; break; + case W_1HSWORD: sd->status.weapon = W_DOUBLE_DS; break; + case W_1HAXE: sd->status.weapon = W_DOUBLE_DA; break; + } + break; + case W_1HSWORD: + switch (sd->weapontype2) { + case W_DAGGER: sd->status.weapon = W_DOUBLE_DS; break; + case W_1HSWORD: sd->status.weapon = W_DOUBLE_SS; break; + case W_1HAXE: sd->status.weapon = W_DOUBLE_SA; break; + } + break; + case W_1HAXE: + switch (sd->weapontype2) { + case W_DAGGER: sd->status.weapon = W_DOUBLE_DA; break; + case W_1HSWORD: sd->status.weapon = W_DOUBLE_SA; break; + case W_1HAXE: sd->status.weapon = W_DOUBLE_AA; break; + } + } + // unknown, default to right hand type + if (!sd->status.weapon) + sd->status.weapon = sd->weapontype1; + + return 2; +} + +int pc_setequipindex(struct map_session_data *sd) +{ + int i,j; + + nullpo_ret(sd); + + for(i=0;i<EQI_MAX;i++) + sd->equip_index[i] = -1; + + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid <= 0) + continue; + if(sd->status.inventory[i].equip) { + for(j=0;j<EQI_MAX;j++) + if(sd->status.inventory[i].equip & equip_pos[j]) + sd->equip_index[j] = i; + + if(sd->status.inventory[i].equip & EQP_HAND_R) + { + if(sd->inventory_data[i]) + sd->weapontype1 = sd->inventory_data[i]->look; + else + sd->weapontype1 = 0; + } + + if( sd->status.inventory[i].equip & EQP_HAND_L ) + { + if( sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON ) + sd->weapontype2 = sd->inventory_data[i]->look; + else + sd->weapontype2 = 0; + } + } + } + pc_calcweapontype(sd); + + return 0; +} + +static int pc_isAllowedCardOn(struct map_session_data *sd,int s,int eqindex,int flag) +{ + int i; + struct item *item = &sd->status.inventory[eqindex]; + struct item_data *data; + + //Crafted/made/hatched items. + if (itemdb_isspecial(item->card[0])) + return 1; + + /* scan for enchant armor gems */ + if( item->card[MAX_SLOTS - 1] && s < MAX_SLOTS - 1 ) + s = MAX_SLOTS - 1; + + ARR_FIND( 0, s, i, item->card[i] && (data = itemdb_exists(item->card[i])) != NULL && data->flag.no_equip&flag ); + return( i < s ) ? 0 : 1; +} + +bool pc_isequipped(struct map_session_data *sd, int nameid) +{ + int i, j, index; + + for( i = 0; i < EQI_MAX; i++ ) + { + index = sd->equip_index[i]; + if( index < 0 ) continue; + + if( i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index ) continue; + if( i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index ) continue; + if( i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index) ) continue; + + if( !sd->inventory_data[index] ) continue; + + if( sd->inventory_data[index]->nameid == nameid ) + return true; + + for( j = 0; j < sd->inventory_data[index]->slot; j++ ) + if( sd->status.inventory[index].card[j] == nameid ) + return true; + } + + return false; +} + +bool pc_can_Adopt(struct map_session_data *p1_sd, struct map_session_data *p2_sd, struct map_session_data *b_sd ) +{ + if( !p1_sd || !p2_sd || !b_sd ) + return false; + + if( b_sd->status.father || b_sd->status.mother || b_sd->adopt_invite ) + return false; // already adopted baby / in adopt request + + if( !p1_sd->status.partner_id || !p1_sd->status.party_id || p1_sd->status.party_id != b_sd->status.party_id ) + return false; // You need to be married and in party with baby to adopt + + if( p1_sd->status.partner_id != p2_sd->status.char_id || p2_sd->status.partner_id != p1_sd->status.char_id ) + return false; // Not married, wrong married + + if( p2_sd->status.party_id != p1_sd->status.party_id ) + return false; // Both parents need to be in the same party + + // Parents need to have their ring equipped + if( !pc_isequipped(p1_sd, WEDDING_RING_M) && !pc_isequipped(p1_sd, WEDDING_RING_F) ) + return false; + + if( !pc_isequipped(p2_sd, WEDDING_RING_M) && !pc_isequipped(p2_sd, WEDDING_RING_F) ) + return false; + + // Already adopted a baby + if( p1_sd->status.child || p2_sd->status.child ) { + clif_Adopt_reply(p1_sd, 0); + return false; + } + + // Parents need at least lvl 70 to adopt + if( p1_sd->status.base_level < 70 || p2_sd->status.base_level < 70 ) { + clif_Adopt_reply(p1_sd, 1); + return false; + } + + if( b_sd->status.partner_id ) { + clif_Adopt_reply(p1_sd, 2); + return false; + } + + if( !( ( b_sd->status.class_ >= JOB_NOVICE && b_sd->status.class_ <= JOB_THIEF ) || b_sd->status.class_ == JOB_SUPER_NOVICE ) ) + return false; + + return true; +} + +/*========================================== + * Adoption Process + *------------------------------------------*/ +bool pc_adoption(struct map_session_data *p1_sd, struct map_session_data *p2_sd, struct map_session_data *b_sd) +{ + int job, joblevel; + unsigned int jobexp; + + if( !pc_can_Adopt(p1_sd, p2_sd, b_sd) ) + return false; + + // Preserve current job levels and progress + joblevel = b_sd->status.job_level; + jobexp = b_sd->status.job_exp; + + job = pc_mapid2jobid(b_sd->class_|JOBL_BABY, b_sd->status.sex); + if( job != -1 && !pc_jobchange(b_sd, job, 0) ) + { // Success, proceed to configure parents and baby skills + p1_sd->status.child = b_sd->status.char_id; + p2_sd->status.child = b_sd->status.char_id; + b_sd->status.father = p1_sd->status.char_id; + b_sd->status.mother = p2_sd->status.char_id; + + // Restore progress + b_sd->status.job_level = joblevel; + clif_updatestatus(b_sd, SP_JOBLEVEL); + b_sd->status.job_exp = jobexp; + clif_updatestatus(b_sd, SP_JOBEXP); + + // Baby Skills + pc_skill(b_sd, WE_BABY, 1, 0); + pc_skill(b_sd, WE_CALLPARENT, 1, 0); + + // Parents Skills + pc_skill(p1_sd, WE_CALLBABY, 1, 0); + pc_skill(p2_sd, WE_CALLBABY, 1, 0); + + return true; + } + + return false; // Job Change Fail +} + +/*================================================= + * Checks if the player can equip the item at index n in inventory. + * Returns 0 (no) or 1 (yes). + *------------------------------------------------*/ +int pc_isequip(struct map_session_data *sd,int n) +{ + struct item_data *item; + + nullpo_ret(sd); + + item = sd->inventory_data[n]; + + if(pc_has_permission(sd, PC_PERM_USE_ALL_EQUIPMENT)) + return 1; + + if(item == NULL) + return 0; + if(item->elv && sd->status.base_level < (unsigned int)item->elv) + return 0; +#ifdef RENEWAL + if(item->elvmax && sd->status.base_level > (unsigned int)item->elvmax) + return 0; +#endif + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + if(!map_flag_vs(sd->bl.m) && ((item->flag.no_equip&1) || !pc_isAllowedCardOn(sd,item->slot,n,1))) + return 0; + if(map[sd->bl.m].flag.pvp && ((item->flag.no_equip&2) || !pc_isAllowedCardOn(sd,item->slot,n,2))) + return 0; + if(map_flag_gvg(sd->bl.m) && ((item->flag.no_equip&4) || !pc_isAllowedCardOn(sd,item->slot,n,4))) + return 0; + if(map[sd->bl.m].flag.battleground && ((item->flag.no_equip&8) || !pc_isAllowedCardOn(sd,item->slot,n,8))) + return 0; + if(map[sd->bl.m].flag.restricted) + { + int flag =8*map[sd->bl.m].zone; + if (item->flag.no_equip&flag || !pc_isAllowedCardOn(sd,item->slot,n,flag)) + return 0; + } + + if (sd->sc.count) { + + if(item->equip & EQP_ARMS && item->type == IT_WEAPON && sd->sc.data[SC_STRIPWEAPON]) // Also works with left-hand weapons [DracoRPG] + return 0; + if(item->equip & EQP_SHIELD && item->type == IT_ARMOR && sd->sc.data[SC_STRIPSHIELD]) + return 0; + if(item->equip & EQP_ARMOR && sd->sc.data[SC_STRIPARMOR]) + return 0; + if(item->equip & EQP_HEAD_TOP && sd->sc.data[SC_STRIPHELM]) + return 0; + if(item->equip & EQP_ACC && sd->sc.data[SC__STRIPACCESSORY]) + return 0; + if(item->equip && sd->sc.data[SC_KYOUGAKU]) + return 0; + + if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SUPERNOVICE) { + //Spirit of Super Novice equip bonuses. [Skotlex] + if (sd->status.base_level > 90 && item->equip & EQP_HELM) + return 1; //Can equip all helms + + if (sd->status.base_level > 96 && item->equip & EQP_ARMS && item->type == IT_WEAPON) + switch(item->look) { //In weapons, the look determines type of weapon. + case W_DAGGER: //Level 4 Knives are equippable.. this means all knives, I'd guess? + case W_1HSWORD: //All 1H swords + case W_1HAXE: //All 1H Axes + case W_MACE: //All 1H Maces + case W_STAFF: //All 1H Staves + return 1; + } + } + } + //Not equipable by class. [Skotlex] + if (!(1<<(sd->class_&MAPID_BASEMASK)&item->class_base[(sd->class_&JOBL_2_1)?1:((sd->class_&JOBL_2_2)?2:0)])) + return 0; + //Not usable by upper class. [Inkfish] + while( 1 ) { + if( item->class_upper&1 && !(sd->class_&(JOBL_UPPER|JOBL_THIRD|JOBL_BABY)) ) break; + if( item->class_upper&2 && sd->class_&(JOBL_UPPER|JOBL_THIRD) ) break; + if( item->class_upper&4 && sd->class_&JOBL_BABY ) break; + if( item->class_upper&8 && sd->class_&JOBL_THIRD ) break; + return 0; + } + + return 1; +} + +/*========================================== + * No problem with the session id + * set the status that has been sent from char server + *------------------------------------------*/ +bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_time, int group_id, struct mmo_charstatus *st, bool changing_mapservers) +{ + int i; + unsigned long tick = gettick(); + uint32 ip = session[sd->fd]->client_addr; + + sd->login_id2 = login_id2; + sd->group_id = group_id; + + /* load user permissions */ + pc_group_pc_load(sd); + + memcpy(&sd->status, st, sizeof(*st)); + + if (st->sex != sd->status.sex) { + clif_authfail_fd(sd->fd, 0); + return false; + } + + //Set the map-server used job id. [Skotlex] + i = pc_jobid2mapid(sd->status.class_); + if (i == -1) { //Invalid class? + ShowError("pc_authok: Invalid class %d for player %s (%d:%d). Class was changed to novice.\n", sd->status.class_, sd->status.name, sd->status.account_id, sd->status.char_id); + sd->status.class_ = JOB_NOVICE; + sd->class_ = MAPID_NOVICE; + } else + sd->class_ = i; + + // Checks and fixes to character status data, that are required + // in case of configuration change or stuff, which cannot be + // checked on char-server. + if( sd->status.hair < MIN_HAIR_STYLE || sd->status.hair > MAX_HAIR_STYLE ) + { + sd->status.hair = MIN_HAIR_STYLE; + } + if( sd->status.hair_color < MIN_HAIR_COLOR || sd->status.hair_color > MAX_HAIR_COLOR ) + { + sd->status.hair_color = MIN_HAIR_COLOR; + } + if( sd->status.clothes_color < MIN_CLOTH_COLOR || sd->status.clothes_color > MAX_CLOTH_COLOR ) + { + sd->status.clothes_color = MIN_CLOTH_COLOR; + } + + //Initializations to null/0 unneeded since map_session_data was filled with 0 upon allocation. + if(!sd->status.hp) pc_setdead(sd); + sd->state.connect_new = 1; + + sd->followtimer = INVALID_TIMER; // [MouseJstr] + sd->invincible_timer = INVALID_TIMER; + sd->npc_timer_id = INVALID_TIMER; + sd->pvp_timer = INVALID_TIMER; + /** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + /** + * Initialize to defaults/expected + **/ + sd->npc_idle_timer = INVALID_TIMER; + sd->npc_idle_tick = tick; +#endif + + sd->canuseitem_tick = tick; + sd->canusecashfood_tick = tick; + sd->canequip_tick = tick; + sd->cantalk_tick = tick; + sd->canskill_tick = tick; + sd->cansendmail_tick = tick; + + for(i = 0; i < MAX_SKILL_LEVEL; i++) + sd->spirit_timer[i] = INVALID_TIMER; + for(i = 0; i < ARRAYLENGTH(sd->autobonus); i++) + sd->autobonus[i].active = INVALID_TIMER; + for(i = 0; i < ARRAYLENGTH(sd->autobonus2); i++) + sd->autobonus2[i].active = INVALID_TIMER; + for(i = 0; i < ARRAYLENGTH(sd->autobonus3); i++) + sd->autobonus3[i].active = INVALID_TIMER; + + if (battle_config.item_auto_get) + sd->state.autoloot = 10000; + + if (battle_config.disp_experience) + sd->state.showexp = 1; + if (battle_config.disp_zeny) + sd->state.showzeny = 1; + + if (!(battle_config.display_skill_fail&2)) + sd->state.showdelay = 1; + + pc_setinventorydata(sd); + pc_setequipindex(sd); + + status_change_init(&sd->bl); + + if (pc_can_use_command(sd, "hide", COMMAND_ATCOMMAND)) + sd->status.option &= (OPTION_MASK | OPTION_INVISIBLE); + else + sd->status.option &= OPTION_MASK; + + sd->sc.option = sd->status.option; //This is the actual option used in battle. + //Set here because we need the inventory data for weapon sprite parsing. + status_set_viewdata(&sd->bl, sd->status.class_); + unit_dataset(&sd->bl); + + sd->guild_x = -1; + sd->guild_y = -1; + + // Event Timers + for( i = 0; i < MAX_EVENTTIMER; i++ ) + sd->eventtimer[i] = INVALID_TIMER; + // Rental Timer + sd->rental_timer = INVALID_TIMER; + + for( i = 0; i < 3; i++ ) + sd->hate_mob[i] = -1; + + //warp player + if ((i=pc_setpos(sd,sd->status.last_point.map, sd->status.last_point.x, sd->status.last_point.y, CLR_OUTSIGHT)) != 0) { + ShowError ("Last_point_map %s - id %d not found (error code %d)\n", mapindex_id2name(sd->status.last_point.map), sd->status.last_point.map, i); + + // try warping to a default map instead (church graveyard) + if (pc_setpos(sd, mapindex_name2id(MAP_PRONTERA), 273, 354, CLR_OUTSIGHT) != 0) { + // if we fail again + clif_authfail_fd(sd->fd, 0); + return false; + } + } + + clif_authok(sd); + + //Prevent S. Novices from getting the no-death bonus just yet. [Skotlex] + sd->die_counter=-1; + + //display login notice + ShowInfo("'"CL_WHITE"%s"CL_RESET"' logged in." + " (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"'," + " Packet Ver: '"CL_WHITE"%d"CL_RESET"', IP: '"CL_WHITE"%d.%d.%d.%d"CL_RESET"'," + " Group '"CL_WHITE"%d"CL_RESET"').\n", + sd->status.name, sd->status.account_id, sd->status.char_id, + sd->packet_ver, CONVIP(ip), sd->group_id); + // Send friends list + clif_friendslist_send(sd); + + if( !changing_mapservers ) { + + if (battle_config.display_version == 1){ + char buf[256]; + sprintf(buf, "SVN version: %s", get_svn_revision()); + clif_displaymessage(sd->fd, buf); + } + + // Message of the Day [Valaris] + for(i=0; motd_text[i][0] && i < MOTD_LINE_SIZE; i++) { + if (battle_config.motd_type) + clif_disp_onlyself(sd,motd_text[i],strlen(motd_text[i])); + else + clif_displaymessage(sd->fd, motd_text[i]); + } + + // message of the limited time of the account + if (expiration_time != 0) { // don't display if it's unlimited or unknow value + char tmpstr[1024]; + strftime(tmpstr, sizeof(tmpstr) - 1, msg_txt(501), localtime(&expiration_time)); // "Your account time limit is: %d-%m-%Y %H:%M:%S." + clif_wis_message(sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + + /** + * Fixes login-without-aura glitch (the screen won't blink at this point, don't worry :P) + **/ + clif_changemap(sd,sd->mapindex,sd->bl.x,sd->bl.y); + } + + /** + * Check if player have any cool downs on + **/ + skill_cooldown_load(sd); + + /** + * Check if player have any item cooldowns on + **/ + pc_itemcd_do(sd,true); + + // Request all registries (auth is considered completed whence they arrive) + intif_request_registry(sd,7); + return true; +} + +/*========================================== + * Closes a connection because it failed to be authenticated from the char server. + *------------------------------------------*/ +void pc_authfail(struct map_session_data *sd) +{ + clif_authfail_fd(sd->fd, 0); + return; +} + +//Attempts to set a mob. +int pc_set_hate_mob(struct map_session_data *sd, int pos, struct block_list *bl) +{ + int class_; + if (!sd || !bl || pos < 0 || pos > 2) + return 0; + if (sd->hate_mob[pos] != -1) + { //Can't change hate targets. + clif_hate_info(sd, pos, sd->hate_mob[pos], 0); //Display current + return 0; + } + + class_ = status_get_class(bl); + if (!pcdb_checkid(class_)) { + unsigned int max_hp = status_get_max_hp(bl); + if ((pos == 1 && max_hp < 6000) || (pos == 2 && max_hp < 20000)) + return 0; + if (pos != status_get_size(bl)) + return 0; //Wrong size + } + sd->hate_mob[pos] = class_; + pc_setglobalreg(sd,sg_info[pos].hate_var,class_+1); + clif_hate_info(sd, pos, class_, 1); + return 1; +} + +/*========================================== + * Invoked once after the char/account/account2 registry variables are received. [Skotlex] + *------------------------------------------*/ +int pc_reg_received(struct map_session_data *sd) +{ + int i,j; + + sd->change_level_2nd = pc_readglobalreg(sd,"jobchange_level"); + sd->change_level_3rd = pc_readglobalreg(sd,"jobchange_level_3rd"); + sd->die_counter = pc_readglobalreg(sd,"PC_DIE_COUNTER"); + + // Cash shop + sd->cashPoints = pc_readaccountreg(sd,"#CASHPOINTS"); + sd->kafraPoints = pc_readaccountreg(sd,"#KAFRAPOINTS"); + + // Cooking Exp + sd->cook_mastery = pc_readglobalreg(sd,"COOK_MASTERY"); + + if( (sd->class_&MAPID_BASEMASK) == MAPID_TAEKWON ) + { // Better check for class rather than skill to prevent "skill resets" from unsetting this + sd->mission_mobid = pc_readglobalreg(sd,"TK_MISSION_ID"); + sd->mission_count = pc_readglobalreg(sd,"TK_MISSION_COUNT"); + } + + //SG map and mob read [Komurka] + for(i=0;i<MAX_PC_FEELHATE;i++) //for now - someone need to make reading from txt/sql + { + if ((j = pc_readglobalreg(sd,sg_info[i].feel_var))!=0) { + sd->feel_map[i].index = j; + sd->feel_map[i].m = map_mapindex2mapid(j); + } else { + sd->feel_map[i].index = 0; + sd->feel_map[i].m = -1; + } + sd->hate_mob[i] = pc_readglobalreg(sd,sg_info[i].hate_var)-1; + } + + if ((i = pc_checkskill(sd,RG_PLAGIARISM)) > 0) { + sd->cloneskill_id = pc_readglobalreg(sd,"CLONE_SKILL"); + if (sd->cloneskill_id > 0) { + sd->status.skill[sd->cloneskill_id].id = sd->cloneskill_id; + sd->status.skill[sd->cloneskill_id].lv = pc_readglobalreg(sd,"CLONE_SKILL_LV"); + if (sd->status.skill[sd->cloneskill_id].lv > i) + sd->status.skill[sd->cloneskill_id].lv = i; + sd->status.skill[sd->cloneskill_id].flag = SKILL_FLAG_PLAGIARIZED; + } + } + if ((i = pc_checkskill(sd,SC_REPRODUCE)) > 0) { + sd->reproduceskill_id = pc_readglobalreg(sd,"REPRODUCE_SKILL"); + if( sd->reproduceskill_id > 0) { + sd->status.skill[sd->reproduceskill_id].id = sd->reproduceskill_id; + sd->status.skill[sd->reproduceskill_id].lv = pc_readglobalreg(sd,"REPRODUCE_SKILL_LV"); + if( i < sd->status.skill[sd->reproduceskill_id].lv) + sd->status.skill[sd->reproduceskill_id].lv = i; + sd->status.skill[sd->reproduceskill_id].flag = SKILL_FLAG_PLAGIARIZED; + } + } + //Weird... maybe registries were reloaded? + if (sd->state.active) + return 0; + sd->state.active = 1; + + if (sd->status.party_id) + party_member_joined(sd); + if (sd->status.guild_id) + guild_member_joined(sd); + + // pet + if (sd->status.pet_id > 0) + intif_request_petdata(sd->status.account_id, sd->status.char_id, sd->status.pet_id); + + // Homunculus [albator] + if( sd->status.hom_id > 0 ) + intif_homunculus_requestload(sd->status.account_id, sd->status.hom_id); + if( sd->status.mer_id > 0 ) + intif_mercenary_request(sd->status.mer_id, sd->status.char_id); + if( sd->status.ele_id > 0 ) + intif_elemental_request(sd->status.ele_id, sd->status.char_id); + + map_addiddb(&sd->bl); + map_delnickdb(sd->status.char_id, sd->status.name); + if (!chrif_auth_finished(sd)) + ShowError("pc_reg_received: Failed to properly remove player %d:%d from logging db!\n", sd->status.account_id, sd->status.char_id); + + pc_load_combo(sd); + + status_calc_pc(sd,1); + chrif_scdata_request(sd->status.account_id, sd->status.char_id); + + intif_Mail_requestinbox(sd->status.char_id, 0); // MAIL SYSTEM - Request Mail Inbox + intif_request_questlog(sd); + + if (sd->state.connect_new == 0 && sd->fd) + { //Character already loaded map! Gotta trigger LoadEndAck manually. + sd->state.connect_new = 1; + clif_parse_LoadEndAck(sd->fd, sd); + } + + pc_inventory_rentals(sd); + + return 1; +} + +static int pc_calc_skillpoint(struct map_session_data* sd) +{ + int i,skill,inf2,skill_point=0; + + nullpo_ret(sd); + + for(i=1;i<MAX_SKILL;i++){ + if( (skill = pc_checkskill(sd,i)) > 0) { + inf2 = skill_get_inf2(i); + if((!(inf2&INF2_QUEST_SKILL) || battle_config.quest_skill_learn) && + !(inf2&(INF2_WEDDING_SKILL|INF2_SPIRIT_SKILL)) //Do not count wedding/link skills. [Skotlex] + ) { + if(sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) + skill_point += skill; + else + if(sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0) + skill_point += (sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0); + } + } + } + + return skill_point; +} + + +/*========================================== + * Calculation of skill level. + *------------------------------------------*/ +int pc_calc_skilltree(struct map_session_data *sd) +{ + int i,id=0,flag; + int c=0; + + nullpo_ret(sd); + i = pc_calc_skilltree_normalize_job(sd); + c = pc_mapid2jobid(i, sd->status.sex); + if( c == -1 ) + { //Unable to normalize job?? + ShowError("pc_calc_skilltree: Unable to normalize job %d for character %s (%d:%d)\n", i, sd->status.name, sd->status.account_id, sd->status.char_id); + return 1; + } + c = pc_class2idx(c); + + for( i = 0; i < MAX_SKILL; i++ ) + { + if( sd->status.skill[i].flag != SKILL_FLAG_PLAGIARIZED ) //Don't touch plagiarized skills + sd->status.skill[i].id = 0; //First clear skills. + } + + for( i = 0; i < MAX_SKILL; i++ ) + { + if( sd->status.skill[i].flag != SKILL_FLAG_PERMANENT && sd->status.skill[i].flag != SKILL_FLAG_PLAGIARIZED ) + { // Restore original level of skills after deleting earned skills. + sd->status.skill[i].lv = (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) ? 0 : sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0; + sd->status.skill[i].flag = SKILL_FLAG_PERMANENT; + } + + if( sd->sc.count && sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_BARDDANCER && i >= DC_HUMMING && i<= DC_SERVICEFORYOU ) + { //Enable Bard/Dancer spirit linked skills. + if( sd->status.sex ) + { //Link dancer skills to bard. + if( sd->status.skill[i-8].lv < 10 ) + continue; + sd->status.skill[i].id = i; + sd->status.skill[i].lv = sd->status.skill[i-8].lv; // Set the level to the same as the linking skill + sd->status.skill[i].flag = SKILL_FLAG_TEMPORARY; // Tag it as a non-savable, non-uppable, bonus skill + } + else + { //Link bard skills to dancer. + if( sd->status.skill[i].lv < 10 ) + continue; + sd->status.skill[i-8].id = i - 8; + sd->status.skill[i-8].lv = sd->status.skill[i].lv; // Set the level to the same as the linking skill + sd->status.skill[i-8].flag = SKILL_FLAG_TEMPORARY; // Tag it as a non-savable, non-uppable, bonus skill + } + } + } + + if( pc_has_permission(sd, PC_PERM_ALL_SKILL) ) { + for( i = 0; i < MAX_SKILL; i++ ) { + switch(i) { + /** + * Dummy skills must be added here otherwise they'll be displayed in the, + * skill tree and since they have no icons they'll give resource errors + **/ + case SM_SELFPROVOKE: + case AB_DUPLELIGHT_MELEE: + case AB_DUPLELIGHT_MAGIC: + case WL_CHAINLIGHTNING_ATK: + case WL_TETRAVORTEX_FIRE: + case WL_TETRAVORTEX_WATER: + case WL_TETRAVORTEX_WIND: + case WL_TETRAVORTEX_GROUND: + case WL_SUMMON_ATK_FIRE: + case WL_SUMMON_ATK_WIND: + case WL_SUMMON_ATK_WATER: + case WL_SUMMON_ATK_GROUND: + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + case WM_SEVERE_RAINSTORM_MELEE: + continue; + default: + break; + } + if( skill_get_inf2(i)&(INF2_NPC_SKILL|INF2_GUILD_SKILL) ) + continue; //Only skills you can't have are npc/guild ones + if( skill_get_max(i) > 0 ) + sd->status.skill[i].id = i; + } + return 0; + } + + do { + flag = 0; + for( i = 0; i < MAX_SKILL_TREE && (id = skill_tree[c][i].id) > 0; i++ ) + { + int j, f, k, inf2; + + if( sd->status.skill[id].id ) + continue; //Skill already known. + + f = 1; + if(!battle_config.skillfree) { + for(j = 0; j < MAX_PC_SKILL_REQUIRE; j++) { + if((k=skill_tree[c][i].need[j].id)) + { + if (sd->status.skill[k].id == 0 || sd->status.skill[k].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[k].flag == SKILL_FLAG_PLAGIARIZED) + k = 0; //Not learned. + else + if (sd->status.skill[k].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level + k = sd->status.skill[skill_tree[c][i].need[j].id].flag - SKILL_FLAG_REPLACED_LV_0; + else + k = pc_checkskill(sd,k); + if (k < skill_tree[c][i].need[j].lv) + { + f = 0; + break; + } + } + } + if( sd->status.job_level < skill_tree[c][i].joblv ) + f = 0; // job level requirement wasn't satisfied + } + + if( f ) { + inf2 = skill_get_inf2(id); + + if(!sd->status.skill[id].lv && ( + (inf2&INF2_QUEST_SKILL && !battle_config.quest_skill_learn) || + inf2&INF2_WEDDING_SKILL || + (inf2&INF2_SPIRIT_SKILL && !sd->sc.data[SC_SPIRIT]) + )) + continue; //Cannot be learned via normal means. Note this check DOES allows raising already known skills. + + sd->status.skill[id].id = id; + + if(inf2&INF2_SPIRIT_SKILL) { //Spirit skills cannot be learned, they will only show up on your tree when you get buffed. + sd->status.skill[id].lv = 1; // need to manually specify a skill level + sd->status.skill[id].flag = SKILL_FLAG_TEMPORARY; //So it is not saved, and tagged as a "bonus" skill. + } + flag = 1; // skill list has changed, perform another pass + } + } + } while(flag); + + // + if( c > 0 && (sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && sd->status.skill_point == 0 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) ) + { + /* Taekwon Ranger Bonus Skill Tree + ============================================ + - Grant All Taekwon Tree, but only as Bonus Skills in case they drop from ranking. + - (c > 0) to avoid grant Novice Skill Tree in case of Skill Reset (need more logic) + - (sd->status.skill_point == 0) to wait until all skill points are asigned to avoid problems with Job Change quest. */ + + for( i = 0; i < MAX_SKILL_TREE && (id = skill_tree[c][i].id) > 0; i++ ) + { + if( (skill_get_inf2(id)&(INF2_QUEST_SKILL|INF2_WEDDING_SKILL)) ) + continue; //Do not include Quest/Wedding skills. + + if( sd->status.skill[id].id == 0 ) + { + sd->status.skill[id].id = id; + sd->status.skill[id].flag = SKILL_FLAG_TEMPORARY; // So it is not saved, and tagged as a "bonus" skill. + } + else + { + sd->status.skill[id].flag = SKILL_FLAG_REPLACED_LV_0 + sd->status.skill[id].lv; // Remember original level + } + + sd->status.skill[id].lv = skill_tree_get_max(id, sd->status.class_); + } + } + + return 0; +} + +//Checks if you can learn a new skill after having leveled up a skill. +static void pc_check_skilltree(struct map_session_data *sd, int skill) +{ + int i,id=0,flag; + int c=0; + + if(battle_config.skillfree) + return; //Function serves no purpose if this is set + + i = pc_calc_skilltree_normalize_job(sd); + c = pc_mapid2jobid(i, sd->status.sex); + if (c == -1) { //Unable to normalize job?? + ShowError("pc_check_skilltree: Unable to normalize job %d for character %s (%d:%d)\n", i, sd->status.name, sd->status.account_id, sd->status.char_id); + return; + } + c = pc_class2idx(c); + do { + flag = 0; + for( i = 0; i < MAX_SKILL_TREE && (id=skill_tree[c][i].id)>0; i++ ) + { + int j, f = 1, k; + + if( sd->status.skill[id].id ) //Already learned + continue; + + for( j = 0; j < MAX_PC_SKILL_REQUIRE; j++ ) + { + if( (k = skill_tree[c][i].need[j].id) ) + { + if( sd->status.skill[k].id == 0 || sd->status.skill[k].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[k].flag == SKILL_FLAG_PLAGIARIZED ) + k = 0; //Not learned. + else + if( sd->status.skill[k].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level + k = sd->status.skill[skill_tree[c][i].need[j].id].flag - SKILL_FLAG_REPLACED_LV_0; + else + k = pc_checkskill(sd,k); + if( k < skill_tree[c][i].need[j].lv ) + { + f = 0; + break; + } + } + } + if( !f ) + continue; + if( sd->status.job_level < skill_tree[c][i].joblv ) + continue; + + j = skill_get_inf2(id); + if( !sd->status.skill[id].lv && ( + (j&INF2_QUEST_SKILL && !battle_config.quest_skill_learn) || + j&INF2_WEDDING_SKILL || + (j&INF2_SPIRIT_SKILL && !sd->sc.data[SC_SPIRIT]) + ) ) + continue; //Cannot be learned via normal means. + + sd->status.skill[id].id = id; + flag = 1; + } + } while(flag); +} + +// Make sure all the skills are in the correct condition +// before persisting to the backend.. [MouseJstr] +int pc_clean_skilltree(struct map_session_data *sd) +{ + int i; + for (i = 0; i < MAX_SKILL; i++){ + if (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED) + { + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + } + else + if (sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0){ + sd->status.skill[i].lv = sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0; + sd->status.skill[i].flag = 0; + } + } + + return 0; +} + +int pc_calc_skilltree_normalize_job(struct map_session_data *sd) +{ + int skill_point, novice_skills; + int c = sd->class_; + + if (!battle_config.skillup_limit || pc_has_permission(sd, PC_PERM_ALL_SKILL)) + return c; + + skill_point = pc_calc_skillpoint(sd); + + novice_skills = max_level[pc_class2idx(JOB_NOVICE)][1] - 1; + + // limit 1st class and above to novice job levels + if(skill_point < novice_skills) + { + c = MAPID_NOVICE; + } + // limit 2nd class and above to first class job levels (super novices are exempt) + else if ((sd->class_&JOBL_2) && (sd->class_&MAPID_UPPERMASK) != MAPID_SUPER_NOVICE) + { + // regenerate change_level_2nd + if (!sd->change_level_2nd) + { + if (sd->class_&JOBL_THIRD) + { + // if neither 2nd nor 3rd jobchange levels are known, we have to assume a default for 2nd + if (!sd->change_level_3rd) + sd->change_level_2nd = max_level[pc_class2idx(pc_mapid2jobid(sd->class_&MAPID_UPPERMASK, sd->status.sex))][1]; + else + sd->change_level_2nd = 1 + skill_point + sd->status.skill_point + - (sd->status.job_level - 1) + - (sd->change_level_3rd - 1) + - novice_skills; + } + else + { + sd->change_level_2nd = 1 + skill_point + sd->status.skill_point + - (sd->status.job_level - 1) + - novice_skills; + + } + + pc_setglobalreg (sd, "jobchange_level", sd->change_level_2nd); + } + + if (skill_point < novice_skills + (sd->change_level_2nd - 1)) + { + c &= MAPID_BASEMASK; + } + // limit 3rd class to 2nd class/trans job levels + else if(sd->class_&JOBL_THIRD) + { + // regenerate change_level_3rd + if (!sd->change_level_3rd) + { + sd->change_level_3rd = 1 + skill_point + sd->status.skill_point + - (sd->status.job_level - 1) + - (sd->change_level_2nd - 1) + - novice_skills; + pc_setglobalreg (sd, "jobchange_level_3rd", sd->change_level_3rd); + } + + if (skill_point < novice_skills + (sd->change_level_2nd - 1) + (sd->change_level_3rd - 1)) + c &= MAPID_UPPERMASK; + } + } + + // restore non-limiting flags + c |= sd->class_&(JOBL_UPPER|JOBL_BABY); + + return c; +} + +/*========================================== + * Updates the weight status + *------------------------------------------ + * 1: overweight 50% + * 2: overweight 90% + * It's assumed that SC_WEIGHT50 and SC_WEIGHT90 are only started/stopped here. + */ +int pc_updateweightstatus(struct map_session_data *sd) +{ + int old_overweight; + int new_overweight; + + nullpo_retr(1, sd); + + old_overweight = (sd->sc.data[SC_WEIGHT90]) ? 2 : (sd->sc.data[SC_WEIGHT50]) ? 1 : 0; + new_overweight = (pc_is90overweight(sd)) ? 2 : (pc_is50overweight(sd)) ? 1 : 0; + + if( old_overweight == new_overweight ) + return 0; // no change + + // stop old status change + if( old_overweight == 1 ) + status_change_end(&sd->bl, SC_WEIGHT50, INVALID_TIMER); + else if( old_overweight == 2 ) + status_change_end(&sd->bl, SC_WEIGHT90, INVALID_TIMER); + + // start new status change + if( new_overweight == 1 ) + sc_start(&sd->bl, SC_WEIGHT50, 100, 0, 0); + else if( new_overweight == 2 ) + sc_start(&sd->bl, SC_WEIGHT90, 100, 0, 0); + + // update overweight status + sd->regen.state.overweight = new_overweight; + + return 0; +} + +int pc_disguise(struct map_session_data *sd, int class_) +{ + if (!class_ && !sd->disguise) + return 0; + if (class_ && sd->disguise == class_) + return 0; + + if(sd->sc.option&OPTION_INVISIBLE) + { //Character is invisible. Stealth class-change. [Skotlex] + sd->disguise = class_; //viewdata is set on uncloaking. + return 2; + } + + if (sd->bl.prev != NULL) { + pc_stop_walking(sd, 0); + clif_clearunit_area(&sd->bl, CLR_OUTSIGHT); + } + + if (!class_) { + sd->disguise = 0; + class_ = sd->status.class_; + } else + sd->disguise=class_; + + status_set_viewdata(&sd->bl, class_); + clif_changeoption(&sd->bl); + + if (sd->bl.prev != NULL) { + clif_spawn(&sd->bl); + if (class_ == sd->status.class_ && pc_iscarton(sd)) + { //It seems the cart info is lost on undisguise. + clif_cartlist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + } + return 1; +} + +static int pc_bonus_autospell(struct s_autospell *spell, int max, short id, short lv, short rate, short flag, short card_id) +{ + int i; + + if( !rate ) + return 0; + + for( i = 0; i < max && spell[i].id; i++ ) + { + if( (spell[i].card_id == card_id || spell[i].rate < 0 || rate < 0) && spell[i].id == id && spell[i].lv == lv ) + { + if( !battle_config.autospell_stacking && spell[i].rate > 0 && rate > 0 ) + return 0; + rate += spell[i].rate; + break; + } + } + if (i == max) { + ShowWarning("pc_bonus: Reached max (%d) number of autospells per character!\n", max); + return 0; + } + spell[i].id = id; + spell[i].lv = lv; + spell[i].rate = rate; + //Auto-update flag value. + if (!(flag&BF_RANGEMASK)) flag|=BF_SHORT|BF_LONG; //No range defined? Use both. + if (!(flag&BF_WEAPONMASK)) flag|=BF_WEAPON; //No attack type defined? Use weapon. + if (!(flag&BF_SKILLMASK)) { + if (flag&(BF_MAGIC|BF_MISC)) flag|=BF_SKILL; //These two would never trigger without BF_SKILL + if (flag&BF_WEAPON) flag|=BF_NORMAL; //By default autospells should only trigger on normal weapon attacks. + } + spell[i].flag|= flag; + spell[i].card_id = card_id; + return 1; +} + +static int pc_bonus_autospell_onskill(struct s_autospell *spell, int max, short src_skill, short id, short lv, short rate, short card_id) +{ + int i; + + if( !rate ) + return 0; + + for( i = 0; i < max && spell[i].id; i++ ) + { + ; // each autospell works independently + } + + if( i == max ) + { + ShowWarning("pc_bonus: Reached max (%d) number of autospells per character!\n", max); + return 0; + } + + spell[i].flag = src_skill; + spell[i].id = id; + spell[i].lv = lv; + spell[i].rate = rate; + spell[i].card_id = card_id; + return 1; +} + +static int pc_bonus_addeff(struct s_addeffect* effect, int max, enum sc_type id, short rate, short arrow_rate, unsigned char flag) +{ + int i; + if (!(flag&(ATF_SHORT|ATF_LONG))) + flag|=ATF_SHORT|ATF_LONG; //Default range: both + if (!(flag&(ATF_TARGET|ATF_SELF))) + flag|=ATF_TARGET; //Default target: enemy. + if (!(flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC))) + flag|=ATF_WEAPON; //Default type: weapon. + + for (i = 0; i < max && effect[i].flag; i++) { + if (effect[i].id == id && effect[i].flag == flag) + { + effect[i].rate += rate; + effect[i].arrow_rate += arrow_rate; + return 1; + } + } + if (i == max) { + ShowWarning("pc_bonus: Reached max (%d) number of add effects per character!\n", max); + return 0; + } + effect[i].id = id; + effect[i].rate = rate; + effect[i].arrow_rate = arrow_rate; + effect[i].flag = flag; + return 1; +} + +static int pc_bonus_addeff_onskill(struct s_addeffectonskill* effect, int max, enum sc_type id, short rate, short skill, unsigned char target) +{ + int i; + for( i = 0; i < max && effect[i].skill; i++ ) + { + if( effect[i].id == id && effect[i].skill == skill && effect[i].target == target ) + { + effect[i].rate += rate; + return 1; + } + } + if( i == max ) { + ShowWarning("pc_bonus: Reached max (%d) number of add effects on skill per character!\n", max); + return 0; + } + effect[i].id = id; + effect[i].rate = rate; + effect[i].skill = skill; + effect[i].target = target; + return 1; +} + +static int pc_bonus_item_drop(struct s_add_drop *drop, const short max, short id, short group, int race, int rate) +{ + int i; + //Apply config rate adjustment settings. + if (rate >= 0) { //Absolute drop. + if (battle_config.item_rate_adddrop != 100) + rate = rate*battle_config.item_rate_adddrop/100; + if (rate < battle_config.item_drop_adddrop_min) + rate = battle_config.item_drop_adddrop_min; + else if (rate > battle_config.item_drop_adddrop_max) + rate = battle_config.item_drop_adddrop_max; + } else { //Relative drop, max/min limits are applied at drop time. + if (battle_config.item_rate_adddrop != 100) + rate = rate*battle_config.item_rate_adddrop/100; + if (rate > -1) + rate = -1; + } + for(i = 0; i < max && (drop[i].id || drop[i].group); i++) { + if( + ((id && drop[i].id == id) || + (group && drop[i].group == group)) + && race > 0 + ) { + drop[i].race |= race; + if(drop[i].rate > 0 && rate > 0) + { //Both are absolute rates. + if (drop[i].rate < rate) + drop[i].rate = rate; + } else + if(drop[i].rate < 0 && rate < 0) { + //Both are relative rates. + if (drop[i].rate > rate) + drop[i].rate = rate; + } else if (rate < 0) //Give preference to relative rate. + drop[i].rate = rate; + return 1; + } + } + if(i == max) { + ShowWarning("pc_bonus: Reached max (%d) number of added drops per character!\n", max); + return 0; + } + drop[i].id = id; + drop[i].group = group; + drop[i].race |= race; + drop[i].rate = rate; + return 1; +} + +int pc_addautobonus(struct s_autobonus *bonus,char max,const char *script,short rate,unsigned int dur,short flag,const char *other_script,unsigned short pos,bool onskill) +{ + int i; + + ARR_FIND(0, max, i, bonus[i].rate == 0); + if( i == max ) + { + ShowWarning("pc_addautobonus: Reached max (%d) number of autobonus per character!\n", max); + return 0; + } + + if( !onskill ) + { + if( !(flag&BF_RANGEMASK) ) + flag|=BF_SHORT|BF_LONG; //No range defined? Use both. + if( !(flag&BF_WEAPONMASK) ) + flag|=BF_WEAPON; //No attack type defined? Use weapon. + if( !(flag&BF_SKILLMASK) ) + { + if( flag&(BF_MAGIC|BF_MISC) ) + flag|=BF_SKILL; //These two would never trigger without BF_SKILL + if( flag&BF_WEAPON ) + flag|=BF_NORMAL|BF_SKILL; + } + } + + bonus[i].rate = rate; + bonus[i].duration = dur; + bonus[i].active = INVALID_TIMER; + bonus[i].atk_type = flag; + bonus[i].pos = pos; + bonus[i].bonus_script = aStrdup(script); + bonus[i].other_script = other_script?aStrdup(other_script):NULL; + return 1; +} + +int pc_delautobonus(struct map_session_data* sd, struct s_autobonus *autobonus,char max,bool restore) +{ + int i; + nullpo_ret(sd); + + for( i = 0; i < max; i++ ) + { + if( autobonus[i].active != INVALID_TIMER ) + { + if( restore && sd->state.autobonus&autobonus[i].pos ) + { + if( autobonus[i].bonus_script ) + { + int j; + ARR_FIND( 0, EQI_MAX-1, j, sd->equip_index[j] >= 0 && sd->status.inventory[sd->equip_index[j]].equip == autobonus[i].pos ); + if( j < EQI_MAX-1 ) + script_run_autobonus(autobonus[i].bonus_script,sd->bl.id,sd->equip_index[j]); + } + continue; + } + else + { // Logout / Unequipped an item with an activated bonus + delete_timer(autobonus[i].active,pc_endautobonus); + autobonus[i].active = INVALID_TIMER; + } + } + + if( autobonus[i].bonus_script ) aFree(autobonus[i].bonus_script); + if( autobonus[i].other_script ) aFree(autobonus[i].other_script); + autobonus[i].bonus_script = autobonus[i].other_script = NULL; + autobonus[i].rate = autobonus[i].atk_type = autobonus[i].duration = autobonus[i].pos = 0; + autobonus[i].active = INVALID_TIMER; + } + + return 0; +} + +int pc_exeautobonus(struct map_session_data *sd,struct s_autobonus *autobonus) +{ + nullpo_ret(sd); + nullpo_ret(autobonus); + + if( autobonus->other_script ) + { + int j; + ARR_FIND( 0, EQI_MAX-1, j, sd->equip_index[j] >= 0 && sd->status.inventory[sd->equip_index[j]].equip == autobonus->pos ); + if( j < EQI_MAX-1 ) + script_run_autobonus(autobonus->other_script,sd->bl.id,sd->equip_index[j]); + } + + autobonus->active = add_timer(gettick()+autobonus->duration, pc_endautobonus, sd->bl.id, (intptr_t)autobonus); + sd->state.autobonus |= autobonus->pos; + status_calc_pc(sd,0); + + return 0; +} + +int pc_endautobonus(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = map_id2sd(id); + struct s_autobonus *autobonus = (struct s_autobonus *)data; + + nullpo_ret(sd); + nullpo_ret(autobonus); + + autobonus->active = INVALID_TIMER; + sd->state.autobonus &= ~autobonus->pos; + status_calc_pc(sd,0); + return 0; +} + +int pc_bonus_addele(struct map_session_data* sd, unsigned char ele, short rate, short flag) +{ + int i; + struct weapon_data* wd; + + wd = (sd->state.lr_flag ? &sd->left_weapon : &sd->right_weapon); + + ARR_FIND(0, MAX_PC_BONUS, i, wd->addele2[i].rate == 0); + + if (i == MAX_PC_BONUS) + { + ShowWarning("pc_addele: Reached max (%d) possible bonuses for this player.\n", MAX_PC_BONUS); + return 0; + } + + if (!(flag&BF_RANGEMASK)) + flag |= BF_SHORT|BF_LONG; + if (!(flag&BF_WEAPONMASK)) + flag |= BF_WEAPON; + if (!(flag&BF_SKILLMASK)) + { + if (flag&(BF_MAGIC|BF_MISC)) + flag |= BF_SKILL; + if (flag&BF_WEAPON) + flag |= BF_NORMAL|BF_SKILL; + } + + wd->addele2[i].ele = ele; + wd->addele2[i].rate = rate; + wd->addele2[i].flag = flag; + + return 0; +} + +int pc_bonus_subele(struct map_session_data* sd, unsigned char ele, short rate, short flag) +{ + int i; + + ARR_FIND(0, MAX_PC_BONUS, i, sd->subele2[i].rate == 0); + + if (i == MAX_PC_BONUS) + { + ShowWarning("pc_subele: Reached max (%d) possible bonuses for this player.\n", MAX_PC_BONUS); + return 0; + } + + if (!(flag&BF_RANGEMASK)) + flag |= BF_SHORT|BF_LONG; + if (!(flag&BF_WEAPONMASK)) + flag |= BF_WEAPON; + if (!(flag&BF_SKILLMASK)) + { + if (flag&(BF_MAGIC|BF_MISC)) + flag |= BF_SKILL; + if (flag&BF_WEAPON) + flag |= BF_NORMAL|BF_SKILL; + } + + sd->subele2[i].ele = ele; + sd->subele2[i].rate = rate; + sd->subele2[i].flag = flag; + + return 0; +} + +/*========================================== + * Add a bonus(type) to player sd + *------------------------------------------*/ +int pc_bonus(struct map_session_data *sd,int type,int val) +{ + struct status_data *status; + int bonus; + nullpo_ret(sd); + + status = &sd->base_status; + + switch(type){ + case SP_STR: + case SP_AGI: + case SP_VIT: + case SP_INT: + case SP_DEX: + case SP_LUK: + if(sd->state.lr_flag != 2) + sd->param_bonus[type-SP_STR]+=val; + break; + case SP_ATK1: + if(!sd->state.lr_flag) { + bonus = status->rhw.atk + val; + status->rhw.atk = cap_value(bonus, 0, USHRT_MAX); + } + else if(sd->state.lr_flag == 1) { + bonus = status->lhw.atk + val; + status->lhw.atk = cap_value(bonus, 0, USHRT_MAX); + } + break; + case SP_ATK2: + if(!sd->state.lr_flag) { + bonus = status->rhw.atk2 + val; + status->rhw.atk2 = cap_value(bonus, 0, USHRT_MAX); + } + else if(sd->state.lr_flag == 1) { + bonus = status->lhw.atk2 + val; + status->lhw.atk2 = cap_value(bonus, 0, USHRT_MAX); + } + break; + case SP_BASE_ATK: + if(sd->state.lr_flag != 2) { +//#ifdef RENEWAL +// sd->bonus.eatk += val; +//#else + bonus = status->batk + val; + status->batk = cap_value(bonus, 0, USHRT_MAX); +//#endif + } + break; + case SP_DEF1: + if(sd->state.lr_flag != 2) { + bonus = status->def + val; +#ifdef RENEWAL + status->def = cap_value(bonus, SHRT_MIN, SHRT_MAX); +#else + status->def = cap_value(bonus, CHAR_MIN, CHAR_MAX); +#endif + } + break; + case SP_DEF2: + if(sd->state.lr_flag != 2) { + bonus = status->def2 + val; + status->def2 = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } + break; + case SP_MDEF1: + if(sd->state.lr_flag != 2) { + bonus = status->mdef + val; +#ifdef RENEWAL + status->mdef = cap_value(bonus, SHRT_MIN, SHRT_MAX); +#else + status->mdef = cap_value(bonus, CHAR_MIN, CHAR_MAX); +#endif + if( sd->state.lr_flag == 3 ) {//Shield, used for royal guard + sd->bonus.shieldmdef += bonus; + } + } + break; + case SP_MDEF2: + if(sd->state.lr_flag != 2) { + bonus = status->mdef2 + val; + status->mdef2 = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } + break; + case SP_HIT: + if(sd->state.lr_flag != 2) { + bonus = status->hit + val; + status->hit = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } else + sd->bonus.arrow_hit+=val; + break; + case SP_FLEE1: + if(sd->state.lr_flag != 2) { + bonus = status->flee + val; + status->flee = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } + break; + case SP_FLEE2: + if(sd->state.lr_flag != 2) { + bonus = status->flee2 + val*10; + status->flee2 = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } + break; + case SP_CRITICAL: + if(sd->state.lr_flag != 2) { + bonus = status->cri + val*10; + status->cri = cap_value(bonus, SHRT_MIN, SHRT_MAX); + } else + sd->bonus.arrow_cri += val*10; + break; + case SP_ATKELE: + if(val >= ELE_MAX) { + ShowError("pc_bonus: SP_ATKELE: Invalid element %d\n", val); + break; + } + switch (sd->state.lr_flag) + { + case 2: + switch (sd->status.weapon) { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + //Become weapon element. + status->rhw.ele=val; + break; + default: //Become arrow element. + sd->bonus.arrow_ele=val; + break; + } + break; + case 1: + status->lhw.ele=val; + break; + default: + status->rhw.ele=val; + break; + } + break; + case SP_DEFELE: + if(val >= ELE_MAX) { + ShowError("pc_bonus: SP_DEFELE: Invalid element %d\n", val); + break; + } + if(sd->state.lr_flag != 2) + status->def_ele=val; + break; + case SP_MAXHP: + if(sd->state.lr_flag == 2) + break; + val += (int)status->max_hp; + //Negative bonuses will underflow, this will be handled in status_calc_pc through casting + //If this is called outside of status_calc_pc, you'd better pray they do not underflow and end with UINT_MAX max_hp. + status->max_hp = (unsigned int)val; + break; + case SP_MAXSP: + if(sd->state.lr_flag == 2) + break; + val += (int)status->max_sp; + status->max_sp = (unsigned int)val; + break; +#ifndef RENEWAL_CAST + case SP_VARCASTRATE: +#endif + case SP_CASTRATE: + if(sd->state.lr_flag != 2) + sd->castrate+=val; + break; + case SP_MAXHPRATE: + if(sd->state.lr_flag != 2) + sd->hprate+=val; + break; + case SP_MAXSPRATE: + if(sd->state.lr_flag != 2) + sd->sprate+=val; + break; + case SP_SPRATE: + if(sd->state.lr_flag != 2) + sd->dsprate+=val; + break; + case SP_ATTACKRANGE: + switch (sd->state.lr_flag) { + case 2: + switch (sd->status.weapon) { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + status->rhw.range += val; + } + break; + case 1: + status->lhw.range += val; + break; + default: + status->rhw.range += val; + break; + } + break; + case SP_SPEED_RATE: //Non stackable increase + if(sd->state.lr_flag != 2) + sd->bonus.speed_rate = min(sd->bonus.speed_rate, -val); + break; + case SP_SPEED_ADDRATE: //Stackable increase + if(sd->state.lr_flag != 2) + sd->bonus.speed_add_rate -= val; + break; + case SP_ASPD: //Raw increase + if(sd->state.lr_flag != 2) + sd->bonus.aspd_add -= 10*val; + break; + case SP_ASPD_RATE: //Stackable increase - Made it linear as per rodatazone + if(sd->state.lr_flag != 2) +#ifndef RENEWAL_ASPD + status->aspd_rate -= 10*val; +#else + status->aspd_rate2 += val; +#endif + break; + case SP_HP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->hprecov_rate += val; + break; + case SP_SP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->sprecov_rate += val; + break; + case SP_CRITICAL_DEF: + if(sd->state.lr_flag != 2) + sd->bonus.critical_def += val; + break; + case SP_NEAR_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->bonus.near_attack_def_rate += val; + break; + case SP_LONG_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->bonus.long_attack_def_rate += val; + break; + case SP_DOUBLE_RATE: + if(sd->state.lr_flag == 0 && sd->bonus.double_rate < val) + sd->bonus.double_rate = val; + break; + case SP_DOUBLE_ADD_RATE: + if(sd->state.lr_flag == 0) + sd->bonus.double_add_rate += val; + break; + case SP_MATK_RATE: + if(sd->state.lr_flag != 2) + sd->matk_rate += val; + break; + case SP_IGNORE_DEF_ELE: + if(val >= ELE_MAX) { + ShowError("pc_bonus: SP_IGNORE_DEF_ELE: Invalid element %d\n", val); + break; + } + if(!sd->state.lr_flag) + sd->right_weapon.ignore_def_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.ignore_def_ele |= 1<<val; + break; + case SP_IGNORE_DEF_RACE: + if(!sd->state.lr_flag) + sd->right_weapon.ignore_def_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.ignore_def_race |= 1<<val; + break; + case SP_ATK_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.atk_rate += val; + break; + case SP_MAGIC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->bonus.magic_def_rate += val; + break; + case SP_MISC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->bonus.misc_def_rate += val; + break; + case SP_IGNORE_MDEF_RATE: + if(sd->state.lr_flag != 2) { + sd->ignore_mdef[RC_NONBOSS] += val; + sd->ignore_mdef[RC_BOSS] += val; + } + break; + case SP_IGNORE_MDEF_ELE: + if(val >= ELE_MAX) { + ShowError("pc_bonus: SP_IGNORE_MDEF_ELE: Invalid element %d\n", val); + break; + } + if(sd->state.lr_flag != 2) + sd->bonus.ignore_mdef_ele |= 1<<val; + break; + case SP_IGNORE_MDEF_RACE: + if(sd->state.lr_flag != 2) + sd->bonus.ignore_mdef_race |= 1<<val; + break; + case SP_PERFECT_HIT_RATE: + if(sd->state.lr_flag != 2 && sd->bonus.perfect_hit < val) + sd->bonus.perfect_hit = val; + break; + case SP_PERFECT_HIT_ADD_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.perfect_hit_add += val; + break; + case SP_CRITICAL_RATE: + if(sd->state.lr_flag != 2) + sd->critical_rate+=val; + break; + case SP_DEF_RATIO_ATK_ELE: + if(val >= ELE_MAX) { + ShowError("pc_bonus: SP_DEF_RATIO_ATK_ELE: Invalid element %d\n", val); + break; + } + if(!sd->state.lr_flag) + sd->right_weapon.def_ratio_atk_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.def_ratio_atk_ele |= 1<<val; + break; + case SP_DEF_RATIO_ATK_RACE: + if(val >= RC_MAX) { + ShowError("pc_bonus: SP_DEF_RATIO_ATK_RACE: Invalid race %d\n", val); + break; + } + if(!sd->state.lr_flag) + sd->right_weapon.def_ratio_atk_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.def_ratio_atk_race |= 1<<val; + break; + case SP_HIT_RATE: + if(sd->state.lr_flag != 2) + sd->hit_rate += val; + break; + case SP_FLEE_RATE: + if(sd->state.lr_flag != 2) + sd->flee_rate += val; + break; + case SP_FLEE2_RATE: + if(sd->state.lr_flag != 2) + sd->flee2_rate += val; + break; + case SP_DEF_RATE: + if(sd->state.lr_flag != 2) + sd->def_rate += val; + break; + case SP_DEF2_RATE: + if(sd->state.lr_flag != 2) + sd->def2_rate += val; + break; + case SP_MDEF_RATE: + if(sd->state.lr_flag != 2) + sd->mdef_rate += val; + break; + case SP_MDEF2_RATE: + if(sd->state.lr_flag != 2) + sd->mdef2_rate += val; + break; + case SP_RESTART_FULL_RECOVER: + if(sd->state.lr_flag != 2) + sd->special_state.restart_full_recover = 1; + break; + case SP_NO_CASTCANCEL: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel = 1; + break; + case SP_NO_CASTCANCEL2: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel2 = 1; + break; + case SP_NO_SIZEFIX: + if(sd->state.lr_flag != 2) + sd->special_state.no_sizefix = 1; + break; + case SP_NO_MAGIC_DAMAGE: + if(sd->state.lr_flag == 2) + break; + val+= sd->special_state.no_magic_damage; + sd->special_state.no_magic_damage = cap_value(val,0,100); + break; + case SP_NO_WEAPON_DAMAGE: + if(sd->state.lr_flag == 2) + break; + val+= sd->special_state.no_weapon_damage; + sd->special_state.no_weapon_damage = cap_value(val,0,100); + break; + case SP_NO_MISC_DAMAGE: + if(sd->state.lr_flag == 2) + break; + val+= sd->special_state.no_misc_damage; + sd->special_state.no_misc_damage = cap_value(val,0,100); + break; + case SP_NO_GEMSTONE: + if(sd->state.lr_flag != 2) + sd->special_state.no_gemstone = 1; + break; + case SP_INTRAVISION: // Maya Purple Card effect allowing to see Hiding/Cloaking people [DracoRPG] + if(sd->state.lr_flag != 2) { + sd->special_state.intravision = 1; + clif_status_load(&sd->bl, SI_INTRAVISION, 1); + } + break; + case SP_NO_KNOCKBACK: + if(sd->state.lr_flag != 2) + sd->special_state.no_knockback = 1; + break; + case SP_SPLASH_RANGE: + if(sd->bonus.splash_range < val) + sd->bonus.splash_range = val; + break; + case SP_SPLASH_ADD_RANGE: + sd->bonus.splash_add_range += val; + break; + case SP_SHORT_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->bonus.short_weapon_damage_return += val; + break; + case SP_LONG_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->bonus.long_weapon_damage_return += val; + break; + case SP_MAGIC_DAMAGE_RETURN: //AppleGirl Was Here + if(sd->state.lr_flag != 2) + sd->bonus.magic_damage_return += val; + break; + case SP_ALL_STATS: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->param_bonus[SP_STR-SP_STR]+=val; + sd->param_bonus[SP_AGI-SP_STR]+=val; + sd->param_bonus[SP_VIT-SP_STR]+=val; + sd->param_bonus[SP_INT-SP_STR]+=val; + sd->param_bonus[SP_DEX-SP_STR]+=val; + sd->param_bonus[SP_LUK-SP_STR]+=val; + } + break; + case SP_AGI_VIT: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->param_bonus[SP_AGI-SP_STR]+=val; + sd->param_bonus[SP_VIT-SP_STR]+=val; + } + break; + case SP_AGI_DEX_STR: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->param_bonus[SP_AGI-SP_STR]+=val; + sd->param_bonus[SP_DEX-SP_STR]+=val; + sd->param_bonus[SP_STR-SP_STR]+=val; + } + break; + case SP_PERFECT_HIDE: // [Valaris] + if(sd->state.lr_flag!=2) + sd->special_state.perfect_hiding=1; + break; + case SP_UNBREAKABLE: + if(sd->state.lr_flag!=2) + sd->bonus.unbreakable += val; + break; + case SP_UNBREAKABLE_WEAPON: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_WEAPON; + break; + case SP_UNBREAKABLE_ARMOR: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_ARMOR; + break; + case SP_UNBREAKABLE_HELM: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_HELM; + break; + case SP_UNBREAKABLE_SHIELD: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_SHIELD; + break; + case SP_UNBREAKABLE_GARMENT: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_GARMENT; + break; + case SP_UNBREAKABLE_SHOES: + if(sd->state.lr_flag != 2) + sd->bonus.unbreakable_equip |= EQP_SHOES; + break; + case SP_CLASSCHANGE: // [Valaris] + if(sd->state.lr_flag !=2) + sd->bonus.classchange=val; + break; + case SP_LONG_ATK_RATE: + if(sd->state.lr_flag != 2) //[Lupus] it should stack, too. As any other cards rate bonuses + sd->bonus.long_attack_atk_rate+=val; + break; + case SP_BREAK_WEAPON_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.break_weapon_rate+=val; + break; + case SP_BREAK_ARMOR_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.break_armor_rate+=val; + break; + case SP_ADD_STEAL_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.add_steal_rate+=val; + break; + case SP_DELAYRATE: + if(sd->state.lr_flag != 2) + sd->delayrate+=val; + break; + case SP_CRIT_ATK_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.crit_atk_rate += val; + break; + case SP_NO_REGEN: + if(sd->state.lr_flag != 2) + sd->regen.state.block|=val; + break; + case SP_UNSTRIPABLE_WEAPON: + if(sd->state.lr_flag != 2) + sd->bonus.unstripable_equip |= EQP_WEAPON; + break; + case SP_UNSTRIPABLE: + case SP_UNSTRIPABLE_ARMOR: + if(sd->state.lr_flag != 2) + sd->bonus.unstripable_equip |= EQP_ARMOR; + break; + case SP_UNSTRIPABLE_HELM: + if(sd->state.lr_flag != 2) + sd->bonus.unstripable_equip |= EQP_HELM; + break; + case SP_UNSTRIPABLE_SHIELD: + if(sd->state.lr_flag != 2) + sd->bonus.unstripable_equip |= EQP_SHIELD; + break; + case SP_HP_DRAIN_VALUE: + if(!sd->state.lr_flag) { + sd->right_weapon.hp_drain[RC_NONBOSS].value += val; + sd->right_weapon.hp_drain[RC_BOSS].value += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.hp_drain[RC_NONBOSS].value += val; + sd->left_weapon.hp_drain[RC_BOSS].value += val; + } + break; + case SP_SP_DRAIN_VALUE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[RC_NONBOSS].value += val; + sd->right_weapon.sp_drain[RC_BOSS].value += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[RC_NONBOSS].value += val; + sd->left_weapon.sp_drain[RC_BOSS].value += val; + } + break; + case SP_SP_GAIN_VALUE: + if(!sd->state.lr_flag) + sd->bonus.sp_gain_value += val; + break; + case SP_HP_GAIN_VALUE: + if(!sd->state.lr_flag) + sd->bonus.hp_gain_value += val; + break; + case SP_MAGIC_SP_GAIN_VALUE: + if(!sd->state.lr_flag) + sd->bonus.magic_sp_gain_value += val; + break; + case SP_MAGIC_HP_GAIN_VALUE: + if(!sd->state.lr_flag) + sd->bonus.magic_hp_gain_value += val; + break; + case SP_ADD_HEAL_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.add_heal_rate += val; + break; + case SP_ADD_HEAL2_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.add_heal2_rate += val; + break; + case SP_ADD_ITEM_HEAL_RATE: + if(sd->state.lr_flag != 2) + sd->bonus.itemhealrate2 += val; + break; + case SP_EMATK: + if(sd->state.lr_flag != 2) + sd->bonus.ematk += val; + break; + case SP_FIXCASTRATE: + if(sd->state.lr_flag != 2) + sd->bonus.fixcastrate -= val; + break; +#ifdef RENEWAL_CAST + case SP_VARCASTRATE: + if(sd->state.lr_flag != 2) + sd->bonus.varcastrate -= val; + break; +#endif + default: + ShowWarning("pc_bonus: unknown type %d %d !\n",type,val); + break; + } + return 0; +} + +/*========================================== + * Player bonus (type) with args type2 and val, called trough bonus2 (npc) + *------------------------------------------*/ +int pc_bonus2(struct map_session_data *sd,int type,int type2,int val) +{ + int i; + + nullpo_ret(sd); + + switch(type){ + case SP_ADDELE: + if(type2 >= ELE_MAX) { + ShowError("pc_bonus2: SP_ADDELE: Invalid element %d\n", type2); + break; + } + if(!sd->state.lr_flag) + sd->right_weapon.addele[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.addele[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addele[type2]+=val; + break; + case SP_ADDRACE: + if(!sd->state.lr_flag) + sd->right_weapon.addrace[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.addrace[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addrace[type2]+=val; + break; + case SP_ADDSIZE: + if(!sd->state.lr_flag) + sd->right_weapon.addsize[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->left_weapon.addsize[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addsize[type2]+=val; + break; + case SP_SUBELE: + if(type2 >= ELE_MAX) { + ShowError("pc_bonus2: SP_SUBELE: Invalid element %d\n", type2); + break; + } + if(sd->state.lr_flag != 2) + sd->subele[type2]+=val; + break; + case SP_SUBRACE: + if(sd->state.lr_flag != 2) + sd->subrace[type2]+=val; + break; + case SP_ADDEFF: + if (type2 > SC_MAX) { + ShowWarning("pc_bonus2 (Add Effect): %d is not supported.\n", type2); + break; + } + pc_bonus_addeff(sd->addeff, ARRAYLENGTH(sd->addeff), (sc_type)type2, + sd->state.lr_flag!=2?val:0, sd->state.lr_flag==2?val:0, 0); + break; + case SP_ADDEFF2: + if (type2 > SC_MAX) { + ShowWarning("pc_bonus2 (Add Effect2): %d is not supported.\n", type2); + break; + } + pc_bonus_addeff(sd->addeff, ARRAYLENGTH(sd->addeff), (sc_type)type2, + sd->state.lr_flag!=2?val:0, sd->state.lr_flag==2?val:0, ATF_SELF); + break; + case SP_RESEFF: + if (type2 < SC_COMMON_MIN || type2 > SC_COMMON_MAX) { + ShowWarning("pc_bonus2 (Resist Effect): %d is not supported.\n", type2); + break; + } + if(sd->state.lr_flag == 2) + break; + i = sd->reseff[type2-SC_COMMON_MIN]+val; + sd->reseff[type2-SC_COMMON_MIN]= cap_value(i, 0, 10000); + break; + case SP_MAGIC_ADDELE: + if(type2 >= ELE_MAX) { + ShowError("pc_bonus2: SP_MAGIC_ADDELE: Invalid element %d\n", type2); + break; + } + if(sd->state.lr_flag != 2) + sd->magic_addele[type2]+=val; + break; + case SP_MAGIC_ADDRACE: + if(sd->state.lr_flag != 2) + sd->magic_addrace[type2]+=val; + break; + case SP_MAGIC_ADDSIZE: + if(sd->state.lr_flag != 2) + sd->magic_addsize[type2]+=val; + break; + case SP_MAGIC_ATK_ELE: + if(sd->state.lr_flag != 2) + sd->magic_atk_ele[type2]+=val; + break; + case SP_ADD_DAMAGE_CLASS: + switch (sd->state.lr_flag) { + case 0: //Right hand + ARR_FIND(0, ARRAYLENGTH(sd->right_weapon.add_dmg), i, sd->right_weapon.add_dmg[i].rate == 0 || sd->right_weapon.add_dmg[i].class_ == type2); + if (i == ARRAYLENGTH(sd->right_weapon.add_dmg)) + { + ShowWarning("pc_bonus2: Reached max (%d) number of add Class dmg bonuses per character!\n", ARRAYLENGTH(sd->right_weapon.add_dmg)); + break; + } + sd->right_weapon.add_dmg[i].class_ = type2; + sd->right_weapon.add_dmg[i].rate += val; + if (!sd->right_weapon.add_dmg[i].rate) //Shift the rest of elements up. + memmove(&sd->right_weapon.add_dmg[i], &sd->right_weapon.add_dmg[i+1], sizeof(sd->right_weapon.add_dmg) - (i+1)*sizeof(sd->right_weapon.add_dmg[0])); + break; + case 1: //Left hand + ARR_FIND(0, ARRAYLENGTH(sd->left_weapon.add_dmg), i, sd->left_weapon.add_dmg[i].rate == 0 || sd->left_weapon.add_dmg[i].class_ == type2); + if (i == ARRAYLENGTH(sd->left_weapon.add_dmg)) + { + ShowWarning("pc_bonus2: Reached max (%d) number of add Class dmg bonuses per character!\n", ARRAYLENGTH(sd->left_weapon.add_dmg)); + break; + } + sd->left_weapon.add_dmg[i].class_ = type2; + sd->left_weapon.add_dmg[i].rate += val; + if (!sd->left_weapon.add_dmg[i].rate) //Shift the rest of elements up. + memmove(&sd->left_weapon.add_dmg[i], &sd->left_weapon.add_dmg[i+1], sizeof(sd->left_weapon.add_dmg) - (i+1)*sizeof(sd->left_weapon.add_dmg[0])); + break; + } + break; + case SP_ADD_MAGIC_DAMAGE_CLASS: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->add_mdmg), i, sd->add_mdmg[i].rate == 0 || sd->add_mdmg[i].class_ == type2); + if (i == ARRAYLENGTH(sd->add_mdmg)) + { + ShowWarning("pc_bonus2: Reached max (%d) number of add Class magic dmg bonuses per character!\n", ARRAYLENGTH(sd->add_mdmg)); + break; + } + sd->add_mdmg[i].class_ = type2; + sd->add_mdmg[i].rate += val; + if (!sd->add_mdmg[i].rate) //Shift the rest of elements up. + memmove(&sd->add_mdmg[i], &sd->add_mdmg[i+1], sizeof(sd->add_mdmg) - (i+1)*sizeof(sd->add_mdmg[0])); + break; + case SP_ADD_DEF_CLASS: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->add_def), i, sd->add_def[i].rate == 0 || sd->add_def[i].class_ == type2); + if (i == ARRAYLENGTH(sd->add_def)) + { + ShowWarning("pc_bonus2: Reached max (%d) number of add Class def bonuses per character!\n", ARRAYLENGTH(sd->add_def)); + break; + } + sd->add_def[i].class_ = type2; + sd->add_def[i].rate += val; + if (!sd->add_def[i].rate) //Shift the rest of elements up. + memmove(&sd->add_def[i], &sd->add_def[i+1], sizeof(sd->add_def) - (i+1)*sizeof(sd->add_def[0])); + break; + case SP_ADD_MDEF_CLASS: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->add_mdef), i, sd->add_mdef[i].rate == 0 || sd->add_mdef[i].class_ == type2); + if (i == ARRAYLENGTH(sd->add_mdef)) + { + ShowWarning("pc_bonus2: Reached max (%d) number of add Class mdef bonuses per character!\n", ARRAYLENGTH(sd->add_mdef)); + break; + } + sd->add_mdef[i].class_ = type2; + sd->add_mdef[i].rate += val; + if (!sd->add_mdef[i].rate) //Shift the rest of elements up. + memmove(&sd->add_mdef[i], &sd->add_mdef[i+1], sizeof(sd->add_mdef) - (i+1)*sizeof(sd->add_mdef[0])); + break; + case SP_HP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->right_weapon.hp_drain[RC_NONBOSS].rate += type2; + sd->right_weapon.hp_drain[RC_NONBOSS].per += val; + sd->right_weapon.hp_drain[RC_BOSS].rate += type2; + sd->right_weapon.hp_drain[RC_BOSS].per += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.hp_drain[RC_NONBOSS].rate += type2; + sd->left_weapon.hp_drain[RC_NONBOSS].per += val; + sd->left_weapon.hp_drain[RC_BOSS].rate += type2; + sd->left_weapon.hp_drain[RC_BOSS].per += val; + } + break; + case SP_HP_DRAIN_VALUE: + if(!sd->state.lr_flag) { + sd->right_weapon.hp_drain[RC_NONBOSS].value += type2; + sd->right_weapon.hp_drain[RC_NONBOSS].type = val; + sd->right_weapon.hp_drain[RC_BOSS].value += type2; + sd->right_weapon.hp_drain[RC_BOSS].type = val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.hp_drain[RC_NONBOSS].value += type2; + sd->left_weapon.hp_drain[RC_NONBOSS].type = val; + sd->left_weapon.hp_drain[RC_BOSS].value += type2; + sd->left_weapon.hp_drain[RC_BOSS].type = val; + } + break; + case SP_SP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[RC_NONBOSS].rate += type2; + sd->right_weapon.sp_drain[RC_NONBOSS].per += val; + sd->right_weapon.sp_drain[RC_BOSS].rate += type2; + sd->right_weapon.sp_drain[RC_BOSS].per += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[RC_NONBOSS].rate += type2; + sd->left_weapon.sp_drain[RC_NONBOSS].per += val; + sd->left_weapon.sp_drain[RC_BOSS].rate += type2; + sd->left_weapon.sp_drain[RC_BOSS].per += val; + } + break; + case SP_SP_DRAIN_VALUE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[RC_NONBOSS].value += type2; + sd->right_weapon.sp_drain[RC_NONBOSS].type = val; + sd->right_weapon.sp_drain[RC_BOSS].value += type2; + sd->right_weapon.sp_drain[RC_BOSS].type = val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[RC_NONBOSS].value += type2; + sd->left_weapon.sp_drain[RC_NONBOSS].type = val; + sd->left_weapon.sp_drain[RC_BOSS].value += type2; + sd->left_weapon.sp_drain[RC_BOSS].type = val; + } + break; + case SP_SP_VANISH_RATE: + if(sd->state.lr_flag != 2) { + sd->bonus.sp_vanish_rate += type2; + sd->bonus.sp_vanish_per += val; + } + break; + case SP_GET_ZENY_NUM: + if(sd->state.lr_flag != 2 && sd->bonus.get_zeny_rate < val) { + sd->bonus.get_zeny_rate = val; + sd->bonus.get_zeny_num = type2; + } + break; + case SP_ADD_GET_ZENY_NUM: + if(sd->state.lr_flag != 2) { + sd->bonus.get_zeny_rate += val; + sd->bonus.get_zeny_num += type2; + } + break; + case SP_WEAPON_COMA_ELE: + if(type2 >= ELE_MAX) { + ShowError("pc_bonus2: SP_WEAPON_COMA_ELE: Invalid element %d\n", type2); + break; + } + if(sd->state.lr_flag == 2) + break; + sd->weapon_coma_ele[type2] += val; + sd->special_state.bonus_coma = 1; + break; + case SP_WEAPON_COMA_RACE: + if(sd->state.lr_flag == 2) + break; + sd->weapon_coma_race[type2] += val; + sd->special_state.bonus_coma = 1; + break; + case SP_WEAPON_ATK: + if(sd->state.lr_flag != 2) + sd->weapon_atk[type2]+=val; + break; + case SP_WEAPON_ATK_RATE: + if(sd->state.lr_flag != 2) + sd->weapon_atk_rate[type2]+=val; + break; + case SP_CRITICAL_ADDRACE: + if(sd->state.lr_flag != 2) + sd->critaddrace[type2] += val*10; + break; + case SP_ADDEFF_WHENHIT: + if (type2 > SC_MAX) { + ShowWarning("pc_bonus2 (Add Effect when hit): %d is not supported.\n", type2); + break; + } + if(sd->state.lr_flag != 2) + pc_bonus_addeff(sd->addeff2, ARRAYLENGTH(sd->addeff2), (sc_type)type2, val, 0, 0); + break; + case SP_SKILL_ATK: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillatk), i, sd->skillatk[i].id == 0 || sd->skillatk[i].id == type2); + if (i == ARRAYLENGTH(sd->skillatk)) + { //Better mention this so the array length can be updated. [Skotlex] + ShowDebug("run_script: bonus2 bSkillAtk reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillatk), type2, val); + break; + } + if (sd->skillatk[i].id == type2) + sd->skillatk[i].val += val; + else { + sd->skillatk[i].id = type2; + sd->skillatk[i].val = val; + } + break; + case SP_SKILL_HEAL: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillheal), i, sd->skillheal[i].id == 0 || sd->skillheal[i].id == type2); + if (i == ARRAYLENGTH(sd->skillheal)) + { // Better mention this so the array length can be updated. [Skotlex] + ShowDebug("run_script: bonus2 bSkillHeal reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillheal), type2, val); + break; + } + if (sd->skillheal[i].id == type2) + sd->skillheal[i].val += val; + else { + sd->skillheal[i].id = type2; + sd->skillheal[i].val = val; + } + break; + case SP_SKILL_HEAL2: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillheal2), i, sd->skillheal2[i].id == 0 || sd->skillheal2[i].id == type2); + if (i == ARRAYLENGTH(sd->skillheal2)) + { // Better mention this so the array length can be updated. [Skotlex] + ShowDebug("run_script: bonus2 bSkillHeal2 reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillheal2), type2, val); + break; + } + if (sd->skillheal2[i].id == type2) + sd->skillheal2[i].val += val; + else { + sd->skillheal2[i].id = type2; + sd->skillheal2[i].val = val; + } + break; + case SP_ADD_SKILL_BLOW: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillblown), i, sd->skillblown[i].id == 0 || sd->skillblown[i].id == type2); + if (i == ARRAYLENGTH(sd->skillblown)) + { //Better mention this so the array length can be updated. [Skotlex] + ShowDebug("run_script: bonus2 bSkillBlown reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillblown), type2, val); + break; + } + if(sd->skillblown[i].id == type2) + sd->skillblown[i].val += val; + else { + sd->skillblown[i].id = type2; + sd->skillblown[i].val = val; + } + break; +#ifndef RENEWAL_CAST + case SP_VARCASTRATE: +#endif + case SP_CASTRATE: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillcast), i, sd->skillcast[i].id == 0 || sd->skillcast[i].id == type2); + if (i == ARRAYLENGTH(sd->skillcast)) + { //Better mention this so the array length can be updated. [Skotlex] + ShowDebug("run_script: bonus2 bCastRate reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillcast), type2, val); + break; + } + if(sd->skillcast[i].id == type2) + sd->skillcast[i].val += val; + else { + sd->skillcast[i].id = type2; + sd->skillcast[i].val = val; + } + break; + + case SP_HP_LOSS_RATE: + if(sd->state.lr_flag != 2) { + sd->hp_loss.value = type2; + sd->hp_loss.rate = val; + } + break; + case SP_HP_REGEN_RATE: + if(sd->state.lr_flag != 2) { + sd->hp_regen.value = type2; + sd->hp_regen.rate = val; + } + break; + case SP_ADDRACE2: + if (!(type2 > RC2_NONE && type2 < RC2_MAX)) + break; + if(sd->state.lr_flag != 2) + sd->right_weapon.addrace2[type2] += val; + else + sd->left_weapon.addrace2[type2] += val; + break; + case SP_SUBSIZE: + if(sd->state.lr_flag != 2) + sd->subsize[type2]+=val; + break; + case SP_SUBRACE2: + if (!(type2 > RC2_NONE && type2 < RC2_MAX)) + break; + if(sd->state.lr_flag != 2) + sd->subrace2[type2]+=val; + break; + case SP_ADD_ITEM_HEAL_RATE: + if(sd->state.lr_flag == 2) + break; + if (type2 < MAX_ITEMGROUP) { //Group bonus + sd->itemgrouphealrate[type2] += val; + break; + } + //Standard item bonus. + for(i=0; i < ARRAYLENGTH(sd->itemhealrate) && sd->itemhealrate[i].nameid && sd->itemhealrate[i].nameid != type2; i++); + if(i == ARRAYLENGTH(sd->itemhealrate)) { + ShowWarning("pc_bonus2: Reached max (%d) number of item heal bonuses per character!\n", ARRAYLENGTH(sd->itemhealrate)); + break; + } + sd->itemhealrate[i].nameid = type2; + sd->itemhealrate[i].rate += val; + break; + case SP_EXP_ADDRACE: + if(sd->state.lr_flag != 2) + sd->expaddrace[type2]+=val; + break; + case SP_SP_GAIN_RACE: + if(sd->state.lr_flag != 2) + sd->sp_gain_race[type2]+=val; + break; + case SP_ADD_MONSTER_DROP_ITEM: + if (sd->state.lr_flag != 2) + pc_bonus_item_drop(sd->add_drop, ARRAYLENGTH(sd->add_drop), type2, 0, (1<<RC_BOSS)|(1<<RC_NONBOSS), val); + break; + case SP_ADD_MONSTER_DROP_ITEMGROUP: + if (sd->state.lr_flag != 2) + pc_bonus_item_drop(sd->add_drop, ARRAYLENGTH(sd->add_drop), 0, type2, (1<<RC_BOSS)|(1<<RC_NONBOSS), val); + break; + case SP_SP_LOSS_RATE: + if(sd->state.lr_flag != 2) { + sd->sp_loss.value = type2; + sd->sp_loss.rate = val; + } + break; + case SP_SP_REGEN_RATE: + if(sd->state.lr_flag != 2) { + sd->sp_regen.value = type2; + sd->sp_regen.rate = val; + } + break; + case SP_HP_DRAIN_VALUE_RACE: + if(!sd->state.lr_flag) { + sd->right_weapon.hp_drain[type2].value += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.hp_drain[type2].value += val; + } + break; + case SP_SP_DRAIN_VALUE_RACE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[type2].value += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[type2].value += val; + } + break; + case SP_IGNORE_MDEF_RATE: + if(sd->state.lr_flag != 2) + sd->ignore_mdef[type2] += val; + break; + case SP_IGNORE_DEF_RATE: + if(sd->state.lr_flag != 2) + sd->ignore_def[type2] += val; + break; + case SP_SP_GAIN_RACE_ATTACK: + if(sd->state.lr_flag != 2) + sd->sp_gain_race_attack[type2] = cap_value(sd->sp_gain_race_attack[type2] + val, 0, INT16_MAX); + break; + case SP_HP_GAIN_RACE_ATTACK: + if(sd->state.lr_flag != 2) + sd->hp_gain_race_attack[type2] = cap_value(sd->hp_gain_race_attack[type2] + val, 0, INT16_MAX); + break; + case SP_SKILL_USE_SP_RATE: //bonus2 bSkillUseSPrate,n,x; + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillusesprate), i, sd->skillusesprate[i].id == 0 || sd->skillusesprate[i].id == type2); + if (i == ARRAYLENGTH(sd->skillusesprate)) { + ShowDebug("run_script: bonus2 bSkillUseSPrate reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillusesprate), type2, val); + break; + } + if (sd->skillusesprate[i].id == type2) + sd->skillusesprate[i].val += val; + else { + sd->skillusesprate[i].id = type2; + sd->skillusesprate[i].val = val; + } + break; + case SP_SKILL_COOLDOWN: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillcooldown), i, sd->skillcooldown[i].id == 0 || sd->skillcooldown[i].id == type2); + if (i == ARRAYLENGTH(sd->skillcooldown)) + { + ShowDebug("run_script: bonus2 bSkillCoolDown reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillcooldown), type2, val); + break; + } + if (sd->skillcooldown[i].id == type2) + sd->skillcooldown[i].val += val; + else { + sd->skillcooldown[i].id = type2; + sd->skillcooldown[i].val = val; + } + break; + case SP_SKILL_FIXEDCAST: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillfixcast), i, sd->skillfixcast[i].id == 0 || sd->skillfixcast[i].id == type2); + if (i == ARRAYLENGTH(sd->skillfixcast)) + { + ShowDebug("run_script: bonus2 bSkillFixedCast reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillfixcast), type2, val); + break; + } + if (sd->skillfixcast[i].id == type2) + sd->skillfixcast[i].val += val; + else { + sd->skillfixcast[i].id = type2; + sd->skillfixcast[i].val = val; + } + break; + case SP_SKILL_VARIABLECAST: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillvarcast), i, sd->skillvarcast[i].id == 0 || sd->skillvarcast[i].id == type2); + if (i == ARRAYLENGTH(sd->skillvarcast)) + { + ShowDebug("run_script: bonus2 bSkillVariableCast reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillvarcast), type2, val); + break; + } + if (sd->skillvarcast[i].id == type2) + sd->skillvarcast[i].val += val; + else { + sd->skillvarcast[i].id = type2; + sd->skillvarcast[i].val = val; + } + break; +#ifdef RENEWAL_CAST + case SP_VARCASTRATE: + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillcast), i, sd->skillcast[i].id == 0 || sd->skillcast[i].id == type2); + if (i == ARRAYLENGTH(sd->skillcast)) + { + ShowDebug("run_script: bonus2 bVariableCastrate reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n",ARRAYLENGTH(sd->skillcast), type2, val); + break; + } + if(sd->skillcast[i].id == type2) + sd->skillcast[i].val -= val; + else { + sd->skillcast[i].id = type2; + sd->skillcast[i].val -= val; + } + break; +#endif + case SP_SKILL_USE_SP: //bonus2 bSkillUseSP,n,x; + if(sd->state.lr_flag == 2) + break; + ARR_FIND(0, ARRAYLENGTH(sd->skillusesp), i, sd->skillusesp[i].id == 0 || sd->skillusesp[i].id == type2); + if (i == ARRAYLENGTH(sd->skillusesp)) { + ShowDebug("run_script: bonus2 bSkillUseSP reached it's limit (%d skills per character), bonus skill %d (+%d%%) lost.\n", ARRAYLENGTH(sd->skillusesp), type2, val); + break; + } + if (sd->skillusesp[i].id == type2) + sd->skillusesp[i].val += val; + else { + sd->skillusesp[i].id = type2; + sd->skillusesp[i].val = val; + } + break; + default: + ShowWarning("pc_bonus2: unknown type %d %d %d!\n",type,type2,val); + break; + } + return 0; +} + +int pc_bonus3(struct map_session_data *sd,int type,int type2,int type3,int val) +{ + nullpo_ret(sd); + + switch(type){ + case SP_ADD_MONSTER_DROP_ITEM: + if(sd->state.lr_flag != 2) + pc_bonus_item_drop(sd->add_drop, ARRAYLENGTH(sd->add_drop), type2, 0, 1<<type3, val); + break; + case SP_ADD_CLASS_DROP_ITEM: + if(sd->state.lr_flag != 2) + pc_bonus_item_drop(sd->add_drop, ARRAYLENGTH(sd->add_drop), type2, 0, -type3, val); + break; + case SP_AUTOSPELL: + if(sd->state.lr_flag != 2) + { + int target = skill_get_inf(type2); //Support or Self (non-auto-target) skills should pick self. + target = target&INF_SUPPORT_SKILL || (target&INF_SELF_SKILL && !(skill_get_inf2(type2)&INF2_NO_TARGET_SELF)); + pc_bonus_autospell(sd->autospell, ARRAYLENGTH(sd->autospell), + target?-type2:type2, type3, val, 0, current_equip_card_id); + } + break; + case SP_AUTOSPELL_WHENHIT: + if(sd->state.lr_flag != 2) + { + int target = skill_get_inf(type2); //Support or Self (non-auto-target) skills should pick self. + target = target&INF_SUPPORT_SKILL || (target&INF_SELF_SKILL && !(skill_get_inf2(type2)&INF2_NO_TARGET_SELF)); + pc_bonus_autospell(sd->autospell2, ARRAYLENGTH(sd->autospell2), + target?-type2:type2, type3, val, BF_NORMAL|BF_SKILL, current_equip_card_id); + } + break; + case SP_SP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[RC_NONBOSS].rate += type2; + sd->right_weapon.sp_drain[RC_NONBOSS].per += type3; + sd->right_weapon.sp_drain[RC_NONBOSS].type = val; + sd->right_weapon.sp_drain[RC_BOSS].rate += type2; + sd->right_weapon.sp_drain[RC_BOSS].per += type3; + sd->right_weapon.sp_drain[RC_BOSS].type = val; + + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[RC_NONBOSS].rate += type2; + sd->left_weapon.sp_drain[RC_NONBOSS].per += type3; + sd->left_weapon.sp_drain[RC_NONBOSS].type = val; + sd->left_weapon.sp_drain[RC_BOSS].rate += type2; + sd->left_weapon.sp_drain[RC_BOSS].per += type3; + sd->left_weapon.sp_drain[RC_BOSS].type = val; + } + break; + case SP_HP_DRAIN_RATE_RACE: + if(!sd->state.lr_flag) { + sd->right_weapon.hp_drain[type2].rate += type3; + sd->right_weapon.hp_drain[type2].per += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.hp_drain[type2].rate += type3; + sd->left_weapon.hp_drain[type2].per += val; + } + break; + case SP_SP_DRAIN_RATE_RACE: + if(!sd->state.lr_flag) { + sd->right_weapon.sp_drain[type2].rate += type3; + sd->right_weapon.sp_drain[type2].per += val; + } + else if(sd->state.lr_flag == 1) { + sd->left_weapon.sp_drain[type2].rate += type3; + sd->left_weapon.sp_drain[type2].per += val; + } + break; + case SP_ADD_MONSTER_DROP_ITEMGROUP: + if (sd->state.lr_flag != 2) + pc_bonus_item_drop(sd->add_drop, ARRAYLENGTH(sd->add_drop), 0, type2, 1<<type3, val); + break; + + case SP_ADDEFF: + if (type2 > SC_MAX) { + ShowWarning("pc_bonus3 (Add Effect): %d is not supported.\n", type2); + break; + } + pc_bonus_addeff(sd->addeff, ARRAYLENGTH(sd->addeff), (sc_type)type2, + sd->state.lr_flag!=2?type3:0, sd->state.lr_flag==2?type3:0, val); + break; + + case SP_ADDEFF_WHENHIT: + if (type2 > SC_MAX) { + ShowWarning("pc_bonus3 (Add Effect when hit): %d is not supported.\n", type2); + break; + } + if(sd->state.lr_flag != 2) + pc_bonus_addeff(sd->addeff2, ARRAYLENGTH(sd->addeff2), (sc_type)type2, type3, 0, val); + break; + + case SP_ADDEFF_ONSKILL: + if( type3 > SC_MAX ) { + ShowWarning("pc_bonus3 (Add Effect on skill): %d is not supported.\n", type3); + break; + } + if( sd->state.lr_flag != 2 ) + pc_bonus_addeff_onskill(sd->addeff3, ARRAYLENGTH(sd->addeff3), (sc_type)type3, val, type2, ATF_TARGET); + break; + + case SP_ADDELE: + if (type2 > ELE_MAX) { + ShowWarning("pc_bonus3 (SP_ADDELE): element %d is out of range.\n", type2); + break; + } + if (sd->state.lr_flag != 2) + pc_bonus_addele(sd, (unsigned char)type2, type3, val); + break; + + case SP_SUBELE: + if (type2 > ELE_MAX) { + ShowWarning("pc_bonus3 (SP_SUBELE): element %d is out of range.\n", type2); + break; + } + if (sd->state.lr_flag != 2) + pc_bonus_subele(sd, (unsigned char)type2, type3, val); + break; + + default: + ShowWarning("pc_bonus3: unknown type %d %d %d %d!\n",type,type2,type3,val); + break; + } + + return 0; +} + +int pc_bonus4(struct map_session_data *sd,int type,int type2,int type3,int type4,int val) +{ + nullpo_ret(sd); + + switch(type){ + case SP_AUTOSPELL: + if(sd->state.lr_flag != 2) + pc_bonus_autospell(sd->autospell, ARRAYLENGTH(sd->autospell), (val&1?type2:-type2), (val&2?-type3:type3), type4, 0, current_equip_card_id); + break; + + case SP_AUTOSPELL_WHENHIT: + if(sd->state.lr_flag != 2) + pc_bonus_autospell(sd->autospell2, ARRAYLENGTH(sd->autospell2), (val&1?type2:-type2), (val&2?-type3:type3), type4, BF_NORMAL|BF_SKILL, current_equip_card_id); + break; + + case SP_AUTOSPELL_ONSKILL: + if(sd->state.lr_flag != 2) + { + int target = skill_get_inf(type2); //Support or Self (non-auto-target) skills should pick self. + target = target&INF_SUPPORT_SKILL || (target&INF_SELF_SKILL && !(skill_get_inf2(type2)&INF2_NO_TARGET_SELF)); + + pc_bonus_autospell_onskill(sd->autospell3, ARRAYLENGTH(sd->autospell3), type2, target?-type3:type3, type4, val, current_equip_card_id); + } + break; + + case SP_ADDEFF_ONSKILL: + if( type2 > SC_MAX ) { + ShowWarning("pc_bonus3 (Add Effect on skill): %d is not supported.\n", type2); + break; + } + if( sd->state.lr_flag != 2 ) + pc_bonus_addeff_onskill(sd->addeff3, ARRAYLENGTH(sd->addeff3), (sc_type)type3, type4, type2, val); + break; + + default: + ShowWarning("pc_bonus4: unknown type %d %d %d %d %d!\n",type,type2,type3,type4,val); + break; + } + + return 0; +} + +int pc_bonus5(struct map_session_data *sd,int type,int type2,int type3,int type4,int type5,int val) +{ + nullpo_ret(sd); + + switch(type){ + case SP_AUTOSPELL: + if(sd->state.lr_flag != 2) + pc_bonus_autospell(sd->autospell, ARRAYLENGTH(sd->autospell), (val&1?type2:-type2), (val&2?-type3:type3), type4, type5, current_equip_card_id); + break; + + case SP_AUTOSPELL_WHENHIT: + if(sd->state.lr_flag != 2) + pc_bonus_autospell(sd->autospell2, ARRAYLENGTH(sd->autospell2), (val&1?type2:-type2), (val&2?-type3:type3), type4, type5, current_equip_card_id); + break; + + case SP_AUTOSPELL_ONSKILL: + if(sd->state.lr_flag != 2) + pc_bonus_autospell_onskill(sd->autospell3, ARRAYLENGTH(sd->autospell3), type2, (val&1?-type3:type3), (val&2?-type4:type4), type5, current_equip_card_id); + break; + + default: + ShowWarning("pc_bonus5: unknown type %d %d %d %d %d %d!\n",type,type2,type3,type4,type5,val); + break; + } + + return 0; +} + +/*========================================== + * Grants a player a given skill. Flag values are: + * 0 - Grant skill unconditionally and forever (only this one invokes status_calc_pc, + * as the other two are assumed to be invoked from within it) + * 1 - Grant an item skill (temporary) + * 2 - Like 1, except the level granted can stack with previously learned level. + *------------------------------------------*/ +int pc_skill(TBL_PC* sd, int id, int level, int flag) +{ + nullpo_ret(sd); + + if( id <= 0 || id >= MAX_SKILL || skill_db[id].name == NULL) { + ShowError("pc_skill: Skill with id %d does not exist in the skill database\n", id); + return 0; + } + if( level > MAX_SKILL_LEVEL ) { + ShowError("pc_skill: Skill level %d too high. Max lv supported is %d\n", level, MAX_SKILL_LEVEL); + return 0; + } + if( flag == 2 && sd->status.skill[id].lv + level > MAX_SKILL_LEVEL ) { + ShowError("pc_skill: Skill level bonus %d too high. Max lv supported is %d. Curr lv is %d\n", level, MAX_SKILL_LEVEL, sd->status.skill[id].lv); + return 0; + } + + switch( flag ){ + case 0: //Set skill data overwriting whatever was there before. + sd->status.skill[id].id = id; + sd->status.skill[id].lv = level; + sd->status.skill[id].flag = SKILL_FLAG_PERMANENT; + if( level == 0 ) //Remove skill. + { + sd->status.skill[id].id = 0; + clif_deleteskill(sd,id); + } + else + clif_addskill(sd,id); + if( !skill_get_inf(id) ) //Only recalculate for passive skills. + status_calc_pc(sd, 0); + break; + case 1: //Item bonus skill. + if( sd->status.skill[id].id == id ){ + if( sd->status.skill[id].lv >= level ) + return 0; + if( sd->status.skill[id].flag == SKILL_FLAG_PERMANENT ) //Non-granted skill, store it's level. + sd->status.skill[id].flag = SKILL_FLAG_REPLACED_LV_0 + sd->status.skill[id].lv; + } else { + sd->status.skill[id].id = id; + sd->status.skill[id].flag = SKILL_FLAG_TEMPORARY; + } + sd->status.skill[id].lv = level; + break; + case 2: //Add skill bonus on top of what you had. + if( sd->status.skill[id].id == id ){ + if( sd->status.skill[id].flag == SKILL_FLAG_PERMANENT ) + sd->status.skill[id].flag = SKILL_FLAG_REPLACED_LV_0 + sd->status.skill[id].lv; // Store previous level. + } else { + sd->status.skill[id].id = id; + sd->status.skill[id].flag = SKILL_FLAG_TEMPORARY; //Set that this is a bonus skill. + } + sd->status.skill[id].lv += level; + break; + default: //Unknown flag? + return 0; + } + return 1; +} +/*========================================== + * Append a card to an item ? + *------------------------------------------*/ +int pc_insert_card(struct map_session_data* sd, int idx_card, int idx_equip) +{ + int i; + int nameid; + + nullpo_ret(sd); + + if( idx_equip < 0 || idx_equip >= MAX_INVENTORY || sd->inventory_data[idx_equip] == NULL ) + return 0; //Invalid item index. + if( idx_card < 0 || idx_card >= MAX_INVENTORY || sd->inventory_data[idx_card] == NULL ) + return 0; //Invalid card index. + if( sd->status.inventory[idx_equip].nameid <= 0 || sd->status.inventory[idx_equip].amount < 1 ) + return 0; // target item missing + if( sd->status.inventory[idx_card].nameid <= 0 || sd->status.inventory[idx_card].amount < 1 ) + return 0; // target card missing + if( sd->inventory_data[idx_equip]->type != IT_WEAPON && sd->inventory_data[idx_equip]->type != IT_ARMOR ) + return 0; // only weapons and armor are allowed + if( sd->inventory_data[idx_card]->type != IT_CARD ) + return 0; // must be a card + if( sd->status.inventory[idx_equip].identify == 0 ) + return 0; // target must be identified + if( itemdb_isspecial(sd->status.inventory[idx_equip].card[0]) ) + return 0; // card slots reserved for other purposes + if( (sd->inventory_data[idx_equip]->equip & sd->inventory_data[idx_card]->equip) == 0 ) + return 0; // card cannot be compounded on this item type + if( sd->inventory_data[idx_equip]->type == IT_WEAPON && sd->inventory_data[idx_card]->equip == EQP_SHIELD ) + return 0; // attempted to place shield card on left-hand weapon. + if( sd->status.inventory[idx_equip].equip != 0 ) + return 0; // item must be unequipped + + ARR_FIND( 0, sd->inventory_data[idx_equip]->slot, i, sd->status.inventory[idx_equip].card[i] == 0 ); + if( i == sd->inventory_data[idx_equip]->slot ) + return 0; // no free slots + + // remember the card id to insert + nameid = sd->status.inventory[idx_card].nameid; + + if( pc_delitem(sd,idx_card,1,1,0,LOG_TYPE_OTHER) == 1 ) + {// failed + clif_insert_card(sd,idx_equip,idx_card,1); + } + else + {// success + log_pick_pc(sd, LOG_TYPE_OTHER, -1, &sd->status.inventory[idx_equip]); + sd->status.inventory[idx_equip].card[i] = nameid; + log_pick_pc(sd, LOG_TYPE_OTHER, 1, &sd->status.inventory[idx_equip]); + clif_insert_card(sd,idx_equip,idx_card,0); + } + + return 0; +} + +// +// Items +// + +/*========================================== + * Update buying value by skills + *------------------------------------------*/ +int pc_modifybuyvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate1 = 0,rate2 = 0; + if((skill=pc_checkskill(sd,MC_DISCOUNT))>0) // merchant discount + rate1 = 5+skill*2-((skill==10)? 1:0); + if((skill=pc_checkskill(sd,RG_COMPULSION))>0) // rogue discount + rate2 = 5+skill*4; + if(rate1 < rate2) rate1 = rate2; + if(rate1) + val = (int)((double)orig_value*(double)(100-rate1)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * Update selling value by skills + *------------------------------------------*/ +int pc_modifysellvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate = 0; + if((skill=pc_checkskill(sd,MC_OVERCHARGE))>0) //OverCharge + rate = 5+skill*2-((skill==10)? 1:0); + if(rate) + val = (int)((double)orig_value*(double)(100+rate)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * Checking if we have enough place on inventory for new item + * Make sure to take 30k as limit (for client I guess) + *------------------------------------------*/ +int pc_checkadditem(struct map_session_data *sd,int nameid,int amount) +{ + int i; + struct item_data* data; + + nullpo_ret(sd); + + if(amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + + data = itemdb_search(nameid); + + if(!itemdb_isstackable2(data)) + return ADDITEM_NEW; + + if( data->stack.inventory && amount > data->stack.amount ) + return ADDITEM_OVERAMOUNT; + + for(i=0;i<MAX_INVENTORY;i++){ + // FIXME: This does not consider the checked item's cards, thus could check a wrong slot for stackability. + if(sd->status.inventory[i].nameid==nameid){ + if( amount > MAX_AMOUNT - sd->status.inventory[i].amount || ( data->stack.inventory && amount > data->stack.amount - sd->status.inventory[i].amount ) ) + return ADDITEM_OVERAMOUNT; + return ADDITEM_EXIST; + } + } + + return ADDITEM_NEW; +} + +/*========================================== + * Return number of available place in inventory + * Each non stackable item will reduce place by 1 + *------------------------------------------*/ +int pc_inventoryblank(struct map_session_data *sd) +{ + int i,b; + + nullpo_ret(sd); + + for(i=0,b=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0) + b++; + } + + return b; +} + +/*========================================== + * attempts to remove zeny from player (sd) + *------------------------------------------*/ +int pc_payzeny(struct map_session_data *sd,int zeny, enum e_log_pick_type type, struct map_session_data *tsd) +{ + nullpo_retr(-1,sd); + + zeny = cap_value(zeny,-MAX_ZENY,MAX_ZENY); //prevent command UB + if( zeny < 0 ) + { + ShowError("pc_payzeny: Paying negative Zeny (zeny=%d, account_id=%d, char_id=%d).\n", zeny, sd->status.account_id, sd->status.char_id); + return 1; + } + + if( sd->status.zeny < zeny ) + return 1; //Not enough. + + sd->status.zeny -= zeny; + clif_updatestatus(sd,SP_ZENY); + + if(!tsd) tsd = sd; + log_zeny(sd, type, tsd, -zeny); + if( zeny > 0 && sd->state.showzeny ) { + char output[255]; + sprintf(output, "Removed %dz.", zeny); + clif_disp_onlyself(sd,output,strlen(output)); + } + + return 0; +} +/*========================================== + * Cash Shop + *------------------------------------------*/ + +int pc_paycash(struct map_session_data *sd, int price, int points) +{ + char output[128]; + int cash; + nullpo_retr(-1,sd); + + points = cap_value(points,-MAX_ZENY,MAX_ZENY); //prevent command UB + if( price < 0 || points < 0 ) + { + ShowError("pc_paycash: Paying negative points (price=%d, points=%d, account_id=%d, char_id=%d).\n", price, points, sd->status.account_id, sd->status.char_id); + return -2; + } + + if( points > price ) + { + ShowWarning("pc_paycash: More kafra points provided than needed (price=%d, points=%d, account_id=%d, char_id=%d).\n", price, points, sd->status.account_id, sd->status.char_id); + points = price; + } + + cash = price-points; + + if( sd->cashPoints < cash || sd->kafraPoints < points ) + { + ShowError("pc_paycash: Not enough points (cash=%d, kafra=%d) to cover the price (cash=%d, kafra=%d) (account_id=%d, char_id=%d).\n", sd->cashPoints, sd->kafraPoints, cash, points, sd->status.account_id, sd->status.char_id); + return -1; + } + + pc_setaccountreg(sd, "#CASHPOINTS", sd->cashPoints-cash); + pc_setaccountreg(sd, "#KAFRAPOINTS", sd->kafraPoints-points); + + if( battle_config.cashshop_show_points ) + { + sprintf(output, msg_txt(504), points, cash, sd->kafraPoints, sd->cashPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + return cash+points; +} + +int pc_getcash(struct map_session_data *sd, int cash, int points) +{ + char output[128]; + nullpo_retr(-1,sd); + + cash = cap_value(cash,-MAX_ZENY,MAX_ZENY); //prevent command UB + points = cap_value(points,-MAX_ZENY,MAX_ZENY); //prevent command UB + if( cash > 0 ) + { + if( cash > MAX_ZENY-sd->cashPoints ) + { + ShowWarning("pc_getcash: Cash point overflow (cash=%d, have cash=%d, account_id=%d, char_id=%d).\n", cash, sd->cashPoints, sd->status.account_id, sd->status.char_id); + cash = MAX_ZENY-sd->cashPoints; + } + + pc_setaccountreg(sd, "#CASHPOINTS", sd->cashPoints+cash); + + if( battle_config.cashshop_show_points ) + { + sprintf(output, msg_txt(505), cash, sd->cashPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + return cash; + } + else if( cash < 0 ) + { + ShowError("pc_getcash: Obtaining negative cash points (cash=%d, account_id=%d, char_id=%d).\n", cash, sd->status.account_id, sd->status.char_id); + return -1; + } + + if( points > 0 ) + { + if( points > MAX_ZENY-sd->kafraPoints ) + { + ShowWarning("pc_getcash: Kafra point overflow (points=%d, have points=%d, account_id=%d, char_id=%d).\n", points, sd->kafraPoints, sd->status.account_id, sd->status.char_id); + points = MAX_ZENY-sd->kafraPoints; + } + + pc_setaccountreg(sd, "#KAFRAPOINTS", sd->kafraPoints+points); + + if( battle_config.cashshop_show_points ) + { + sprintf(output, msg_txt(506), points, sd->kafraPoints); + clif_disp_onlyself(sd, output, strlen(output)); + } + return points; + } + else if( points < 0 ) + { + ShowError("pc_getcash: Obtaining negative kafra points (points=%d, account_id=%d, char_id=%d).\n", points, sd->status.account_id, sd->status.char_id); + return -1; + } + return -2; //shouldn't happen but jsut in case +} + +/*========================================== + * Attempts to give zeny to player (sd) + * tsd (optional) from who for log (if null take sd) + *------------------------------------------*/ +int pc_getzeny(struct map_session_data *sd,int zeny, enum e_log_pick_type type, struct map_session_data *tsd) +{ + nullpo_retr(-1,sd); + + zeny = cap_value(zeny,-MAX_ZENY,MAX_ZENY); //prevent command UB + if( zeny < 0 ) + { + ShowError("pc_getzeny: Obtaining negative Zeny (zeny=%d, account_id=%d, char_id=%d).\n", zeny, sd->status.account_id, sd->status.char_id); + return 1; + } + + if( zeny > MAX_ZENY - sd->status.zeny ) + zeny = MAX_ZENY - sd->status.zeny; + + sd->status.zeny += zeny; + clif_updatestatus(sd,SP_ZENY); + + if(!tsd) tsd = sd; + log_zeny(sd, type, tsd, zeny); + if( zeny > 0 && sd->state.showzeny ) { + char output[255]; + sprintf(output, "Gained %dz.", zeny); + clif_disp_onlyself(sd,output,strlen(output)); + } + + return 0; +} + +/*========================================== + * Searching a specified itemid in inventory and return his stored index + *------------------------------------------*/ +int pc_search_inventory(struct map_session_data *sd,int item_id) +{ + int i; + nullpo_retr(-1, sd); + + ARR_FIND( 0, MAX_INVENTORY, i, sd->status.inventory[i].nameid == item_id && (sd->status.inventory[i].amount > 0 || item_id == 0) ); + return ( i < MAX_INVENTORY ) ? i : -1; +} + +/*========================================== + * Attempt to add a new item to inventory. + * Return: + 0 = success + 1 = invalid itemid not found or negative amount + 2 = overweight + 3 = ? + 4 = no free place found + 5 = max amount reached + 6 = ? + 7 = stack limitation + *------------------------------------------*/ +int pc_additem(struct map_session_data *sd,struct item *item_data,int amount,e_log_pick_type log_type) +{ + struct item_data *data; + int i; + unsigned int w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if( item_data->nameid <= 0 || amount <= 0 ) + return 1; + if( amount > MAX_AMOUNT ) + return 5; + + data = itemdb_search(item_data->nameid); + + if( data->stack.inventory && amount > data->stack.amount ) + {// item stack limitation + return 7; + } + + w = data->weight*amount; + if(sd->weight + w > sd->max_weight) + return 2; + + i = MAX_INVENTORY; + + if( itemdb_isstackable2(data) && item_data->expire_time == 0 ) + { // Stackable | Non Rental + for( i = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid == item_data->nameid && memcmp(&sd->status.inventory[i].card, &item_data->card, sizeof(item_data->card)) == 0 ) + { + if( amount > MAX_AMOUNT - sd->status.inventory[i].amount || ( data->stack.inventory && amount > data->stack.amount - sd->status.inventory[i].amount ) ) + return 5; + sd->status.inventory[i].amount += amount; + clif_additem(sd,i,amount,0); + break; + } + } + } + + if( i >= MAX_INVENTORY ) + { + i = pc_search_inventory(sd,0); + if( i < 0 ) + return 4; + + memcpy(&sd->status.inventory[i], item_data, sizeof(sd->status.inventory[0])); + // clear equips field first, just in case + if( item_data->equip ) + sd->status.inventory[i].equip = 0; + + sd->status.inventory[i].amount = amount; + sd->inventory_data[i] = data; + clif_additem(sd,i,amount,0); + } +#ifdef NSI_UNIQUE_ID + if( !itemdb_isstackable2(data) && !item_data->unique_id ) + sd->status.inventory[i].unique_id = itemdb_unique_id(0,0); +#endif + log_pick_pc(sd, log_type, amount, &sd->status.inventory[i]); + + sd->weight += w; + clif_updatestatus(sd,SP_WEIGHT); + //Auto-equip + if(data->flag.autoequip) + pc_equipitem(sd, i, data->equip); + + /* rental item check */ + if( item_data->expire_time ) { + if( time(NULL) > item_data->expire_time ) { + clif_rental_expired(sd->fd, i, sd->status.inventory[i].nameid); + pc_delitem(sd, i, sd->status.inventory[i].amount, 1, 0, LOG_TYPE_OTHER); + } else { + int seconds = (int)( item_data->expire_time - time(NULL) ); + clif_rental_time(sd->fd, sd->status.inventory[i].nameid, seconds); + pc_inventory_rental_add(sd, seconds); + } + } + + return 0; +} + +/*========================================== + * Remove an item at index n from inventory by amount. + * Parameters : + * @type + * 1 : don't notify deletion + * 2 : don't notify weight change + * Return: + * 0 = success + * 1 = invalid itemid or negative amount + *------------------------------------------*/ +int pc_delitem(struct map_session_data *sd,int n,int amount,int type, short reason, e_log_pick_type log_type) +{ + nullpo_retr(1, sd); + + if(sd->status.inventory[n].nameid==0 || amount <= 0 || sd->status.inventory[n].amount<amount || sd->inventory_data[n] == NULL) + return 1; + + log_pick_pc(sd, log_type, -amount, &sd->status.inventory[n]); + + sd->status.inventory[n].amount -= amount; + sd->weight -= sd->inventory_data[n]->weight*amount ; + if( sd->status.inventory[n].amount <= 0 ){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,3); + memset(&sd->status.inventory[n],0,sizeof(sd->status.inventory[0])); + sd->inventory_data[n] = NULL; + } + if(!(type&1)) + clif_delitem(sd,n,amount,reason); + if(!(type&2)) + clif_updatestatus(sd,SP_WEIGHT); + + return 0; +} + +/*========================================== + * Attempt to drop an item. + * Return: + * 0 = fail + * 1 = success + *------------------------------------------*/ +int pc_dropitem(struct map_session_data *sd,int n,int amount) +{ + nullpo_retr(1, sd); + + if(n < 0 || n >= MAX_INVENTORY) + return 0; + + if(amount <= 0) + return 0; + + if(sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount <= 0 || + sd->status.inventory[n].amount < amount || + sd->state.trading || sd->state.vending || + !sd->inventory_data[n] //pc_delitem would fail on this case. + ) + return 0; + + if( map[sd->bl.m].flag.nodrop ) + { + clif_displaymessage (sd->fd, msg_txt(271)); + return 0; //Can't drop items in nodrop mapflag maps. + } + + if( !pc_candrop(sd,&sd->status.inventory[n]) ) + { + clif_displaymessage (sd->fd, msg_txt(263)); + return 0; + } + + if (!map_addflooritem(&sd->status.inventory[n], amount, sd->bl.m, sd->bl.x, sd->bl.y, 0, 0, 0, 2)) + return 0; + + pc_delitem(sd, n, amount, 1, 0, LOG_TYPE_PICKDROP_PLAYER); + clif_dropitem(sd, n, amount); + return 1; +} + +/*========================================== + * Attempt to pick up an item. + * Return: + * 0 = fail + * 1 = success + *------------------------------------------*/ +int pc_takeitem(struct map_session_data *sd,struct flooritem_data *fitem) +{ + int flag=0; + unsigned int tick = gettick(); + struct map_session_data *first_sd = NULL,*second_sd = NULL,*third_sd = NULL; + struct party_data *p=NULL; + + nullpo_ret(sd); + nullpo_ret(fitem); + + if(!check_distance_bl(&fitem->bl, &sd->bl, 2) && sd->ud.skill_id!=BS_GREED) + return 0; // Distance is too far + + if (sd->status.party_id) + p = party_search(sd->status.party_id); + + if(fitem->first_get_charid > 0 && fitem->first_get_charid != sd->status.char_id) + { + first_sd = map_charid2sd(fitem->first_get_charid); + if(DIFF_TICK(tick,fitem->first_get_tick) < 0) { + if (!(p && p->party.item&1 && + first_sd && first_sd->status.party_id == sd->status.party_id + )) + return 0; + } + else + if(fitem->second_get_charid > 0 && fitem->second_get_charid != sd->status.char_id) + { + second_sd = map_charid2sd(fitem->second_get_charid); + if(DIFF_TICK(tick, fitem->second_get_tick) < 0) { + if(!(p && p->party.item&1 && + ((first_sd && first_sd->status.party_id == sd->status.party_id) || + (second_sd && second_sd->status.party_id == sd->status.party_id)) + )) + return 0; + } + else + if(fitem->third_get_charid > 0 && fitem->third_get_charid != sd->status.char_id) + { + third_sd = map_charid2sd(fitem->third_get_charid); + if(DIFF_TICK(tick,fitem->third_get_tick) < 0) { + if(!(p && p->party.item&1 && + ((first_sd && first_sd->status.party_id == sd->status.party_id) || + (second_sd && second_sd->status.party_id == sd->status.party_id) || + (third_sd && third_sd->status.party_id == sd->status.party_id)) + )) + return 0; + } + } + } + } + + //This function takes care of giving the item to whoever should have it, considering party-share options. + if ((flag = party_share_loot(p,sd,&fitem->item_data, fitem->first_get_charid))) { + clif_additem(sd,0,0,flag); + return 1; + } + + //Display pickup animation. + pc_stop_attack(sd); + clif_takeitem(&sd->bl,&fitem->bl); + map_clearflooritem(&fitem->bl); + return 1; +} + +/*========================================== + * Check if item is usable. + * Return: + * 0 = no + * 1 = yes + *------------------------------------------*/ +int pc_isUseitem(struct map_session_data *sd,int n) +{ + struct item_data *item; + int nameid; + + nullpo_ret(sd); + + item = sd->inventory_data[n]; + nameid = sd->status.inventory[n].nameid; + + if( item == NULL ) + return 0; + //Not consumable item + if( item->type != IT_HEALING && item->type != IT_USABLE && item->type != IT_CASH ) + return 0; + if( !item->script ) //if it has no script, you can't really consume it! + return 0; + + switch( nameid ) //@TODO, lot oh harcoded nameid here + { + case 605: // Anodyne + if( map_flag_gvg(sd->bl.m) ) + return 0; + case 606: + if( pc_issit(sd) ) + return 0; + break; + case 601: // Fly Wing + case 12212: // Giant Fly Wing + if( map[sd->bl.m].flag.noteleport || map_flag_gvg(sd->bl.m) ) + { + clif_skill_teleportmessage(sd,0); + return 0; + } + case 602: // ButterFly Wing + case 14527: // Dungeon Teleport Scroll + case 14581: // Dungeon Teleport Scroll + case 14582: // Yellow Butterfly Wing + case 14583: // Green Butterfly Wing + case 14584: // Red Butterfly Wing + case 14585: // Blue Butterfly Wing + case 14591: // Siege Teleport Scroll + if( sd->duel_group && !battle_config.duel_allow_teleport ) + { + clif_displaymessage(sd->fd, msg_txt(663)); + return 0; + } + if( nameid != 601 && nameid != 12212 && map[sd->bl.m].flag.noreturn ) + return 0; + break; + case 604: // Dead Branch + case 12024: // Red Pouch + case 12103: // Bloody Branch + case 12109: // Poring Box + if( map[sd->bl.m].flag.nobranch || map_flag_gvg(sd->bl.m) ) + return 0; + break; + case 12210: // Bubble Gum + case 12264: // Comp Bubble Gum + if( sd->sc.data[SC_ITEMBOOST] ) + return 0; + break; + case 12208: // Battle Manual + case 12263: // Comp Battle Manual + case 12312: // Thick Battle Manual + case 12705: // Noble Nameplate + case 14532: // Battle_Manual25 + case 14533: // Battle_Manual100 + case 14545: // Battle_Manual300 + if( sd->sc.data[SC_EXPBOOST] ) + return 0; + break; + case 14592: // JOB_Battle_Manual + if( sd->sc.data[SC_JEXPBOOST] ) + return 0; + break; + + // Mercenary Items + + case 12184: // Mercenary's Red Potion + case 12185: // Mercenary's Blue Potion + case 12241: // Mercenary's Concentration Potion + case 12242: // Mercenary's Awakening Potion + case 12243: // Mercenary's Berserk Potion + if( sd->md == NULL || sd->md->db == NULL ) + return 0; + if (sd->md->sc.data[SC_BERSERK] || sd->md->sc.data[SC_SATURDAYNIGHTFEVER] || sd->md->sc.data[SC__BLOODYLUST]) + return 0; + if( nameid == 12242 && sd->md->db->lv < 40 ) + return 0; + if( nameid == 12243 && sd->md->db->lv < 80 ) + return 0; + break; + + case 12213: //Neuralizer + if( !map[sd->bl.m].flag.reset ) + return 0; + break; + } + + if( nameid >= 12153 && nameid <= 12182 && sd->md != NULL ) + return 0; // Mercenary Scrolls + + /** + * Only Rune Knights may use runes + **/ + if( itemdb_is_rune(nameid) && (sd->class_&MAPID_THIRDMASK) != MAPID_RUNE_KNIGHT ) + return 0; + /** + * Only GCross may use poisons + **/ + else if( itemdb_is_poison(nameid) && (sd->class_&MAPID_THIRDMASK) != MAPID_GUILLOTINE_CROSS ) + return 0; + + //added item_noequip.txt items check by Maya&[Lupus] + if ( + (!map_flag_vs(sd->bl.m) && item->flag.no_equip&1) || // Normal + (map[sd->bl.m].flag.pvp && item->flag.no_equip&2) || // PVP + (map_flag_gvg(sd->bl.m) && item->flag.no_equip&4) || // GVG + (map[sd->bl.m].flag.battleground && item->flag.no_equip&8) || // Battleground + (map[sd->bl.m].flag.restricted && item->flag.no_equip&(8*map[sd->bl.m].zone)) // Zone restriction + ) + return 0; + + //Gender check + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + //Required level check + if(item->elv && sd->status.base_level < (unsigned int)item->elv) + return 0; + +#ifdef RENEWAL + if(item->elvmax && sd->status.base_level > (unsigned int)item->elvmax) + return 0; +#endif + + //Not equipable by class. [Skotlex] + if (!( + (1<<(sd->class_&MAPID_BASEMASK)) & + (item->class_base[sd->class_&JOBL_2_1?1:(sd->class_&JOBL_2_2?2:0)]) + )) + return 0; + //Not usable by upper class. [Inkfish] + while( 1 ) { + if( item->class_upper&1 && !(sd->class_&(JOBL_UPPER|JOBL_THIRD|JOBL_BABY)) ) break; + if( item->class_upper&2 && sd->class_&(JOBL_UPPER|JOBL_THIRD) ) break; + if( item->class_upper&4 && sd->class_&JOBL_BABY ) break; + if( item->class_upper&8 && sd->class_&JOBL_THIRD ) break; + return 0; + } + + //Dead Branch & Bloody Branch & Porings Box + // FIXME: outdated, use constants or database + if( nameid == 604 || nameid == 12103 || nameid == 12109 ) + log_branch(sd); + + return 1; +} + +/*========================================== + * Last checks to use an item. + * Return: + * 0 = fail + * 1 = success + *------------------------------------------*/ +int pc_useitem(struct map_session_data *sd,int n) +{ + unsigned int tick = gettick(); + int amount, i, nameid; + struct script_code *script; + + nullpo_ret(sd); + + if( sd->status.inventory[n].nameid <= 0 || sd->status.inventory[n].amount <= 0 ) + return 0; + + if( !pc_isUseitem(sd,n) ) + return 0; + + // Store information for later use before it is lost (via pc_delitem) [Paradox924X] + nameid = sd->inventory_data[n]->nameid; + + if (nameid != ITEMID_NAUTHIZ && sd->sc.opt1 > 0 && sd->sc.opt1 != OPT1_STONEWAIT && sd->sc.opt1 != OPT1_BURNING) + return 0; + + if (sd->sc.count && ( + sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + (sd->sc.data[SC_GRAVITATION] && sd->sc.data[SC_GRAVITATION]->val3 == BCT_SELF) || + sd->sc.data[SC_TRICKDEAD] || + sd->sc.data[SC_HIDING] || + sd->sc.data[SC__SHADOWFORM] || + sd->sc.data[SC__MANHOLE] || + sd->sc.data[SC_KAGEHUMI] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOITEM) + )) + return 0; + + //Prevent mass item usage. [Skotlex] + if( DIFF_TICK(sd->canuseitem_tick, tick) > 0 || + (itemdb_iscashfood(nameid) && DIFF_TICK(sd->canusecashfood_tick, tick) > 0) + ) + return 0; + + /* Items with delayed consume are not meant to work while in mounts except reins of mount(12622) */ + if( sd->inventory_data[n]->flag.delay_consume ) { + if( nameid != ITEMID_REINS_OF_MOUNT && sd->sc.option&OPTION_MOUNTING ) + return 0; + else if( pc_issit(sd) ) + return 0; + } + //Since most delay-consume items involve using a "skill-type" target cursor, + //perform a skill-use check before going through. [Skotlex] + //resurrection was picked as testing skill, as a non-offensive, generic skill, it will do. + //FIXME: Is this really needed here? It'll be checked in unit.c after all and this prevents skill items using when silenced [Inkfish] + if( sd->inventory_data[n]->flag.delay_consume && ( sd->ud.skilltimer != INVALID_TIMER /*|| !status_check_skilluse(&sd->bl, &sd->bl, ALL_RESURRECTION, 0)*/ ) ) + return 0; + + if( sd->inventory_data[n]->delay > 0 ) { + ARR_FIND(0, MAX_ITEMDELAYS, i, sd->item_delay[i].nameid == nameid ); + if( i == MAX_ITEMDELAYS ) /* item not found. try first empty now */ + ARR_FIND(0, MAX_ITEMDELAYS, i, !sd->item_delay[i].nameid ); + if( i < MAX_ITEMDELAYS ) { + if( sd->item_delay[i].nameid ) {// found + if( DIFF_TICK(sd->item_delay[i].tick, tick) > 0 ) { + int e_tick = DIFF_TICK(sd->item_delay[i].tick, tick)/1000; + char e_msg[100]; + if( e_tick > 99 ) + sprintf(e_msg,"Item Failed. [%s] is cooling down. wait %.1f minutes.", + itemdb_jname(sd->status.inventory[n].nameid), + (double)e_tick / 60); + else + sprintf(e_msg,"Item Failed. [%s] is cooling down. wait %d seconds.", + itemdb_jname(sd->status.inventory[n].nameid), + e_tick+1); + clif_colormes(sd,COLOR_RED,e_msg); + return 0; // Delay has not expired yet + } + } else {// not yet used item (all slots are initially empty) + sd->item_delay[i].nameid = nameid; + } + sd->item_delay[i].tick = tick + sd->inventory_data[n]->delay; + } else {// should not happen + ShowError("pc_useitem: Exceeded item delay array capacity! (nameid=%d, char_id=%d)\n", nameid, sd->status.char_id); + } + //clean up used delays so we can give room for more + for(i = 0; i < MAX_ITEMDELAYS; i++) { + if( DIFF_TICK(sd->item_delay[i].tick, tick) <= 0 ) { + sd->item_delay[i].tick = 0; + sd->item_delay[i].nameid = 0; + } + } + } + + sd->itemid = sd->status.inventory[n].nameid; + sd->itemindex = n; + if(sd->catch_target_class != -1) //Abort pet catching. + sd->catch_target_class = -1; + + amount = sd->status.inventory[n].amount; + script = sd->inventory_data[n]->script; + //Check if the item is to be consumed immediately [Skotlex] + if( sd->inventory_data[n]->flag.delay_consume ) + clif_useitemack(sd,n,amount,true); + else { + if( sd->status.inventory[n].expire_time == 0 ) { + clif_useitemack(sd,n,amount-1,true); + pc_delitem(sd,n,1,1,0,LOG_TYPE_CONSUME); // Rental Usable Items are not deleted until expiration + } else + clif_useitemack(sd,n,0,false); + } + if(sd->status.inventory[n].card[0]==CARD0_CREATE && + pc_famerank(MakeDWord(sd->status.inventory[n].card[2],sd->status.inventory[n].card[3]), MAPID_ALCHEMIST)) + { + potion_flag = 2; // Famous player's potions have 50% more efficiency + if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ROGUE) + potion_flag = 3; //Even more effective potions. + } + + //Update item use time. + sd->canuseitem_tick = tick + battle_config.item_use_interval; + if( itemdb_iscashfood(nameid) ) + sd->canusecashfood_tick = tick + battle_config.cashfood_use_interval; + + run_script(script,0,sd->bl.id,fake_nd->bl.id); + potion_flag = 0; + return 1; +} + +/*========================================== + * Add item on cart for given index. + * Return: + * 0 = success + * 1 = fail + *------------------------------------------*/ +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount,e_log_pick_type log_type) +{ + struct item_data *data; + int i,w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search(item_data->nameid); + + if( data->stack.cart && amount > data->stack.amount ) + {// item stack limitation + return 1; + } + + if( !itemdb_cancartstore(item_data, pc_get_group_level(sd)) ) + { // Check item trade restrictions [Skotlex] + clif_displaymessage (sd->fd, msg_txt(264)); + return 1; + } + + if( (w = data->weight*amount) + sd->cart_weight > sd->cart_weight_max ) + return 1; + + i = MAX_CART; + if( itemdb_isstackable2(data) && !item_data->expire_time ) + { + ARR_FIND( 0, MAX_CART, i, + sd->status.cart[i].nameid == item_data->nameid && + sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] && + sd->status.cart[i].card[2] == item_data->card[2] && sd->status.cart[i].card[3] == item_data->card[3] ); + }; + + if( i < MAX_CART ) + {// item already in cart, stack it + if( amount > MAX_AMOUNT - sd->status.cart[i].amount || ( data->stack.cart && amount > data->stack.amount - sd->status.cart[i].amount ) ) + return 1; // no room + + sd->status.cart[i].amount+=amount; + clif_cart_additem(sd,i,amount,0); + } + else + {// item not stackable or not present, add it + ARR_FIND( 0, MAX_CART, i, sd->status.cart[i].nameid == 0 ); + if( i == MAX_CART ) + return 1; // no room + + memcpy(&sd->status.cart[i],item_data,sizeof(sd->status.cart[0])); + sd->status.cart[i].amount=amount; + sd->cart_num++; + clif_cart_additem(sd,i,amount,0); + } + sd->status.cart[i].favorite = 0;/* clear */ + log_pick_pc(sd, log_type, amount, &sd->status.cart[i]); + + sd->cart_weight += w; + clif_updatestatus(sd,SP_CARTINFO); + + return 0; +} + +/*========================================== + * Delete item on cart for given index. + * Return: + * 0 = success + * 1 = fail + *------------------------------------------*/ +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type,e_log_pick_type log_type) +{ + nullpo_retr(1, sd); + + if(sd->status.cart[n].nameid==0 || + sd->status.cart[n].amount<amount) + return 1; + + log_pick_pc(sd, log_type, -amount, &sd->status.cart[n]); + + sd->status.cart[n].amount -= amount; + sd->cart_weight -= itemdb_weight(sd->status.cart[n].nameid)*amount ; + if(sd->status.cart[n].amount <= 0){ + memset(&sd->status.cart[n],0,sizeof(sd->status.cart[0])); + sd->cart_num--; + } + if(!type) { + clif_cart_delitem(sd,n,amount); + clif_updatestatus(sd,SP_CARTINFO); + } + + return 0; +} + +/*========================================== + * Transfer item from inventory to cart. + * Return: + * 0 = fail + * 1 = succes + *------------------------------------------*/ +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + + nullpo_ret(sd); + + if (idx < 0 || idx >= MAX_INVENTORY) //Invalid index check [Skotlex] + return 1; + + item_data = &sd->status.inventory[idx]; + + if( item_data->nameid == 0 || amount < 1 || item_data->amount < amount || sd->state.vending ) + return 1; + + if( pc_cart_additem(sd,item_data,amount,LOG_TYPE_NONE) == 0 ) + return pc_delitem(sd,idx,amount,0,5,LOG_TYPE_NONE); + + return 1; +} + +/*========================================== + * Get number of item in cart. + * Return: + -1 = itemid not found or no amount found + x = remaining itemid on cart after get + *------------------------------------------*/ +int pc_cartitem_amount(struct map_session_data* sd, int idx, int amount) +{ + struct item* item_data; + + nullpo_retr(-1, sd); + + item_data = &sd->status.cart[idx]; + if( item_data->nameid == 0 || item_data->amount == 0 ) + return -1; + + return item_data->amount - amount; +} + +/*========================================== + * Retrieve an item at index idx from cart. + * Return: + * 0 = player not found or (FIXME) succes (from pc_cart_delitem) + * 1 = failure + *------------------------------------------*/ +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + int flag; + + nullpo_ret(sd); + + if (idx < 0 || idx >= MAX_CART) //Invalid index check [Skotlex] + return 1; + + item_data=&sd->status.cart[idx]; + + if(item_data->nameid==0 || amount < 1 || item_data->amount<amount || sd->state.vending ) + return 1; + if((flag = pc_additem(sd,item_data,amount,LOG_TYPE_NONE)) == 0) + return pc_cart_delitem(sd,idx,amount,0,LOG_TYPE_NONE); + + clif_additem(sd,0,0,flag); + return 1; +} + +/*========================================== + * Display item stolen msg to player sd + *------------------------------------------*/ +int pc_show_steal(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + int itemid; + + struct item_data *item=NULL; + char output[100]; + + sd=va_arg(ap,struct map_session_data *); + itemid=va_arg(ap,int); + + if((item=itemdb_exists(itemid))==NULL) + sprintf(output,"%s stole an Unknown Item (id: %i).",sd->status.name, itemid); + else + sprintf(output,"%s stole %s.",sd->status.name,item->jname); + clif_displaymessage( ((struct map_session_data *)bl)->fd, output); + + return 0; +} +/*========================================== + * Steal an item from bl (mob). + * Return: + * 0 = fail + * 1 = succes + *------------------------------------------*/ +int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skill_lv) +{ + int i,itemid,flag; + double rate; + struct status_data *sd_status, *md_status; + struct mob_data *md; + struct item tmp_item; + + if(!sd || !bl || bl->type!=BL_MOB) + return 0; + + md = (TBL_MOB *)bl; + + if(md->state.steal_flag == UCHAR_MAX || ( md->sc.opt1 && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE ) ) //already stolen from / status change check + return 0; + + sd_status= status_get_status_data(&sd->bl); + md_status= status_get_status_data(bl); + + if( md->master_id || md_status->mode&MD_BOSS || mob_is_treasure(md) || + map[bl->m].flag.nomobloot || // check noloot map flag [Lorky] + (battle_config.skill_steal_max_tries && //Reached limit of steal attempts. [Lupus] + md->state.steal_flag++ >= battle_config.skill_steal_max_tries) + ) { //Can't steal from + md->state.steal_flag = UCHAR_MAX; + return 0; + } + + // base skill success chance (percentual) + rate = (sd_status->dex - md_status->dex)/2 + skill_lv*6 + 4; + rate += sd->bonus.add_steal_rate; + + if( rate < 1 ) + return 0; + + // Try dropping one item, in the order from first to last possible slot. + // Droprate is affected by the skill success rate. + for( i = 0; i < MAX_STEAL_DROP; i++ ) + if( md->db->dropitem[i].nameid > 0 && itemdb_exists(md->db->dropitem[i].nameid) && rnd() % 10000 < md->db->dropitem[i].p * rate/100. ) + break; + if( i == MAX_STEAL_DROP ) + return 0; + + itemid = md->db->dropitem[i].nameid; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = itemid; + tmp_item.amount = 1; + tmp_item.identify = itemdb_isidentified(itemid); + flag = pc_additem(sd,&tmp_item,1,LOG_TYPE_PICKDROP_PLAYER); + + //TODO: Should we disable stealing when the item you stole couldn't be added to your inventory? Perhaps players will figure out a way to exploit this behaviour otherwise? + md->state.steal_flag = UCHAR_MAX; //you can't steal from this mob any more + + if(flag) { //Failed to steal due to overweight + clif_additem(sd,0,0,flag); + return 0; + } + + if(battle_config.show_steal_in_same_party) + party_foreachsamemap(pc_show_steal,sd,AREA_SIZE,sd,tmp_item.nameid); + + //Logs items, Stolen from mobs [Lupus] + log_pick_mob(md, LOG_TYPE_STEAL, -1, &tmp_item); + + //A Rare Steal Global Announce by Lupus + if(md->db->dropitem[i].p<=battle_config.rare_drop_announce) { + struct item_data *i_data; + char message[128]; + i_data = itemdb_search(itemid); + sprintf (message, msg_txt(542), (sd->status.name != NULL)?sd->status.name :"GM", md->db->jname, i_data->jname, (float)md->db->dropitem[i].p/100); + //MSG: "'%s' stole %s's %s (chance: %0.02f%%)" + intif_broadcast(message,strlen(message)+1,0); + } + return 1; +} + +/*========================================== + * Stole zeny from bl (mob) + * return + * 0 = fail + * 1 = success + *------------------------------------------*/ +int pc_steal_coin(struct map_session_data *sd,struct block_list *target) +{ + int rate,skill; + struct mob_data *md; + if(!sd || !target || target->type != BL_MOB) + return 0; + + md = (TBL_MOB*)target; + if( md->state.steal_coin_flag || md->sc.data[SC_STONE] || md->sc.data[SC_FREEZE] || md->status.mode&MD_BOSS ) + return 0; + + if( mob_is_treasure(md) ) + return 0; + + // FIXME: This formula is either custom or outdated. + skill = pc_checkskill(sd,RG_STEALCOIN)*10; + rate = skill + (sd->status.base_level - md->level)*3 + sd->battle_status.dex*2 + sd->battle_status.luk*2; + if(rnd()%1000 < rate) + { + int amount = md->level*10 + rnd()%100; + + pc_getzeny(sd, amount, LOG_TYPE_STEAL, NULL); + md->state.steal_coin_flag = 1; + return 1; + } + return 0; +} + +/*========================================== + * Set's a player position. + * Return values: + * 0 - Success. + * 1 - Invalid map index. + * 2 - Map not in this map-server, and failed to locate alternate map-server. + *------------------------------------------*/ +int pc_setpos(struct map_session_data* sd, unsigned short mapindex, int x, int y, clr_type clrtype) +{ + struct party_data *p; + int16 m; + + nullpo_ret(sd); + + if( !mapindex || !mapindex_id2name(mapindex) ) + { + ShowDebug("pc_setpos: Passed mapindex(%d) is invalid!\n", mapindex); + return 1; + } + + if( pc_isdead(sd) ) + { //Revive dead people before warping them + pc_setstand(sd); + pc_setrestartvalue(sd,1); + } + + m = map_mapindex2mapid(mapindex); + if( map[m].flag.src4instance && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + { + // Request the mapid of this src map into the instance of the party + int im = instance_map2imap(m, p->instance_id); + if( im < 0 ) + ; // Player will enter the src map for instances + else + { // Changes destiny to the instance map, not the source map + m = im; + mapindex = map_id2index(m); + } + } + + sd->state.changemap = (sd->mapindex != mapindex); + sd->state.warping = 1; + if( sd->state.changemap ) { // Misc map-changing settings + int i; + sd->state.pmap = sd->bl.m; + if (sd->sc.count) { // Cancel some map related stuff. + if (sd->sc.data[SC_JAILED]) + return 1; //You may not get out! + status_change_end(&sd->bl, SC_BOSSMAPINFO, INVALID_TIMER); + status_change_end(&sd->bl, SC_WARM, INVALID_TIMER); + status_change_end(&sd->bl, SC_SUN_COMFORT, INVALID_TIMER); + status_change_end(&sd->bl, SC_MOON_COMFORT, INVALID_TIMER); + status_change_end(&sd->bl, SC_STAR_COMFORT, INVALID_TIMER); + status_change_end(&sd->bl, SC_MIRACLE, INVALID_TIMER); + if (sd->sc.data[SC_KNOWLEDGE]) { + struct status_change_entry *sce = sd->sc.data[SC_KNOWLEDGE]; + if (sce->timer != INVALID_TIMER) + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(gettick() + skill_get_time(SG_KNOWLEDGE, sce->val1), status_change_timer, sd->bl.id, SC_KNOWLEDGE); + } + status_change_end(&sd->bl, SC_PROPERTYWALK, INVALID_TIMER); + status_change_end(&sd->bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(&sd->bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + for( i = 0; i < EQI_MAX; i++ ) { + if( sd->equip_index[ i ] >= 0 ) + if( !pc_isequip( sd , sd->equip_index[ i ] ) ) + pc_unequipitem( sd , sd->equip_index[ i ] , 2 ); + } + if (battle_config.clear_unit_onwarp&BL_PC) + skill_clear_unitgroup(&sd->bl); + party_send_dot_remove(sd); //minimap dot fix [Kevin] + guild_send_dot_remove(sd); + bg_send_dot_remove(sd); + if (sd->regen.state.gc) + sd->regen.state.gc = 0; + // make sure vending is allowed here + if (sd->state.vending && map[m].flag.novending) { + clif_displaymessage (sd->fd, msg_txt(276)); // "You can't open a shop on this map" + vending_closevending(sd); + } + } + + if( m < 0 ) + { + uint32 ip; + uint16 port; + //if can't find any map-servers, just abort setting position. + if(!sd->mapindex || map_mapname2ipport(mapindex,&ip,&port)) + return 2; + + if (sd->npc_id) + npc_event_dequeue(sd); + npc_script_event(sd, NPCE_LOGOUT); + //remove from map, THEN change x/y coordinates + unit_remove_map_pc(sd,clrtype); + sd->mapindex = mapindex; + sd->bl.x=x; + sd->bl.y=y; + pc_clean_skilltree(sd); + chrif_save(sd,2); + chrif_changemapserver(sd, ip, (short)port); + + //Free session data from this map server [Kevin] + unit_free_pc(sd); + + return 0; + } + + if( x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys ) + { + ShowError("pc_setpos: attempt to place player %s (%d:%d) on invalid coordinates (%s-%d,%d)\n", sd->status.name, sd->status.account_id, sd->status.char_id, mapindex_id2name(mapindex),x,y); + x = y = 0; // make it random + } + + if( x == 0 && y == 0 ) + {// pick a random walkable cell + do { + x=rnd()%(map[m].xs-2)+1; + y=rnd()%(map[m].ys-2)+1; + } while(map_getcell(m,x,y,CELL_CHKNOPASS)); + } + + if (sd->state.vending && map_getcell(m,x,y,CELL_CHKNOVENDING)) { + clif_displaymessage (sd->fd, msg_txt(204)); // "You can't open a shop on this cell." + vending_closevending(sd); + } + + if(sd->bl.prev != NULL){ + unit_remove_map_pc(sd,clrtype); + clif_changemap(sd,map[m].index,x,y); // [MouseJstr] + } else if(sd->state.active) + //Tag player for rewarping after map-loading is done. [Skotlex] + sd->state.rewarp = 1; + + sd->mapindex = mapindex; + sd->bl.m = m; + sd->bl.x = sd->ud.to_x = x; + sd->bl.y = sd->ud.to_y = y; + + if( sd->status.guild_id > 0 && map[m].flag.gvg_castle ) + { // Increased guild castle regen [Valaris] + struct guild_castle *gc = guild_mapindex2gc(sd->mapindex); + if(gc && gc->guild_id == sd->status.guild_id) + sd->regen.state.gc = 1; + } + + if( sd->status.pet_id > 0 && sd->pd && sd->pd->pet.intimate > 0 ) + { + sd->pd->bl.m = m; + sd->pd->bl.x = sd->pd->ud.to_x = x; + sd->pd->bl.y = sd->pd->ud.to_y = y; + sd->pd->ud.dir = sd->ud.dir; + } + + if( merc_is_hom_active(sd->hd) ) + { + sd->hd->bl.m = m; + sd->hd->bl.x = sd->hd->ud.to_x = x; + sd->hd->bl.y = sd->hd->ud.to_y = y; + sd->hd->ud.dir = sd->ud.dir; + } + + if( sd->md ) + { + sd->md->bl.m = m; + sd->md->bl.x = sd->md->ud.to_x = x; + sd->md->bl.y = sd->md->ud.to_y = y; + sd->md->ud.dir = sd->ud.dir; + } + + return 0; +} + +/*========================================== + * Warp player sd to random location on current map. + * May fail if no walkable cell found (1000 attempts). + * Return: + * 0 = fail or FIXME success (from pc_setpos) + * x(1|2) = fail + *------------------------------------------*/ +int pc_randomwarp(struct map_session_data *sd, clr_type type) +{ + int x,y,i=0; + int16 m; + + nullpo_ret(sd); + + m=sd->bl.m; + + if (map[sd->bl.m].flag.noteleport) //Teleport forbidden + return 0; + + do{ + x=rnd()%(map[m].xs-2)+1; + y=rnd()%(map[m].ys-2)+1; + }while(map_getcell(m,x,y,CELL_CHKNOPASS) && (i++)<1000 ); + + if (i < 1000) + return pc_setpos(sd,map[sd->bl.m].index,x,y,type); + + return 0; +} + +/*========================================== + * Records a memo point at sd's current position + * pos - entry to replace, (-1: shift oldest entry out) + *------------------------------------------*/ +int pc_memo(struct map_session_data* sd, int pos) +{ + int skill; + + nullpo_ret(sd); + + // check mapflags + if( sd->bl.m >= 0 && (map[sd->bl.m].flag.nomemo || map[sd->bl.m].flag.nowarpto) && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE) ) { + clif_skill_teleportmessage(sd, 1); // "Saved point cannot be memorized." + return 0; + } + + // check inputs + if( pos < -1 || pos >= MAX_MEMOPOINTS ) + return 0; // invalid input + + // check required skill level + skill = pc_checkskill(sd, AL_WARP); + if( skill < 1 ) { + clif_skill_memomessage(sd,2); // "You haven't learned Warp." + return 0; + } + if( skill < 2 || skill - 2 < pos ) { + clif_skill_memomessage(sd,1); // "Skill Level is not high enough." + return 0; + } + + if( pos == -1 ) + { + int i; + // prevent memo-ing the same map multiple times + ARR_FIND( 0, MAX_MEMOPOINTS, i, sd->status.memo_point[i].map == map_id2index(sd->bl.m) ); + memmove(&sd->status.memo_point[1], &sd->status.memo_point[0], (min(i,MAX_MEMOPOINTS-1))*sizeof(struct point)); + pos = 0; + } + + sd->status.memo_point[pos].map = map_id2index(sd->bl.m); + sd->status.memo_point[pos].x = sd->bl.x; + sd->status.memo_point[pos].y = sd->bl.y; + + clif_skill_memomessage(sd, 0); + + return 1; +} + +// +// Skills +// +/*========================================== + * Return player sd skill_lv learned for given skill + *------------------------------------------*/ +int pc_checkskill(struct map_session_data *sd,uint16 skill_id) +{ + if(sd == NULL) return 0; + if( skill_id >= GD_SKILLBASE && skill_id < GD_MAX ) + { + struct guild *g; + + if( sd->status.guild_id>0 && (g=guild_search(sd->status.guild_id))!=NULL) + return guild_checkskill(g,skill_id); + return 0; + } + else if(skill_id >= ARRAYLENGTH(sd->status.skill) ) + { + ShowError("pc_checkskill: Invalid skill id %d (char_id=%d).\n", skill_id, sd->status.char_id); + return 0; + } + + if(sd->status.skill[skill_id].id == skill_id) + return (sd->status.skill[skill_id].lv); + + return 0; +} + +/*========================================== + * Chk if we still have the correct weapon to continue the skill (actually status) + * If not ending it + * Return + * 0 - No status found or all done + *------------------------------------------*/ +int pc_checkallowskill(struct map_session_data *sd) +{ + const enum sc_type scw_list[] = { + SC_TWOHANDQUICKEN, + SC_ONEHAND, + SC_AURABLADE, + SC_PARRYING, + SC_SPEARQUICKEN, + SC_ADRENALINE, + SC_ADRENALINE2, + SC_DANCING, + SC_GATLINGFEVER, + SC_FEARBREEZE + }; + const enum sc_type scs_list[] = { + SC_AUTOGUARD, + SC_DEFENDER, + SC_REFLECTSHIELD, + SC_REFLECTDAMAGE + }; + int i; + nullpo_ret(sd); + + if(!sd->sc.count) + return 0; + + for (i = 0; i < ARRAYLENGTH(scw_list); i++) + { // Skills requiring specific weapon types + if( scw_list[i] == SC_DANCING && !battle_config.dancing_weaponswitch_fix ) + continue; + if(sd->sc.data[scw_list[i]] && + !pc_check_weapontype(sd,skill_get_weapontype(status_sc2skill(scw_list[i])))) + status_change_end(&sd->bl, scw_list[i], INVALID_TIMER); + } + + if(sd->sc.data[SC_SPURT] && sd->status.weapon) + // Spurt requires bare hands (feet, in fact xD) + status_change_end(&sd->bl, SC_SPURT, INVALID_TIMER); + + if(sd->status.shield <= 0) { // Skills requiring a shield + for (i = 0; i < ARRAYLENGTH(scs_list); i++) + if(sd->sc.data[scs_list[i]]) + status_change_end(&sd->bl, scs_list[i], INVALID_TIMER); + } + return 0; +} + +/*========================================== + * Return equiped itemid? on player sd at pos + * Return + * -1 : mean nothing equiped + * idx : (this index could be used in inventory to found item_data) + *------------------------------------------*/ +int pc_checkequip(struct map_session_data *sd,int pos) +{ + int i; + + nullpo_retr(-1, sd); + + for(i=0;i<EQI_MAX;i++){ + if(pos & equip_pos[i]) + return sd->equip_index[i]; + } + + return -1; +} + +/*========================================== + * Convert's from the client's lame Job ID system + * to the map server's 'makes sense' system. [Skotlex] + *------------------------------------------*/ +int pc_jobid2mapid(unsigned short b_class) +{ + switch(b_class) + { + //Novice And 1-1 Jobs + case JOB_NOVICE: return MAPID_NOVICE; + case JOB_SWORDMAN: return MAPID_SWORDMAN; + case JOB_MAGE: return MAPID_MAGE; + case JOB_ARCHER: return MAPID_ARCHER; + case JOB_ACOLYTE: return MAPID_ACOLYTE; + case JOB_MERCHANT: return MAPID_MERCHANT; + case JOB_THIEF: return MAPID_THIEF; + case JOB_TAEKWON: return MAPID_TAEKWON; + case JOB_WEDDING: return MAPID_WEDDING; + case JOB_GUNSLINGER: return MAPID_GUNSLINGER; + case JOB_NINJA: return MAPID_NINJA; + case JOB_XMAS: return MAPID_XMAS; + case JOB_SUMMER: return MAPID_SUMMER; + case JOB_GANGSI: return MAPID_GANGSI; + //2-1 Jobs + case JOB_SUPER_NOVICE: return MAPID_SUPER_NOVICE; + case JOB_KNIGHT: return MAPID_KNIGHT; + case JOB_WIZARD: return MAPID_WIZARD; + case JOB_HUNTER: return MAPID_HUNTER; + case JOB_PRIEST: return MAPID_PRIEST; + case JOB_BLACKSMITH: return MAPID_BLACKSMITH; + case JOB_ASSASSIN: return MAPID_ASSASSIN; + case JOB_STAR_GLADIATOR: return MAPID_STAR_GLADIATOR; + case JOB_KAGEROU: + case JOB_OBORO: return MAPID_KAGEROUOBORO; + case JOB_DEATH_KNIGHT: return MAPID_DEATH_KNIGHT; + //2-2 Jobs + case JOB_CRUSADER: return MAPID_CRUSADER; + case JOB_SAGE: return MAPID_SAGE; + case JOB_BARD: + case JOB_DANCER: return MAPID_BARDDANCER; + case JOB_MONK: return MAPID_MONK; + case JOB_ALCHEMIST: return MAPID_ALCHEMIST; + case JOB_ROGUE: return MAPID_ROGUE; + case JOB_SOUL_LINKER: return MAPID_SOUL_LINKER; + case JOB_DARK_COLLECTOR: return MAPID_DARK_COLLECTOR; + //Trans Novice And Trans 1-1 Jobs + case JOB_NOVICE_HIGH: return MAPID_NOVICE_HIGH; + case JOB_SWORDMAN_HIGH: return MAPID_SWORDMAN_HIGH; + case JOB_MAGE_HIGH: return MAPID_MAGE_HIGH; + case JOB_ARCHER_HIGH: return MAPID_ARCHER_HIGH; + case JOB_ACOLYTE_HIGH: return MAPID_ACOLYTE_HIGH; + case JOB_MERCHANT_HIGH: return MAPID_MERCHANT_HIGH; + case JOB_THIEF_HIGH: return MAPID_THIEF_HIGH; + //Trans 2-1 Jobs + case JOB_LORD_KNIGHT: return MAPID_LORD_KNIGHT; + case JOB_HIGH_WIZARD: return MAPID_HIGH_WIZARD; + case JOB_SNIPER: return MAPID_SNIPER; + case JOB_HIGH_PRIEST: return MAPID_HIGH_PRIEST; + case JOB_WHITESMITH: return MAPID_WHITESMITH; + case JOB_ASSASSIN_CROSS: return MAPID_ASSASSIN_CROSS; + //Trans 2-2 Jobs + case JOB_PALADIN: return MAPID_PALADIN; + case JOB_PROFESSOR: return MAPID_PROFESSOR; + case JOB_CLOWN: + case JOB_GYPSY: return MAPID_CLOWNGYPSY; + case JOB_CHAMPION: return MAPID_CHAMPION; + case JOB_CREATOR: return MAPID_CREATOR; + case JOB_STALKER: return MAPID_STALKER; + //Baby Novice And Baby 1-1 Jobs + case JOB_BABY: return MAPID_BABY; + case JOB_BABY_SWORDMAN: return MAPID_BABY_SWORDMAN; + case JOB_BABY_MAGE: return MAPID_BABY_MAGE; + case JOB_BABY_ARCHER: return MAPID_BABY_ARCHER; + case JOB_BABY_ACOLYTE: return MAPID_BABY_ACOLYTE; + case JOB_BABY_MERCHANT: return MAPID_BABY_MERCHANT; + case JOB_BABY_THIEF: return MAPID_BABY_THIEF; + //Baby 2-1 Jobs + case JOB_SUPER_BABY: return MAPID_SUPER_BABY; + case JOB_BABY_KNIGHT: return MAPID_BABY_KNIGHT; + case JOB_BABY_WIZARD: return MAPID_BABY_WIZARD; + case JOB_BABY_HUNTER: return MAPID_BABY_HUNTER; + case JOB_BABY_PRIEST: return MAPID_BABY_PRIEST; + case JOB_BABY_BLACKSMITH: return MAPID_BABY_BLACKSMITH; + case JOB_BABY_ASSASSIN: return MAPID_BABY_ASSASSIN; + //Baby 2-2 Jobs + case JOB_BABY_CRUSADER: return MAPID_BABY_CRUSADER; + case JOB_BABY_SAGE: return MAPID_BABY_SAGE; + case JOB_BABY_BARD: + case JOB_BABY_DANCER: return MAPID_BABY_BARDDANCER; + case JOB_BABY_MONK: return MAPID_BABY_MONK; + case JOB_BABY_ALCHEMIST: return MAPID_BABY_ALCHEMIST; + case JOB_BABY_ROGUE: return MAPID_BABY_ROGUE; + //3-1 Jobs + case JOB_SUPER_NOVICE_E: return MAPID_SUPER_NOVICE_E; + case JOB_RUNE_KNIGHT: return MAPID_RUNE_KNIGHT; + case JOB_WARLOCK: return MAPID_WARLOCK; + case JOB_RANGER: return MAPID_RANGER; + case JOB_ARCH_BISHOP: return MAPID_ARCH_BISHOP; + case JOB_MECHANIC: return MAPID_MECHANIC; + case JOB_GUILLOTINE_CROSS: return MAPID_GUILLOTINE_CROSS; + //3-2 Jobs + case JOB_ROYAL_GUARD: return MAPID_ROYAL_GUARD; + case JOB_SORCERER: return MAPID_SORCERER; + case JOB_MINSTREL: + case JOB_WANDERER: return MAPID_MINSTRELWANDERER; + case JOB_SURA: return MAPID_SURA; + case JOB_GENETIC: return MAPID_GENETIC; + case JOB_SHADOW_CHASER: return MAPID_SHADOW_CHASER; + //Trans 3-1 Jobs + case JOB_RUNE_KNIGHT_T: return MAPID_RUNE_KNIGHT_T; + case JOB_WARLOCK_T: return MAPID_WARLOCK_T; + case JOB_RANGER_T: return MAPID_RANGER_T; + case JOB_ARCH_BISHOP_T: return MAPID_ARCH_BISHOP_T; + case JOB_MECHANIC_T: return MAPID_MECHANIC_T; + case JOB_GUILLOTINE_CROSS_T: return MAPID_GUILLOTINE_CROSS_T; + //Trans 3-2 Jobs + case JOB_ROYAL_GUARD_T: return MAPID_ROYAL_GUARD_T; + case JOB_SORCERER_T: return MAPID_SORCERER_T; + case JOB_MINSTREL_T: + case JOB_WANDERER_T: return MAPID_MINSTRELWANDERER_T; + case JOB_SURA_T: return MAPID_SURA_T; + case JOB_GENETIC_T: return MAPID_GENETIC_T; + case JOB_SHADOW_CHASER_T: return MAPID_SHADOW_CHASER_T; + //Baby 3-1 Jobs + case JOB_SUPER_BABY_E: return MAPID_SUPER_BABY_E; + case JOB_BABY_RUNE: return MAPID_BABY_RUNE; + case JOB_BABY_WARLOCK: return MAPID_BABY_WARLOCK; + case JOB_BABY_RANGER: return MAPID_BABY_RANGER; + case JOB_BABY_BISHOP: return MAPID_BABY_BISHOP; + case JOB_BABY_MECHANIC: return MAPID_BABY_MECHANIC; + case JOB_BABY_CROSS: return MAPID_BABY_CROSS; + //Baby 3-2 Jobs + case JOB_BABY_GUARD: return MAPID_BABY_GUARD; + case JOB_BABY_SORCERER: return MAPID_BABY_SORCERER; + case JOB_BABY_MINSTREL: + case JOB_BABY_WANDERER: return MAPID_BABY_MINSTRELWANDERER; + case JOB_BABY_SURA: return MAPID_BABY_SURA; + case JOB_BABY_GENETIC: return MAPID_BABY_GENETIC; + case JOB_BABY_CHASER: return MAPID_BABY_CHASER; + default: + return -1; + } +} + +//Reverts the map-style class id to the client-style one. +int pc_mapid2jobid(unsigned short class_, int sex) +{ + switch(class_) + { + //Novice And 1-1 Jobs + case MAPID_NOVICE: return JOB_NOVICE; + case MAPID_SWORDMAN: return JOB_SWORDMAN; + case MAPID_MAGE: return JOB_MAGE; + case MAPID_ARCHER: return JOB_ARCHER; + case MAPID_ACOLYTE: return JOB_ACOLYTE; + case MAPID_MERCHANT: return JOB_MERCHANT; + case MAPID_THIEF: return JOB_THIEF; + case MAPID_TAEKWON: return JOB_TAEKWON; + case MAPID_WEDDING: return JOB_WEDDING; + case MAPID_GUNSLINGER: return JOB_GUNSLINGER; + case MAPID_NINJA: return JOB_NINJA; + case MAPID_XMAS: return JOB_XMAS; + case MAPID_SUMMER: return JOB_SUMMER; + case MAPID_GANGSI: return JOB_GANGSI; + //2-1 Jobs + case MAPID_SUPER_NOVICE: return JOB_SUPER_NOVICE; + case MAPID_KNIGHT: return JOB_KNIGHT; + case MAPID_WIZARD: return JOB_WIZARD; + case MAPID_HUNTER: return JOB_HUNTER; + case MAPID_PRIEST: return JOB_PRIEST; + case MAPID_BLACKSMITH: return JOB_BLACKSMITH; + case MAPID_ASSASSIN: return JOB_ASSASSIN; + case MAPID_STAR_GLADIATOR: return JOB_STAR_GLADIATOR; + case MAPID_KAGEROUOBORO: return sex?JOB_KAGEROU:JOB_OBORO; + case MAPID_DEATH_KNIGHT: return JOB_DEATH_KNIGHT; + //2-2 Jobs + case MAPID_CRUSADER: return JOB_CRUSADER; + case MAPID_SAGE: return JOB_SAGE; + case MAPID_BARDDANCER: return sex?JOB_BARD:JOB_DANCER; + case MAPID_MONK: return JOB_MONK; + case MAPID_ALCHEMIST: return JOB_ALCHEMIST; + case MAPID_ROGUE: return JOB_ROGUE; + case MAPID_SOUL_LINKER: return JOB_SOUL_LINKER; + case MAPID_DARK_COLLECTOR: return JOB_DARK_COLLECTOR; + //Trans Novice And Trans 2-1 Jobs + case MAPID_NOVICE_HIGH: return JOB_NOVICE_HIGH; + case MAPID_SWORDMAN_HIGH: return JOB_SWORDMAN_HIGH; + case MAPID_MAGE_HIGH: return JOB_MAGE_HIGH; + case MAPID_ARCHER_HIGH: return JOB_ARCHER_HIGH; + case MAPID_ACOLYTE_HIGH: return JOB_ACOLYTE_HIGH; + case MAPID_MERCHANT_HIGH: return JOB_MERCHANT_HIGH; + case MAPID_THIEF_HIGH: return JOB_THIEF_HIGH; + //Trans 2-1 Jobs + case MAPID_LORD_KNIGHT: return JOB_LORD_KNIGHT; + case MAPID_HIGH_WIZARD: return JOB_HIGH_WIZARD; + case MAPID_SNIPER: return JOB_SNIPER; + case MAPID_HIGH_PRIEST: return JOB_HIGH_PRIEST; + case MAPID_WHITESMITH: return JOB_WHITESMITH; + case MAPID_ASSASSIN_CROSS: return JOB_ASSASSIN_CROSS; + //Trans 2-2 Jobs + case MAPID_PALADIN: return JOB_PALADIN; + case MAPID_PROFESSOR: return JOB_PROFESSOR; + case MAPID_CLOWNGYPSY: return sex?JOB_CLOWN:JOB_GYPSY; + case MAPID_CHAMPION: return JOB_CHAMPION; + case MAPID_CREATOR: return JOB_CREATOR; + case MAPID_STALKER: return JOB_STALKER; + //Baby Novice And Baby 1-1 Jobs + case MAPID_BABY: return JOB_BABY; + case MAPID_BABY_SWORDMAN: return JOB_BABY_SWORDMAN; + case MAPID_BABY_MAGE: return JOB_BABY_MAGE; + case MAPID_BABY_ARCHER: return JOB_BABY_ARCHER; + case MAPID_BABY_ACOLYTE: return JOB_BABY_ACOLYTE; + case MAPID_BABY_MERCHANT: return JOB_BABY_MERCHANT; + case MAPID_BABY_THIEF: return JOB_BABY_THIEF; + //Baby 2-1 Jobs + case MAPID_SUPER_BABY: return JOB_SUPER_BABY; + case MAPID_BABY_KNIGHT: return JOB_BABY_KNIGHT; + case MAPID_BABY_WIZARD: return JOB_BABY_WIZARD; + case MAPID_BABY_HUNTER: return JOB_BABY_HUNTER; + case MAPID_BABY_PRIEST: return JOB_BABY_PRIEST; + case MAPID_BABY_BLACKSMITH: return JOB_BABY_BLACKSMITH; + case MAPID_BABY_ASSASSIN: return JOB_BABY_ASSASSIN; + //Baby 2-2 Jobs + case MAPID_BABY_CRUSADER: return JOB_BABY_CRUSADER; + case MAPID_BABY_SAGE: return JOB_BABY_SAGE; + case MAPID_BABY_BARDDANCER: return sex?JOB_BABY_BARD:JOB_BABY_DANCER; + case MAPID_BABY_MONK: return JOB_BABY_MONK; + case MAPID_BABY_ALCHEMIST: return JOB_BABY_ALCHEMIST; + case MAPID_BABY_ROGUE: return JOB_BABY_ROGUE; + //3-1 Jobs + case MAPID_SUPER_NOVICE_E: return JOB_SUPER_NOVICE_E; + case MAPID_RUNE_KNIGHT: return JOB_RUNE_KNIGHT; + case MAPID_WARLOCK: return JOB_WARLOCK; + case MAPID_RANGER: return JOB_RANGER; + case MAPID_ARCH_BISHOP: return JOB_ARCH_BISHOP; + case MAPID_MECHANIC: return JOB_MECHANIC; + case MAPID_GUILLOTINE_CROSS: return JOB_GUILLOTINE_CROSS; + //3-2 Jobs + case MAPID_ROYAL_GUARD: return JOB_ROYAL_GUARD; + case MAPID_SORCERER: return JOB_SORCERER; + case MAPID_MINSTRELWANDERER: return sex?JOB_MINSTREL:JOB_WANDERER; + case MAPID_SURA: return JOB_SURA; + case MAPID_GENETIC: return JOB_GENETIC; + case MAPID_SHADOW_CHASER: return JOB_SHADOW_CHASER; + //Trans 3-1 Jobs + case MAPID_RUNE_KNIGHT_T: return JOB_RUNE_KNIGHT_T; + case MAPID_WARLOCK_T: return JOB_WARLOCK_T; + case MAPID_RANGER_T: return JOB_RANGER_T; + case MAPID_ARCH_BISHOP_T: return JOB_ARCH_BISHOP_T; + case MAPID_MECHANIC_T: return JOB_MECHANIC_T; + case MAPID_GUILLOTINE_CROSS_T: return JOB_GUILLOTINE_CROSS_T; + //Trans 3-2 Jobs + case MAPID_ROYAL_GUARD_T: return JOB_ROYAL_GUARD_T; + case MAPID_SORCERER_T: return JOB_SORCERER_T; + case MAPID_MINSTRELWANDERER_T: return sex?JOB_MINSTREL_T:JOB_WANDERER_T; + case MAPID_SURA_T: return JOB_SURA_T; + case MAPID_GENETIC_T: return JOB_GENETIC_T; + case MAPID_SHADOW_CHASER_T: return JOB_SHADOW_CHASER_T; + //Baby 3-1 Jobs + case MAPID_SUPER_BABY_E: return JOB_SUPER_BABY_E; + case MAPID_BABY_RUNE: return JOB_BABY_RUNE; + case MAPID_BABY_WARLOCK: return JOB_BABY_WARLOCK; + case MAPID_BABY_RANGER: return JOB_BABY_RANGER; + case MAPID_BABY_BISHOP: return JOB_BABY_BISHOP; + case MAPID_BABY_MECHANIC: return JOB_BABY_MECHANIC; + case MAPID_BABY_CROSS: return JOB_BABY_CROSS; + //Baby 3-2 Jobs + case MAPID_BABY_GUARD: return JOB_BABY_GUARD; + case MAPID_BABY_SORCERER: return JOB_BABY_SORCERER; + case MAPID_BABY_MINSTRELWANDERER: return sex?JOB_BABY_MINSTREL:JOB_BABY_WANDERER; + case MAPID_BABY_SURA: return JOB_BABY_SURA; + case MAPID_BABY_GENETIC: return JOB_BABY_GENETIC; + case MAPID_BABY_CHASER: return JOB_BABY_CHASER; + default: + return -1; + } +} + +/*==================================================== + * This function return the name of the job (by [Yor]) + *----------------------------------------------------*/ +const char* job_name(int class_) +{ + switch (class_) { + case JOB_NOVICE: + case JOB_SWORDMAN: + case JOB_MAGE: + case JOB_ARCHER: + case JOB_ACOLYTE: + case JOB_MERCHANT: + case JOB_THIEF: + return msg_txt(550 - JOB_NOVICE+class_); + + case JOB_KNIGHT: + case JOB_PRIEST: + case JOB_WIZARD: + case JOB_BLACKSMITH: + case JOB_HUNTER: + case JOB_ASSASSIN: + return msg_txt(557 - JOB_KNIGHT+class_); + + case JOB_KNIGHT2: + return msg_txt(557); + + case JOB_CRUSADER: + case JOB_MONK: + case JOB_SAGE: + case JOB_ROGUE: + case JOB_ALCHEMIST: + case JOB_BARD: + case JOB_DANCER: + return msg_txt(563 - JOB_CRUSADER+class_); + + case JOB_CRUSADER2: + return msg_txt(563); + + case JOB_WEDDING: + case JOB_SUPER_NOVICE: + case JOB_GUNSLINGER: + case JOB_NINJA: + case JOB_XMAS: + return msg_txt(570 - JOB_WEDDING+class_); + + case JOB_SUMMER: + return msg_txt(621); + + case JOB_NOVICE_HIGH: + case JOB_SWORDMAN_HIGH: + case JOB_MAGE_HIGH: + case JOB_ARCHER_HIGH: + case JOB_ACOLYTE_HIGH: + case JOB_MERCHANT_HIGH: + case JOB_THIEF_HIGH: + return msg_txt(575 - JOB_NOVICE_HIGH+class_); + + case JOB_LORD_KNIGHT: + case JOB_HIGH_PRIEST: + case JOB_HIGH_WIZARD: + case JOB_WHITESMITH: + case JOB_SNIPER: + case JOB_ASSASSIN_CROSS: + return msg_txt(582 - JOB_LORD_KNIGHT+class_); + + case JOB_LORD_KNIGHT2: + return msg_txt(582); + + case JOB_PALADIN: + case JOB_CHAMPION: + case JOB_PROFESSOR: + case JOB_STALKER: + case JOB_CREATOR: + case JOB_CLOWN: + case JOB_GYPSY: + return msg_txt(588 - JOB_PALADIN + class_); + + case JOB_PALADIN2: + return msg_txt(588); + + case JOB_BABY: + case JOB_BABY_SWORDMAN: + case JOB_BABY_MAGE: + case JOB_BABY_ARCHER: + case JOB_BABY_ACOLYTE: + case JOB_BABY_MERCHANT: + case JOB_BABY_THIEF: + return msg_txt(595 - JOB_BABY + class_); + + case JOB_BABY_KNIGHT: + case JOB_BABY_PRIEST: + case JOB_BABY_WIZARD: + case JOB_BABY_BLACKSMITH: + case JOB_BABY_HUNTER: + case JOB_BABY_ASSASSIN: + return msg_txt(602 - JOB_BABY_KNIGHT + class_); + + case JOB_BABY_KNIGHT2: + return msg_txt(602); + + case JOB_BABY_CRUSADER: + case JOB_BABY_MONK: + case JOB_BABY_SAGE: + case JOB_BABY_ROGUE: + case JOB_BABY_ALCHEMIST: + case JOB_BABY_BARD: + case JOB_BABY_DANCER: + return msg_txt(608 - JOB_BABY_CRUSADER + class_); + + case JOB_BABY_CRUSADER2: + return msg_txt(608); + + case JOB_SUPER_BABY: + return msg_txt(615); + + case JOB_TAEKWON: + return msg_txt(616); + case JOB_STAR_GLADIATOR: + case JOB_STAR_GLADIATOR2: + return msg_txt(617); + case JOB_SOUL_LINKER: + return msg_txt(618); + + case JOB_GANGSI: + case JOB_DEATH_KNIGHT: + case JOB_DARK_COLLECTOR: + return msg_txt(622 - JOB_GANGSI+class_); + + case JOB_RUNE_KNIGHT: + case JOB_WARLOCK: + case JOB_RANGER: + case JOB_ARCH_BISHOP: + case JOB_MECHANIC: + case JOB_GUILLOTINE_CROSS: + return msg_txt(625 - JOB_RUNE_KNIGHT+class_); + + case JOB_RUNE_KNIGHT_T: + case JOB_WARLOCK_T: + case JOB_RANGER_T: + case JOB_ARCH_BISHOP_T: + case JOB_MECHANIC_T: + case JOB_GUILLOTINE_CROSS_T: + return msg_txt(681 - JOB_RUNE_KNIGHT_T+class_); + + case JOB_ROYAL_GUARD: + case JOB_SORCERER: + case JOB_MINSTREL: + case JOB_WANDERER: + case JOB_SURA: + case JOB_GENETIC: + case JOB_SHADOW_CHASER: + return msg_txt(631 - JOB_ROYAL_GUARD+class_); + + case JOB_ROYAL_GUARD_T: + case JOB_SORCERER_T: + case JOB_MINSTREL_T: + case JOB_WANDERER_T: + case JOB_SURA_T: + case JOB_GENETIC_T: + case JOB_SHADOW_CHASER_T: + return msg_txt(687 - JOB_ROYAL_GUARD_T+class_); + + case JOB_RUNE_KNIGHT2: + case JOB_RUNE_KNIGHT_T2: + return msg_txt(625); + + case JOB_ROYAL_GUARD2: + case JOB_ROYAL_GUARD_T2: + return msg_txt(631); + + case JOB_RANGER2: + case JOB_RANGER_T2: + return msg_txt(627); + + case JOB_MECHANIC2: + case JOB_MECHANIC_T2: + return msg_txt(629); + + case JOB_BABY_RUNE: + case JOB_BABY_WARLOCK: + case JOB_BABY_RANGER: + case JOB_BABY_BISHOP: + case JOB_BABY_MECHANIC: + case JOB_BABY_CROSS: + case JOB_BABY_GUARD: + case JOB_BABY_SORCERER: + case JOB_BABY_MINSTREL: + case JOB_BABY_WANDERER: + case JOB_BABY_SURA: + case JOB_BABY_GENETIC: + case JOB_BABY_CHASER: + return msg_txt(638 - JOB_BABY_RUNE+class_); + + case JOB_BABY_RUNE2: + return msg_txt(638); + + case JOB_BABY_GUARD2: + return msg_txt(644); + + case JOB_BABY_RANGER2: + return msg_txt(640); + + case JOB_BABY_MECHANIC2: + return msg_txt(642); + + case JOB_SUPER_NOVICE_E: + case JOB_SUPER_BABY_E: + return msg_txt(651 - JOB_SUPER_NOVICE_E+class_); + + case JOB_KAGEROU: + case JOB_OBORO: + return msg_txt(653 - JOB_KAGEROU+class_); + + default: + return msg_txt(655); + } +} + +int pc_follow_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + struct block_list *tbl; + + sd = map_id2sd(id); + nullpo_ret(sd); + + if (sd->followtimer != tid){ + ShowError("pc_follow_timer %d != %d\n",sd->followtimer,tid); + sd->followtimer = INVALID_TIMER; + return 0; + } + + sd->followtimer = INVALID_TIMER; + tbl = map_id2bl(sd->followtarget); + + if (tbl == NULL || pc_isdead(sd) || status_isdead(tbl)) + { + pc_stop_following(sd); + return 0; + } + + // either player or target is currently detached from map blocks (could be teleporting), + // but still connected to this map, so we'll just increment the timer and check back later + if (sd->bl.prev != NULL && tbl->prev != NULL && + sd->ud.skilltimer == INVALID_TIMER && sd->ud.attacktimer == INVALID_TIMER && sd->ud.walktimer == INVALID_TIMER) + { + if((sd->bl.m == tbl->m) && unit_can_reach_bl(&sd->bl,tbl, AREA_SIZE, 0, NULL, NULL)) { + if (!check_distance_bl(&sd->bl, tbl, 5)) + unit_walktobl(&sd->bl, tbl, 5, 0); + } else + pc_setpos(sd, map_id2index(tbl->m), tbl->x, tbl->y, CLR_TELEPORT); + } + sd->followtimer = add_timer( + tick + 1000, // increase time a bit to loosen up map's load + pc_follow_timer, sd->bl.id, 0); + return 0; +} + +int pc_stop_following (struct map_session_data *sd) +{ + nullpo_ret(sd); + + if (sd->followtimer != INVALID_TIMER) { + delete_timer(sd->followtimer,pc_follow_timer); + sd->followtimer = INVALID_TIMER; + } + sd->followtarget = -1; + sd->ud.target_to = 0; + + return 0; +} + +int pc_follow(struct map_session_data *sd,int target_id) +{ + struct block_list *bl = map_id2bl(target_id); + if (bl == NULL /*|| bl->type != BL_PC*/) + return 1; + if (sd->followtimer != INVALID_TIMER) + pc_stop_following(sd); + + sd->followtarget = target_id; + pc_follow_timer(INVALID_TIMER, gettick(), sd->bl.id, 0); + + return 0; +} + +int pc_checkbaselevelup(struct map_session_data *sd) { + unsigned int next = pc_nextbaseexp(sd); + + if (!next || sd->status.base_exp < next) + return 0; + + do { + sd->status.base_exp -= next; + //Kyoki pointed out that the max overcarry exp is the exp needed for the previous level -1. [Skotlex] + if(!battle_config.multi_level_up && sd->status.base_exp > next-1) + sd->status.base_exp = next-1; + + next = pc_gets_status_point(sd->status.base_level); + sd->status.base_level ++; + sd->status.status_point += next; + + } while ((next=pc_nextbaseexp(sd)) > 0 && sd->status.base_exp >= next); + + if (battle_config.pet_lv_rate && sd->pd) //<Skotlex> update pet's level + status_calc_pet(sd->pd,0); + + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_NEXTBASEEXP); + status_calc_pc(sd,0); + status_percent_heal(&sd->bl,100,100); + + if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE) { + sc_start(&sd->bl,status_skill2sc(PR_KYRIE),100,1,skill_get_time(PR_KYRIE,1)); + sc_start(&sd->bl,status_skill2sc(PR_IMPOSITIO),100,1,skill_get_time(PR_IMPOSITIO,1)); + sc_start(&sd->bl,status_skill2sc(PR_MAGNIFICAT),100,1,skill_get_time(PR_MAGNIFICAT,1)); + sc_start(&sd->bl,status_skill2sc(PR_GLORIA),100,1,skill_get_time(PR_GLORIA,1)); + sc_start(&sd->bl,status_skill2sc(PR_SUFFRAGIUM),100,1,skill_get_time(PR_SUFFRAGIUM,1)); + if (sd->state.snovice_dead_flag) + sd->state.snovice_dead_flag = 0; //Reenable steelbody resurrection on dead. + } else if( (sd->class_&MAPID_BASEMASK) == MAPID_TAEKWON ) { + sc_start(&sd->bl,status_skill2sc(AL_INCAGI),100,10,600000); + sc_start(&sd->bl,status_skill2sc(AL_BLESSING),100,10,600000); + } + clif_misceffect(&sd->bl,0); + npc_script_event(sd, NPCE_BASELVUP); //LORDALFA - LVLUPEVENT + + if(sd->status.party_id) + party_send_levelup(sd); + + pc_baselevelchanged(sd); + return 1; +} + +void pc_baselevelchanged(struct map_session_data *sd) { +#ifdef RENEWAL + int i; + for( i = 0; i < EQI_MAX; i++ ) { + if( sd->equip_index[i] >= 0 ) { + if( sd->inventory_data[ sd->equip_index[i] ]->elvmax && sd->status.base_level > (unsigned int)sd->inventory_data[ sd->equip_index[i] ]->elvmax ) + pc_unequipitem(sd, sd->equip_index[i], 3); + } + } +#endif + +} +int pc_checkjoblevelup(struct map_session_data *sd) +{ + unsigned int next = pc_nextjobexp(sd); + + nullpo_ret(sd); + if(!next || sd->status.job_exp < next) + return 0; + + do { + sd->status.job_exp -= next; + //Kyoki pointed out that the max overcarry exp is the exp needed for the previous level -1. [Skotlex] + if(!battle_config.multi_level_up && sd->status.job_exp > next-1) + sd->status.job_exp = next-1; + + sd->status.job_level ++; + sd->status.skill_point ++; + + } while ((next=pc_nextjobexp(sd)) > 0 && sd->status.job_exp >= next); + + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + status_calc_pc(sd,0); + clif_misceffect(&sd->bl,1); + if (pc_checkskill(sd, SG_DEVIL) && !pc_nextjobexp(sd)) + clif_status_change(&sd->bl,SI_DEVIL, 1, 0, 0, 0, 1); //Permanent blind effect from SG_DEVIL. + + npc_script_event(sd, NPCE_JOBLVUP); + return 1; +} + +/*========================================== + * Alters experienced based on self bonuses that do not get even shared to the party. + *------------------------------------------*/ +static void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsigned int *job_exp, struct block_list *src) +{ + int bonus = 0; + struct status_data *status = status_get_status_data(src); + + if (sd->expaddrace[status->race]) + bonus += sd->expaddrace[status->race]; + bonus += sd->expaddrace[status->mode&MD_BOSS?RC_BOSS:RC_NONBOSS]; + + if (battle_config.pk_mode && + (int)(status_get_lv(src) - sd->status.base_level) >= 20) + bonus += 15; // pk_mode additional exp if monster >20 levels [Valaris] + + if (sd->sc.data[SC_EXPBOOST]) + bonus += sd->sc.data[SC_EXPBOOST]->val1; + + *base_exp = (unsigned int) cap_value(*base_exp + (double)*base_exp * bonus/100., 1, UINT_MAX); + + if (sd->sc.data[SC_JEXPBOOST]) + bonus += sd->sc.data[SC_JEXPBOOST]->val1; + + *job_exp = (unsigned int) cap_value(*job_exp + (double)*job_exp * bonus/100., 1, UINT_MAX); + + return; +} +/*========================================== + * Give x exp at sd player and calculate remaining exp for next lvl + *------------------------------------------*/ +int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int base_exp,unsigned int job_exp,bool quest) +{ + float nextbp=0, nextjp=0; + unsigned int nextb=0, nextj=0; + nullpo_ret(sd); + + if(sd->bl.prev == NULL || pc_isdead(sd)) + return 0; + + if(!battle_config.pvp_exp && map[sd->bl.m].flag.pvp) // [MouseJstr] + return 0; // no exp on pvp maps + + if(sd->status.guild_id>0) + base_exp-=guild_payexp(sd,base_exp); + + if(src) pc_calcexp(sd, &base_exp, &job_exp, src); + + nextb = pc_nextbaseexp(sd); + nextj = pc_nextjobexp(sd); + + if(sd->state.showexp || battle_config.max_exp_gain_rate){ + if (nextb > 0) + nextbp = (float) base_exp / (float) nextb; + if (nextj > 0) + nextjp = (float) job_exp / (float) nextj; + + if(battle_config.max_exp_gain_rate) { + if (nextbp > battle_config.max_exp_gain_rate/1000.) { + //Note that this value should never be greater than the original + //base_exp, therefore no overflow checks are needed. [Skotlex] + base_exp = (unsigned int)(battle_config.max_exp_gain_rate/1000.*nextb); + if (sd->state.showexp) + nextbp = (float) base_exp / (float) nextb; + } + if (nextjp > battle_config.max_exp_gain_rate/1000.) { + job_exp = (unsigned int)(battle_config.max_exp_gain_rate/1000.*nextj); + if (sd->state.showexp) + nextjp = (float) job_exp / (float) nextj; + } + } + } + + //Cap exp to the level up requirement of the previous level when you are at max level, otherwise cap at UINT_MAX (this is required for some S. Novice bonuses). [Skotlex] + if (base_exp) { + nextb = nextb?UINT_MAX:pc_thisbaseexp(sd); + if(sd->status.base_exp > nextb - base_exp) + sd->status.base_exp = nextb; + else + sd->status.base_exp += base_exp; + pc_checkbaselevelup(sd); + clif_updatestatus(sd,SP_BASEEXP); + } + + if (job_exp) { + nextj = nextj?UINT_MAX:pc_thisjobexp(sd); + if(sd->status.job_exp > nextj - job_exp) + sd->status.job_exp = nextj; + else + sd->status.job_exp += job_exp; + pc_checkjoblevelup(sd); + clif_updatestatus(sd,SP_JOBEXP); + } + + if(base_exp) + clif_displayexp(sd, base_exp, SP_BASEEXP, quest); + if(job_exp) + clif_displayexp(sd, job_exp, SP_JOBEXP, quest); + if(sd->state.showexp) { + char output[256]; + sprintf(output, + "Experience Gained Base:%u (%.2f%%) Job:%u (%.2f%%)",base_exp,nextbp*(float)100,job_exp,nextjp*(float)100); + clif_disp_onlyself(sd,output,strlen(output)); + } + + return 1; +} + +/*========================================== + * Returns max level for this character. + *------------------------------------------*/ +unsigned int pc_maxbaselv(struct map_session_data *sd) +{ + return max_level[pc_class2idx(sd->status.class_)][0]; +} + +unsigned int pc_maxjoblv(struct map_session_data *sd) +{ + return max_level[pc_class2idx(sd->status.class_)][1]; +} + +/*========================================== + * base level exp lookup. + *------------------------------------------*/ + +//Base exp needed for next level. +unsigned int pc_nextbaseexp(struct map_session_data *sd) +{ + nullpo_ret(sd); + + if(sd->status.base_level>=pc_maxbaselv(sd) || sd->status.base_level<=0) + return 0; + + return exp_table[pc_class2idx(sd->status.class_)][0][sd->status.base_level-1]; +} + +//Base exp needed for this level. +unsigned int pc_thisbaseexp(struct map_session_data *sd) +{ + if(sd->status.base_level>pc_maxbaselv(sd) || sd->status.base_level<=1) + return 0; + + return exp_table[pc_class2idx(sd->status.class_)][0][sd->status.base_level-2]; +} + + +/*========================================== + * job level exp lookup + * Return: + * 0 = not found + * x = exp for level + *------------------------------------------*/ + +//Job exp needed for next level. +unsigned int pc_nextjobexp(struct map_session_data *sd) +{ + nullpo_ret(sd); + + if(sd->status.job_level>=pc_maxjoblv(sd) || sd->status.job_level<=0) + return 0; + return exp_table[pc_class2idx(sd->status.class_)][1][sd->status.job_level-1]; +} + +//Job exp needed for this level. +unsigned int pc_thisjobexp(struct map_session_data *sd) +{ + if(sd->status.job_level>pc_maxjoblv(sd) || sd->status.job_level<=1) + return 0; + return exp_table[pc_class2idx(sd->status.class_)][1][sd->status.job_level-2]; +} + +/// Returns the value of the specified stat. +static int pc_getstat(struct map_session_data* sd, int type) +{ + nullpo_retr(-1, sd); + + switch( type ) { + case SP_STR: return sd->status.str; + case SP_AGI: return sd->status.agi; + case SP_VIT: return sd->status.vit; + case SP_INT: return sd->status.int_; + case SP_DEX: return sd->status.dex; + case SP_LUK: return sd->status.luk; + default: + return -1; + } +} + +/// Sets the specified stat to the specified value. +/// Returns the new value. +static int pc_setstat(struct map_session_data* sd, int type, int val) +{ + nullpo_retr(-1, sd); + + switch( type ) { + case SP_STR: sd->status.str = val; break; + case SP_AGI: sd->status.agi = val; break; + case SP_VIT: sd->status.vit = val; break; + case SP_INT: sd->status.int_ = val; break; + case SP_DEX: sd->status.dex = val; break; + case SP_LUK: sd->status.luk = val; break; + default: + return -1; + } + + return val; +} + +// Calculates the number of status points PC gets when leveling up (from level to level+1) +int pc_gets_status_point(int level) +{ + if (battle_config.use_statpoint_table) //Use values from "db/statpoint.txt" + return (statp[level+1] - statp[level]); + else //Default increase + return ((level+15) / 5); +} + +/// Returns the number of stat points needed to change the specified stat by val. +/// If val is negative, returns the number of stat points that would be needed to +/// raise the specified stat from (current value - val) to current value. +int pc_need_status_point(struct map_session_data* sd, int type, int val) +{ + int low, high, sp = 0; + + if ( val == 0 ) + return 0; + + low = pc_getstat(sd,type); + + if ( low >= pc_maxparameter(sd) && val > 0 ) + return 0; // Official servers show '0' when max is reached + + high = low + val; + + if ( val < 0 ) + swap(low, high); + + for ( ; low < high; low++ ) +#ifdef RENEWAL // renewal status point cost formula + sp += (low < 100) ? (2 + (low - 1) / 10) : (16 + 4 * ((low - 100) / 5)); +#else + sp += ( 1 + (low + 9) / 10 ); +#endif + + return sp; +} + +/// Raises a stat by 1. +/// Obeys max_parameter limits. +/// Subtracts stat points. +/// +/// @param type The stat to change (see enum _sp) +int pc_statusup(struct map_session_data* sd, int type) +{ + int max, need, val; + + nullpo_ret(sd); + + // check conditions + need = pc_need_status_point(sd,type,1); + if( type < SP_STR || type > SP_LUK || need < 0 || need > sd->status.status_point ) + { + clif_statusupack(sd,type,0,0); + return 1; + } + + // check limits + max = pc_maxparameter(sd); + if( pc_getstat(sd,type) >= max ) + { + clif_statusupack(sd,type,0,0); + return 1; + } + + // set new values + val = pc_setstat(sd, type, pc_getstat(sd,type) + 1); + sd->status.status_point -= need; + + status_calc_pc(sd,0); + + // update increase cost indicator + if( need != pc_need_status_point(sd,type,1) ) + clif_updatestatus(sd, SP_USTR + type-SP_STR); + + // update statpoint count + clif_updatestatus(sd,SP_STATUSPOINT); + + // update stat value + clif_statusupack(sd,type,1,val); // required + if( val > 255 ) + clif_updatestatus(sd,type); // send after the 'ack' to override the truncated value + + return 0; +} + +/// Raises a stat by the specified amount. +/// Obeys max_parameter limits. +/// Does not subtract stat points. +/// +/// @param type The stat to change (see enum _sp) +/// @param val The stat increase amount. +int pc_statusup2(struct map_session_data* sd, int type, int val) +{ + int max, need; + nullpo_ret(sd); + + if( type < SP_STR || type > SP_LUK ) + { + clif_statusupack(sd,type,0,0); + return 1; + } + + need = pc_need_status_point(sd,type,1); + + // set new value + max = pc_maxparameter(sd); + val = pc_setstat(sd, type, cap_value(pc_getstat(sd,type) + val, 1, max)); + + status_calc_pc(sd,0); + + // update increase cost indicator + if( need != pc_need_status_point(sd,type,1) ) + clif_updatestatus(sd, SP_USTR + type-SP_STR); + + // update stat value + clif_statusupack(sd,type,1,val); // required + if( val > 255 ) + clif_updatestatus(sd,type); // send after the 'ack' to override the truncated value + + return 0; +} + +/*========================================== + * Update skill_lv for player sd + * Skill point allocation + *------------------------------------------*/ +int pc_skillup(struct map_session_data *sd,uint16 skill_id) +{ + nullpo_ret(sd); + + if( skill_id >= GD_SKILLBASE && skill_id < GD_SKILLBASE+MAX_GUILDSKILL ) + { + guild_skillup(sd, skill_id); + return 0; + } + + if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE+MAX_HOMUNSKILL && sd->hd ) + { + merc_hom_skillup(sd->hd, skill_id); + return 0; + } + + if(skill_id >= MAX_SKILL ) + return 0; + + if( sd->status.skill_point > 0 && + sd->status.skill[skill_id].id && + sd->status.skill[skill_id].flag == SKILL_FLAG_PERMANENT && //Don't allow raising while you have granted skills. [Skotlex] + sd->status.skill[skill_id].lv < skill_tree_get_max(skill_id, sd->status.class_) ) + { + sd->status.skill[skill_id].lv++; + sd->status.skill_point--; + if( !skill_get_inf(skill_id) ) + status_calc_pc(sd,0); // Only recalculate for passive skills. + else if( sd->status.skill_point == 0 && (sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) ) + pc_calc_skilltree(sd); // Required to grant all TK Ranger skills. + else + pc_check_skilltree(sd, skill_id); // Check if a new skill can Lvlup + + clif_skillup(sd,skill_id); + clif_updatestatus(sd,SP_SKILLPOINT); + if( skill_id == GN_REMODELING_CART ) /* cart weight info was updated by status_calc_pc */ + clif_updatestatus(sd,SP_CARTINFO); + if (!pc_has_permission(sd, PC_PERM_ALL_SKILL)) // may skill everything at any time anyways, and this would cause a huge slowdown + clif_skillinfoblock(sd); + } + + return 0; +} + +/*========================================== + * /allskill + *------------------------------------------*/ +int pc_allskillup(struct map_session_data *sd) +{ + int i,id; + + nullpo_ret(sd); + + for(i=0;i<MAX_SKILL;i++){ + if (sd->status.skill[i].flag != SKILL_FLAG_PERMANENT && sd->status.skill[i].flag != SKILL_FLAG_PLAGIARIZED) { + sd->status.skill[i].lv = (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) ? 0 : sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0; + sd->status.skill[i].flag = SKILL_FLAG_PERMANENT; + if (sd->status.skill[i].lv == 0) + sd->status.skill[i].id = 0; + } + } + + if (pc_has_permission(sd, PC_PERM_ALL_SKILL)) + { //Get ALL skills except npc/guild ones. [Skotlex] + //and except SG_DEVIL [Komurka] and MO_TRIPLEATTACK and RG_SNATCHER [ultramage] + for(i=0;i<MAX_SKILL;i++){ + switch( i ) { + case SG_DEVIL: + case MO_TRIPLEATTACK: + case RG_SNATCHER: + continue; + default: + if( !(skill_get_inf2(i)&(INF2_NPC_SKILL|INF2_GUILD_SKILL)) ) + if ( ( sd->status.skill[i].lv = skill_get_max(i) ) )//Nonexistant skills should return a max of 0 anyway. + sd->status.skill[i].id = i; + } + } + } else { + int inf2; + for(i=0;i < MAX_SKILL_TREE && (id=skill_tree[pc_class2idx(sd->status.class_)][i].id)>0;i++){ + inf2 = skill_get_inf2(id); + if ( + (inf2&INF2_QUEST_SKILL && !battle_config.quest_skill_learn) || + (inf2&(INF2_WEDDING_SKILL|INF2_SPIRIT_SKILL)) || + id==SG_DEVIL + ) + continue; //Cannot be learned normally. + + sd->status.skill[id].id = id; + sd->status.skill[id].lv = skill_tree_get_max(id, sd->status.class_); // celest + } + } + status_calc_pc(sd,0); + //Required because if you could level up all skills previously, + //the update will not be sent as only the lv variable changes. + clif_skillinfoblock(sd); + return 0; +} + +/*========================================== + * /resetlvl + *------------------------------------------*/ +int pc_resetlvl(struct map_session_data* sd,int type) +{ + int i; + + nullpo_ret(sd); + + if (type != 3) //Also reset skills + pc_resetskill(sd, 0); + + if(type == 1){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=0; + sd->status.job_exp=0; + if(sd->sc.option !=0) + sd->sc.option = 0; + + sd->status.str=1; + sd->status.agi=1; + sd->status.vit=1; + sd->status.int_=1; + sd->status.dex=1; + sd->status.luk=1; + if(sd->status.class_ == JOB_NOVICE_HIGH) { + sd->status.status_point=100; // not 88 [celest] + // give platinum skills upon changing + pc_skill(sd,142,1,0); + pc_skill(sd,143,1,0); + } + } + + if(type == 2){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=0; + sd->status.job_exp=0; + } + if(type == 3){ + sd->status.base_level=1; + sd->status.base_exp=0; + } + if(type == 4){ + sd->status.job_level=1; + sd->status.job_exp=0; + } + + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + for(i=0;i<EQI_MAX;i++) { // unequip items that can't be equipped by base 1 [Valaris] + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],2); + } + + if ((type == 1 || type == 2 || type == 3) && sd->status.party_id) + party_send_levelup(sd); + + status_calc_pc(sd,0); + clif_skillinfoblock(sd); + + return 0; +} +/*========================================== + * /resetstate + *------------------------------------------*/ +int pc_resetstate(struct map_session_data* sd) +{ + nullpo_ret(sd); + + if (battle_config.use_statpoint_table) + { // New statpoint table used here - Dexity + if (sd->status.base_level > MAX_LEVEL) + { //statp[] goes out of bounds, can't reset! + ShowError("pc_resetstate: Can't reset stats of %d:%d, the base level (%d) is greater than the max level supported (%d)\n", + sd->status.account_id, sd->status.char_id, sd->status.base_level, MAX_LEVEL); + return 0; + } + + sd->status.status_point = statp[sd->status.base_level] + ( sd->class_&JOBL_UPPER ? 52 : 0 ); // extra 52+48=100 stat points + } + else + { + int add=0; + add += pc_need_status_point(sd, SP_STR, 1-pc_getstat(sd, SP_STR)); + add += pc_need_status_point(sd, SP_AGI, 1-pc_getstat(sd, SP_AGI)); + add += pc_need_status_point(sd, SP_VIT, 1-pc_getstat(sd, SP_VIT)); + add += pc_need_status_point(sd, SP_INT, 1-pc_getstat(sd, SP_INT)); + add += pc_need_status_point(sd, SP_DEX, 1-pc_getstat(sd, SP_DEX)); + add += pc_need_status_point(sd, SP_LUK, 1-pc_getstat(sd, SP_LUK)); + + sd->status.status_point+=add; + } + + pc_setstat(sd, SP_STR, 1); + pc_setstat(sd, SP_AGI, 1); + pc_setstat(sd, SP_VIT, 1); + pc_setstat(sd, SP_INT, 1); + pc_setstat(sd, SP_DEX, 1); + pc_setstat(sd, SP_LUK, 1); + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + clif_updatestatus(sd,SP_STATUSPOINT); + + if( sd->mission_mobid ) { //bugreport:2200 + sd->mission_mobid = 0; + sd->mission_count = 0; + pc_setglobalreg(sd,"TK_MISSION_ID", 0); + } + + status_calc_pc(sd,0); + + return 1; +} + +/*========================================== + * /resetskill + * if flag&1, perform block resync and status_calc call. + * if flag&2, just count total amount of skill points used by player, do not really reset. + * if flag&4, just reset the skills if the player class is a bard/dancer type (for changesex.) + *------------------------------------------*/ +int pc_resetskill(struct map_session_data* sd, int flag) +{ + int i, lv, inf2, skill_point=0; + nullpo_ret(sd); + + if( flag&4 && (sd->class_&MAPID_UPPERMASK) != MAPID_BARDDANCER ) + return 0; + + if( !(flag&2) ) { //Remove stuff lost when resetting skills. + + /** + * It has been confirmed on official server that when you reset skills with a ranked tweakwon your skills are not reset (because you have all of them anyway) + **/ + if( (sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) ) + return 0; + + if( pc_checkskill(sd, SG_DEVIL) && !pc_nextjobexp(sd) ) + clif_status_load(&sd->bl, SI_DEVIL, 0); //Remove perma blindness due to skill-reset. [Skotlex] + i = sd->sc.option; + if( i&OPTION_RIDING && pc_checkskill(sd, KN_RIDING) ) + i &= ~OPTION_RIDING; + if( i&OPTION_FALCON && pc_checkskill(sd, HT_FALCON) ) + i &= ~OPTION_FALCON; + if( i&OPTION_DRAGON && pc_checkskill(sd, RK_DRAGONTRAINING) ) + i &= ~OPTION_DRAGON; + if( i&OPTION_WUG && pc_checkskill(sd, RA_WUGMASTERY) ) + i &= ~OPTION_WUG; + if( i&OPTION_WUGRIDER && pc_checkskill(sd, RA_WUGRIDER) ) + i &= ~OPTION_WUGRIDER; + if( i&OPTION_MADOGEAR && ( sd->class_&MAPID_THIRDMASK ) == MAPID_MECHANIC ) + i &= ~OPTION_MADOGEAR; + if( i&OPTION_MOUNTING ) + i &= ~OPTION_MOUNTING; +#ifndef NEW_CARTS + if( i&OPTION_CART && pc_checkskill(sd, MC_PUSHCART) ) + i &= ~OPTION_CART; +#else + if( sd->sc.data[SC_PUSH_CART] ) + pc_setcart(sd, 0); +#endif + if( i != sd->sc.option ) + pc_setoption(sd, i); + + if( merc_is_hom_active(sd->hd) && pc_checkskill(sd, AM_CALLHOMUN) ) + merc_hom_vaporize(sd, 0); + } + + for( i = 1; i < MAX_SKILL; i++ ) + { + lv = sd->status.skill[i].lv; + if (lv < 1) continue; + + inf2 = skill_get_inf2(i); + + if( inf2&(INF2_WEDDING_SKILL|INF2_SPIRIT_SKILL) ) //Avoid reseting wedding/linker skills. + continue; + + // Don't reset trick dead if not a novice/baby + if( i == NV_TRICKDEAD && (sd->class_&MAPID_UPPERMASK) != MAPID_NOVICE ) + { + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + continue; + } + + // do not reset basic skill + if( i == NV_BASIC && (sd->class_&MAPID_UPPERMASK) != MAPID_NOVICE ) + continue; + + if( flag&4 && !skill_ischangesex(i) ) + continue; + + if( inf2&INF2_QUEST_SKILL && !battle_config.quest_skill_learn ) + { //Only handle quest skills in a special way when you can't learn them manually + if( battle_config.quest_skill_reset && !(flag&2) ) + { //Wipe them + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + } + continue; + } + if( sd->status.skill[i].flag == SKILL_FLAG_PERMANENT ) + skill_point += lv; + else + if( sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0 ) + skill_point += (sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0); + + if( !(flag&2) ) + {// reset + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + } + } + + if( flag&2 || !skill_point ) return skill_point; + + sd->status.skill_point += skill_point; + + if( flag&1 ) + { + clif_updatestatus(sd,SP_SKILLPOINT); + clif_skillinfoblock(sd); + status_calc_pc(sd,0); + } + + return skill_point; +} + +/*========================================== + * /resetfeel [Komurka] + *------------------------------------------*/ +int pc_resetfeel(struct map_session_data* sd) +{ + int i; + nullpo_ret(sd); + + for (i=0; i<MAX_PC_FEELHATE; i++) + { + sd->feel_map[i].m = -1; + sd->feel_map[i].index = 0; + pc_setglobalreg(sd,sg_info[i].feel_var,0); + } + + return 0; +} + +int pc_resethate(struct map_session_data* sd) +{ + int i; + nullpo_ret(sd); + + for (i=0; i<3; i++) + { + sd->hate_mob[i] = -1; + pc_setglobalreg(sd,sg_info[i].hate_var,0); + } + return 0; +} + +int pc_skillatk_bonus(struct map_session_data *sd, uint16 skill_id) +{ + int i, bonus = 0; + nullpo_ret(sd); + + ARR_FIND(0, ARRAYLENGTH(sd->skillatk), i, sd->skillatk[i].id == skill_id); + if( i < ARRAYLENGTH(sd->skillatk) ) bonus = sd->skillatk[i].val; + + if(sd->sc.data[SC_PYROTECHNIC_OPTION] || sd->sc.data[SC_AQUAPLAY_OPTION]) + bonus += 10; + + return bonus; +} + +int pc_skillheal_bonus(struct map_session_data *sd, uint16 skill_id) { + int i, bonus = sd->bonus.add_heal_rate; + + if( bonus ) { + switch( skill_id ) { + case AL_HEAL: if( !(battle_config.skill_add_heal_rate&1) ) bonus = 0; break; + case PR_SANCTUARY: if( !(battle_config.skill_add_heal_rate&2) ) bonus = 0; break; + case AM_POTIONPITCHER: if( !(battle_config.skill_add_heal_rate&4) ) bonus = 0; break; + case CR_SLIMPITCHER: if( !(battle_config.skill_add_heal_rate&8) ) bonus = 0; break; + case BA_APPLEIDUN: if( !(battle_config.skill_add_heal_rate&16)) bonus = 0; break; + } + } + + ARR_FIND(0, ARRAYLENGTH(sd->skillheal), i, sd->skillheal[i].id == skill_id); + + if( i < ARRAYLENGTH(sd->skillheal) ) + bonus += sd->skillheal[i].val; + + return bonus; +} + +int pc_skillheal2_bonus(struct map_session_data *sd, uint16 skill_id) { + int i, bonus = sd->bonus.add_heal2_rate; + + ARR_FIND(0, ARRAYLENGTH(sd->skillheal2), i, sd->skillheal2[i].id == skill_id); + + if( i < ARRAYLENGTH(sd->skillheal2) ) + bonus += sd->skillheal2[i].val; + + return bonus; +} + +void pc_respawn(struct map_session_data* sd, clr_type clrtype) +{ + if( !pc_isdead(sd) ) + return; // not applicable + if( sd->bg_id && bg_member_respawn(sd) ) + return; // member revived by battleground + + pc_setstand(sd); + pc_setrestartvalue(sd,3); + if( pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, clrtype) ) + clif_resurrection(&sd->bl, 1); //If warping fails, send a normal stand up packet. +} + +static int pc_respawn_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = map_id2sd(id); + if( sd != NULL ) + { + sd->pvp_point=0; + pc_respawn(sd,CLR_OUTSIGHT); + } + + return 0; +} + +/*========================================== + * Invoked when a player has received damage + *------------------------------------------*/ +void pc_damage(struct map_session_data *sd,struct block_list *src,unsigned int hp, unsigned int sp) +{ + if (sp) clif_updatestatus(sd,SP_SP); + if (hp) clif_updatestatus(sd,SP_HP); + else return; + + if( !src || src == &sd->bl ) + return; + + if( pc_issit(sd) ) + { + pc_setstand(sd); + skill_sit(sd,0); + } + + if( sd->progressbar.npc_id ) + clif_progressbar_abort(sd); + + if( sd->status.pet_id > 0 && sd->pd && battle_config.pet_damage_support ) + pet_target_check(sd,src,1); + + if( sd->status.ele_id > 0 ) + elemental_set_target(sd,src); + + sd->canlog_tick = gettick(); +} + +/*========================================== + * Invoked when a player has negative current hp + *------------------------------------------*/ +int pc_dead(struct map_session_data *sd,struct block_list *src) +{ + int i=0,j=0,k=0; + unsigned int tick = gettick(); + + for(k = 0; k < 5; k++) + if (sd->devotion[k]){ + struct map_session_data *devsd = map_id2sd(sd->devotion[k]); + if (devsd) + status_change_end(&devsd->bl, SC_DEVOTION, INVALID_TIMER); + sd->devotion[k] = 0; + } + + if(sd->status.pet_id > 0 && sd->pd) { + struct pet_data *pd = sd->pd; + if( !map[sd->bl.m].flag.noexppenalty ) { + pet_set_intimate(pd, pd->pet.intimate - pd->petDB->die); + if( pd->pet.intimate < 0 ) + pd->pet.intimate = 0; + clif_send_petdata(sd,sd->pd,1,pd->pet.intimate); + } + if( sd->pd->target_id ) // Unlock all targets... + pet_unlocktarget(sd->pd); + } + + if (sd->status.hom_id > 0){ + if(battle_config.homunculus_auto_vapor && sd->hd && !sd->hd->sc.data[SC_LIGHT_OF_REGENE]) + merc_hom_vaporize(sd, 0); + } + + if( sd->md ) + merc_delete(sd->md, 3); // Your mercenary soldier has ran away. + + if( sd->ed ) + elemental_delete(sd->ed, 0); + + // Leave duel if you die [LuzZza] + if(battle_config.duel_autoleave_when_die) { + if(sd->duel_group > 0) + duel_leave(sd->duel_group, sd); + if(sd->duel_invite > 0) + duel_reject(sd->duel_invite, sd); + } + + pc_setglobalreg(sd,"PC_DIE_COUNTER",sd->die_counter+1); + pc_setparam(sd, SP_KILLERRID, src?src->id:0); + + if( sd->bg_id ) { + struct battleground_data *bg; + if( (bg = bg_team_search(sd->bg_id)) != NULL && bg->die_event[0] ) + npc_event(sd, bg->die_event, 0); + } + + // Clear anything NPC-related when you die and was interacting with one. + if (sd->npc_id) + { + if (sd->state.using_fake_npc) { + clif_clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd); + sd->state.using_fake_npc = 0; + } + if (sd->state.menu_or_input) + sd->state.menu_or_input = 0; + if (sd->npc_menu) + sd->npc_menu = 0; + + sd->npc_id = 0; + if (sd->st && sd->st->state != END) + sd->st->state = END; + } + + npc_script_event(sd,NPCE_DIE); + + /* e.g. not killed thru pc_damage */ + if( pc_issit(sd) ) { + clif_status_load(&sd->bl,SI_SIT,0); + } + + pc_setdead(sd); + //Reset menu skills/item skills + if (sd->skillitem) + sd->skillitem = sd->skillitemlv = 0; + if (sd->menuskill_id) + sd->menuskill_id = sd->menuskill_val = 0; + //Reset ticks. + sd->hp_loss.tick = sd->sp_loss.tick = sd->hp_regen.tick = sd->sp_regen.tick = 0; + + if ( sd && sd->spiritball ) + pc_delspiritball(sd,sd->spiritball,0); + + for(i = 1; i < 5; i++) + pc_del_talisman(sd, sd->talisman[i], i); + + if (src) + switch (src->type) { + case BL_MOB: + { + struct mob_data *md=(struct mob_data *)src; + if(md->target_id==sd->bl.id) + mob_unlocktarget(md,tick); + if(battle_config.mobs_level_up && md->status.hp && + (unsigned int)md->level < pc_maxbaselv(sd) && + !md->guardian_data && !md->special_state.ai// Guardians/summons should not level. [Skotlex] + ) { // monster level up [Valaris] + clif_misceffect(&md->bl,0); + md->level++; + status_calc_mob(md, 0); + status_percent_heal(src,10,0); + + if( battle_config.show_mob_info&4 ) + {// update name with new level + clif_charnameack(0, &md->bl); + } + } + src = battle_get_master(src); // Maybe Player Summon + } + break; + case BL_PET: //Pass on to master... + src = &((TBL_PET*)src)->msd->bl; + break; + case BL_HOM: + src = &((TBL_HOM*)src)->master->bl; + break; + case BL_MER: + src = &((TBL_MER*)src)->master->bl; + break; + } + + if (src && src->type == BL_PC) + { + struct map_session_data *ssd = (struct map_session_data *)src; + pc_setparam(ssd, SP_KILLEDRID, sd->bl.id); + npc_script_event(ssd, NPCE_KILLPC); + + if (battle_config.pk_mode&2) { + ssd->status.manner -= 5; + if(ssd->status.manner < 0) + sc_start(src,SC_NOCHAT,100,0,0); +#if 0 + // PK/Karma system code (not enabled yet) [celest] + // originally from Kade Online, so i don't know if any of these is correct ^^; + // note: karma is measured REVERSE, so more karma = more 'evil' / less honourable, + // karma going down = more 'good' / more honourable. + // The Karma System way... + + if (sd->status.karma > ssd->status.karma) { // If player killed was more evil + sd->status.karma--; + ssd->status.karma--; + } + else if (sd->status.karma < ssd->status.karma) // If player killed was more good + ssd->status.karma++; + + + // or the PK System way... + + if (sd->status.karma > 0) // player killed is dishonourable? + ssd->status.karma--; // honour points earned + sd->status.karma++; // honour points lost + + // To-do: Receive exp on certain occasions +#endif + } + } + + if(battle_config.bone_drop==2 + || (battle_config.bone_drop==1 && map[sd->bl.m].flag.pvp)) + { + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=ITEMID_SKULL_; + item_tmp.identify=1; + item_tmp.card[0]=CARD0_CREATE; + item_tmp.card[1]=0; + item_tmp.card[2]=GetWord(sd->status.char_id,0); // CharId + item_tmp.card[3]=GetWord(sd->status.char_id,1); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + + // activate Steel body if a super novice dies at 99+% exp [celest] + if ((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && !sd->state.snovice_dead_flag) + { + unsigned int next = pc_nextbaseexp(sd); + if( next == 0 ) next = pc_thisbaseexp(sd); + if( get_percentage(sd->status.base_exp,next) >= 99 ) { + sd->state.snovice_dead_flag = 1; + pc_setstand(sd); + status_percent_heal(&sd->bl, 100, 100); + clif_resurrection(&sd->bl, 1); + if(battle_config.pc_invincible_time) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + sc_start(&sd->bl,status_skill2sc(MO_STEELBODY),100,1,skill_get_time(MO_STEELBODY,1)); + if(map_flag_gvg(sd->bl.m)) + pc_respawn_timer(INVALID_TIMER, gettick(), sd->bl.id, 0); + return 0; + } + } + + // changed penalty options, added death by player if pk_mode [Valaris] + if(battle_config.death_penalty_type + && (sd->class_&MAPID_UPPERMASK) != MAPID_NOVICE // only novices will receive no penalty + && !map[sd->bl.m].flag.noexppenalty && !map_flag_gvg(sd->bl.m) + && !sd->sc.data[SC_BABY] && !sd->sc.data[SC_LIFEINSURANCE]) + { + unsigned int base_penalty =0; + if (battle_config.death_penalty_base > 0) { + switch (battle_config.death_penalty_type) { + case 1: + base_penalty = (unsigned int) ((double)pc_nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000); + break; + case 2: + base_penalty = (unsigned int) ((double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000); + break; + } + if(base_penalty) { + if (battle_config.pk_mode && src && src->type==BL_PC) + base_penalty*=2; + sd->status.base_exp -= min(sd->status.base_exp, base_penalty); + clif_updatestatus(sd,SP_BASEEXP); + } + } + if(battle_config.death_penalty_job > 0) + { + base_penalty = 0; + switch (battle_config.death_penalty_type) { + case 1: + base_penalty = (unsigned int) ((double)pc_nextjobexp(sd) * (double)battle_config.death_penalty_job/10000); + break; + case 2: + base_penalty = (unsigned int) ((double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000); + break; + } + if(base_penalty) { + if (battle_config.pk_mode && src && src->type==BL_PC) + base_penalty*=2; + sd->status.job_exp -= min(sd->status.job_exp, base_penalty); + clif_updatestatus(sd,SP_JOBEXP); + } + } + if(battle_config.zeny_penalty > 0 && !map[sd->bl.m].flag.nozenypenalty) + { + base_penalty = (unsigned int)((double)sd->status.zeny * (double)battle_config.zeny_penalty / 10000.); + if(base_penalty) + pc_payzeny(sd, base_penalty, LOG_TYPE_PICKDROP_PLAYER, NULL); + } + } + + if(map[sd->bl.m].flag.pvp_nightmaredrop) + { // Moved this outside so it works when PVP isn't enabled and during pk mode [Ancyker] + for(j=0;j<MAX_DROP_PER_MAP;j++){ + int id = map[sd->bl.m].drop_list[j].drop_id; + int type = map[sd->bl.m].drop_list[j].drop_type; + int per = map[sd->bl.m].drop_list[j].drop_per; + if(id == 0) + continue; + if(id == -1){ + int eq_num=0,eq_n[MAX_INVENTORY]; + memset(eq_n,0,sizeof(eq_n)); + for(i=0;i<MAX_INVENTORY;i++){ + int k; + if( (type == 1 && !sd->status.inventory[i].equip) + || (type == 2 && sd->status.inventory[i].equip) + || type == 3) + { + ARR_FIND( 0, MAX_INVENTORY, k, eq_n[k] <= 0 ); + if( k < MAX_INVENTORY ) + eq_n[k] = i; + + eq_num++; + } + } + if(eq_num > 0){ + int n = eq_n[rnd()%eq_num]; + if(rnd()%10000 < per){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,3); + pc_dropitem(sd,n,1); + } + } + } + else if(id > 0){ + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid == id + && rnd()%10000 < per + && ((type == 1 && !sd->status.inventory[i].equip) + || (type == 2 && sd->status.inventory[i].equip) + || type == 3) ){ + if(sd->status.inventory[i].equip) + pc_unequipitem(sd,i,3); + pc_dropitem(sd,i,1); + break; + } + } + } + } + } + // pvp + // disable certain pvp functions on pk_mode [Valaris] + if( map[sd->bl.m].flag.pvp && !battle_config.pk_mode && !map[sd->bl.m].flag.pvp_nocalcrank ) + { + sd->pvp_point -= 5; + sd->pvp_lost++; + if( src && src->type == BL_PC ) + { + struct map_session_data *ssd = (struct map_session_data *)src; + ssd->pvp_point++; + ssd->pvp_won++; + } + if( sd->pvp_point < 0 ) + { + add_timer(tick+1000, pc_respawn_timer,sd->bl.id,0); + return 1|8; + } + } + //GvG + if( map_flag_gvg(sd->bl.m) ) + { + add_timer(tick+1000, pc_respawn_timer, sd->bl.id, 0); + return 1|8; + } + else if( sd->bg_id ) + { + struct battleground_data *bg = bg_team_search(sd->bg_id); + if( bg && bg->mapindex > 0 ) + { // Respawn by BG + add_timer(tick+1000, pc_respawn_timer, sd->bl.id, 0); + return 1|8; + } + } + + + //Reset "can log out" tick. + if( battle_config.prevent_logout ) + sd->canlog_tick = gettick() - battle_config.prevent_logout; + return 1; +} + +void pc_revive(struct map_session_data *sd,unsigned int hp, unsigned int sp) { + if(hp) clif_updatestatus(sd,SP_HP); + if(sp) clif_updatestatus(sd,SP_SP); + + pc_setstand(sd); + if(battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + + if( sd->state.gmaster_flag ) { + guild_guildaura_refresh(sd,GD_LEADERSHIP,guild_checkskill(sd->state.gmaster_flag,GD_LEADERSHIP)); + guild_guildaura_refresh(sd,GD_GLORYWOUNDS,guild_checkskill(sd->state.gmaster_flag,GD_GLORYWOUNDS)); + guild_guildaura_refresh(sd,GD_SOULCOLD,guild_checkskill(sd->state.gmaster_flag,GD_SOULCOLD)); + guild_guildaura_refresh(sd,GD_HAWKEYES,guild_checkskill(sd->state.gmaster_flag,GD_HAWKEYES)); + } +} +// script +// +/*========================================== + * script reading pc status registry + *------------------------------------------*/ +int pc_readparam(struct map_session_data* sd,int type) +{ + int val = 0; + + nullpo_ret(sd); + + switch(type) { + case SP_SKILLPOINT: val = sd->status.skill_point; break; + case SP_STATUSPOINT: val = sd->status.status_point; break; + case SP_ZENY: val = sd->status.zeny; break; + case SP_BASELEVEL: val = sd->status.base_level; break; + case SP_JOBLEVEL: val = sd->status.job_level; break; + case SP_CLASS: val = sd->status.class_; break; + case SP_BASEJOB: val = pc_mapid2jobid(sd->class_&MAPID_UPPERMASK, sd->status.sex); break; //Base job, extracting upper type. + case SP_UPPER: val = sd->class_&JOBL_UPPER?1:(sd->class_&JOBL_BABY?2:0); break; + case SP_BASECLASS: val = pc_mapid2jobid(sd->class_&MAPID_BASEMASK, sd->status.sex); break; //Extract base class tree. [Skotlex] + case SP_SEX: val = sd->status.sex; break; + case SP_WEIGHT: val = sd->weight; break; + case SP_MAXWEIGHT: val = sd->max_weight; break; + case SP_BASEEXP: val = sd->status.base_exp; break; + case SP_JOBEXP: val = sd->status.job_exp; break; + case SP_NEXTBASEEXP: val = pc_nextbaseexp(sd); break; + case SP_NEXTJOBEXP: val = pc_nextjobexp(sd); break; + case SP_HP: val = sd->battle_status.hp; break; + case SP_MAXHP: val = sd->battle_status.max_hp; break; + case SP_SP: val = sd->battle_status.sp; break; + case SP_MAXSP: val = sd->battle_status.max_sp; break; + case SP_STR: val = sd->status.str; break; + case SP_AGI: val = sd->status.agi; break; + case SP_VIT: val = sd->status.vit; break; + case SP_INT: val = sd->status.int_; break; + case SP_DEX: val = sd->status.dex; break; + case SP_LUK: val = sd->status.luk; break; + case SP_KARMA: val = sd->status.karma; break; + case SP_MANNER: val = sd->status.manner; break; + case SP_FAME: val = sd->status.fame; break; + case SP_KILLERRID: val = sd->killerrid; break; + case SP_KILLEDRID: val = sd->killedrid; break; + case SP_CRITICAL: val = sd->battle_status.cri/10; break; + case SP_ASPD: val = (2000-sd->battle_status.amotion)/10; break; + } + + return val; +} + +/*========================================== + * script set pc status registry + *------------------------------------------*/ +int pc_setparam(struct map_session_data *sd,int type,int val) +{ + int i = 0; + + nullpo_ret(sd); + + switch(type){ + case SP_BASELEVEL: + if ((unsigned int)val > pc_maxbaselv(sd)) //Capping to max + val = pc_maxbaselv(sd); + if ((unsigned int)val > sd->status.base_level) { + int stat=0; + for (i = 0; i < (int)((unsigned int)val - sd->status.base_level); i++) + stat += pc_gets_status_point(sd->status.base_level + i); + sd->status.status_point += stat; + } + sd->status.base_level = (unsigned int)val; + sd->status.base_exp = 0; + // clif_updatestatus(sd, SP_BASELEVEL); // Gets updated at the bottom + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + clif_updatestatus(sd, SP_BASEEXP); + status_calc_pc(sd, 0); + if(sd->status.party_id) + { + party_send_levelup(sd); + } + break; + case SP_JOBLEVEL: + if ((unsigned int)val >= sd->status.job_level) { + if ((unsigned int)val > pc_maxjoblv(sd)) val = pc_maxjoblv(sd); + sd->status.skill_point += val - sd->status.job_level; + clif_updatestatus(sd, SP_SKILLPOINT); + } + sd->status.job_level = (unsigned int)val; + sd->status.job_exp = 0; + // clif_updatestatus(sd, SP_JOBLEVEL); // Gets updated at the bottom + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_JOBEXP); + status_calc_pc(sd, 0); + break; + case SP_SKILLPOINT: + sd->status.skill_point = val; + break; + case SP_STATUSPOINT: + sd->status.status_point = val; + break; + case SP_ZENY: + if( val < 0 ) + return 0;// can't set negative zeny + log_zeny(sd, LOG_TYPE_SCRIPT, sd, -(sd->status.zeny - cap_value(val, 0, MAX_ZENY))); + sd->status.zeny = cap_value(val, 0, MAX_ZENY); + break; + case SP_BASEEXP: + if(pc_nextbaseexp(sd) > 0) { + sd->status.base_exp = val; + pc_checkbaselevelup(sd); + } + break; + case SP_JOBEXP: + if(pc_nextjobexp(sd) > 0) { + sd->status.job_exp = val; + pc_checkjoblevelup(sd); + } + break; + case SP_SEX: + sd->status.sex = val ? SEX_MALE : SEX_FEMALE; + break; + case SP_WEIGHT: + sd->weight = val; + break; + case SP_MAXWEIGHT: + sd->max_weight = val; + break; + case SP_HP: + sd->battle_status.hp = cap_value(val, 1, (int)sd->battle_status.max_hp); + break; + case SP_MAXHP: + sd->battle_status.max_hp = cap_value(val, 1, battle_config.max_hp); + + if( sd->battle_status.max_hp < sd->battle_status.hp ) + { + sd->battle_status.hp = sd->battle_status.max_hp; + clif_updatestatus(sd, SP_HP); + } + break; + case SP_SP: + sd->battle_status.sp = cap_value(val, 0, (int)sd->battle_status.max_sp); + break; + case SP_MAXSP: + sd->battle_status.max_sp = cap_value(val, 1, battle_config.max_sp); + + if( sd->battle_status.max_sp < sd->battle_status.sp ) + { + sd->battle_status.sp = sd->battle_status.max_sp; + clif_updatestatus(sd, SP_SP); + } + break; + case SP_STR: + sd->status.str = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_AGI: + sd->status.agi = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_VIT: + sd->status.vit = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_INT: + sd->status.int_ = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_DEX: + sd->status.dex = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_LUK: + sd->status.luk = cap_value(val, 1, pc_maxparameter(sd)); + break; + case SP_KARMA: + sd->status.karma = val; + break; + case SP_MANNER: + sd->status.manner = val; + break; + case SP_FAME: + sd->status.fame = val; + break; + case SP_KILLERRID: + sd->killerrid = val; + return 1; + case SP_KILLEDRID: + sd->killedrid = val; + return 1; + default: + ShowError("pc_setparam: Attempted to set unknown parameter '%d'.\n", type); + return 0; + } + clif_updatestatus(sd,type); + + return 1; +} + +/*========================================== + * HP/SP Healing. If flag is passed, the heal type is through clif_heal, otherwise update status. + *------------------------------------------*/ +void pc_heal(struct map_session_data *sd,unsigned int hp,unsigned int sp, int type) +{ + if (type) { + if (hp) + clif_heal(sd->fd,SP_HP,hp); + if (sp) + clif_heal(sd->fd,SP_SP,sp); + } else { + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + } + return; +} + +/*========================================== + * HP/SP Recovery + * Heal player hp and/or sp linearly. + * Calculate bonus by status. + *------------------------------------------*/ +int pc_itemheal(struct map_session_data *sd,int itemid, int hp,int sp) +{ + int i, bonus; + + if(hp) { + bonus = 100 + (sd->battle_status.vit<<1) + + pc_checkskill(sd,SM_RECOVERY)*10 + + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + // A potion produced by an Alchemist in the Fame Top 10 gets +50% effect [DracoRPG] + if (potion_flag > 1) + bonus += bonus*(potion_flag-1)*50/100; + //All item bonuses. + bonus += sd->bonus.itemhealrate2; + //Item Group bonuses + bonus += bonus*itemdb_group_bonus(sd, itemid)/100; + //Individual item bonuses. + for(i = 0; i < ARRAYLENGTH(sd->itemhealrate) && sd->itemhealrate[i].nameid; i++) + { + if (sd->itemhealrate[i].nameid == itemid) { + bonus += bonus*sd->itemhealrate[i].rate/100; + break; + } + } + if(bonus!=100) + hp = hp * bonus / 100; + + // Recovery Potion + if( sd->sc.data[SC_INCHEALRATE] ) + hp += (int)(hp * sd->sc.data[SC_INCHEALRATE]->val1/100.); + } + if(sp) { + bonus = 100 + (sd->battle_status.int_<<1) + + pc_checkskill(sd,MG_SRECOVERY)*10 + + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + if (potion_flag > 1) + bonus += bonus*(potion_flag-1)*50/100; + if(bonus != 100) + sp = sp * bonus / 100; + } + if( sd->sc.count ) { + if ( sd->sc.data[SC_CRITICALWOUND] ) { + hp -= hp * sd->sc.data[SC_CRITICALWOUND]->val2 / 100; + sp -= sp * sd->sc.data[SC_CRITICALWOUND]->val2 / 100; + } + + if ( sd->sc.data[SC_DEATHHURT] ) { + hp -= hp * 20 / 100; + sp -= sp * 20 / 100; + } + + if( sd->sc.data[SC_WATER_INSIGNIA] && sd->sc.data[SC_WATER_INSIGNIA]->val1 == 2 ) { + hp += hp / 10; + sp += sp / 10; + } +#ifdef RENEWAL + if( sd->sc.data[SC_EXTREMITYFIST2] ) + sp = 0; +#endif + } + + return status_heal(&sd->bl, hp, sp, 1); +} + +/*========================================== + * HP/SP Recovery + * Heal player hp nad/or sp by rate + *------------------------------------------*/ +int pc_percentheal(struct map_session_data *sd,int hp,int sp) +{ + nullpo_ret(sd); + + if(hp > 100) hp = 100; + else + if(hp <-100) hp =-100; + + if(sp > 100) sp = 100; + else + if(sp <-100) sp =-100; + + if(hp >= 0 && sp >= 0) //Heal + return status_percent_heal(&sd->bl, hp, sp); + + if(hp <= 0 && sp <= 0) //Damage (negative rates indicate % of max rather than current), and only kill target IF the specified amount is 100% + return status_percent_damage(NULL, &sd->bl, hp, sp, hp==-100); + + //Crossed signs + if(hp) { + if(hp > 0) + status_percent_heal(&sd->bl, hp, 0); + else + status_percent_damage(NULL, &sd->bl, hp, 0, hp==-100); + } + + if(sp) { + if(sp > 0) + status_percent_heal(&sd->bl, 0, sp); + else + status_percent_damage(NULL, &sd->bl, 0, sp, false); + } + return 0; +} + +static int jobchange_killclone(struct block_list *bl, va_list ap) +{ + struct mob_data *md; + int flag; + md = (struct mob_data *)bl; + nullpo_ret(md); + flag = va_arg(ap, int); + + if (md->master_id && md->special_state.clone && md->master_id == flag) + status_kill(&md->bl); + return 1; +} + +/*========================================== + * Called when player changes job + * Rewrote to make it tidider [Celest] + *------------------------------------------*/ +int pc_jobchange(struct map_session_data *sd,int job, int upper) +{ + int i, fame_flag=0; + int b_class; + + nullpo_ret(sd); + + if (job < 0) + return 1; + + //Normalize job. + b_class = pc_jobid2mapid(job); + if (b_class == -1) + return 1; + switch (upper) { + case 1: + b_class|= JOBL_UPPER; + break; + case 2: + b_class|= JOBL_BABY; + break; + } + //This will automatically adjust bard/dancer classes to the correct gender + //That is, if you try to jobchange into dancer, it will turn you to bard. + job = pc_mapid2jobid(b_class, sd->status.sex); + if (job == -1) + return 1; + + if ((unsigned short)b_class == sd->class_) + return 1; //Nothing to change. + + // changing from 1st to 2nd job + if ((b_class&JOBL_2) && !(sd->class_&JOBL_2) && (b_class&MAPID_UPPERMASK) != MAPID_SUPER_NOVICE) { + sd->change_level_2nd = sd->status.job_level; + pc_setglobalreg (sd, "jobchange_level", sd->change_level_2nd); + } + // changing from 2nd to 3rd job + else if((b_class&JOBL_THIRD) && !(sd->class_&JOBL_THIRD)) { + sd->change_level_3rd = sd->status.job_level; + pc_setglobalreg (sd, "jobchange_level_3rd", sd->change_level_3rd); + } + + if(sd->cloneskill_id) { + if( sd->status.skill[sd->cloneskill_id].flag == SKILL_FLAG_PLAGIARIZED ) { + sd->status.skill[sd->cloneskill_id].id = 0; + sd->status.skill[sd->cloneskill_id].lv = 0; + sd->status.skill[sd->cloneskill_id].flag = 0; + clif_deleteskill(sd,sd->cloneskill_id); + } + sd->cloneskill_id = 0; + pc_setglobalreg(sd, "CLONE_SKILL", 0); + pc_setglobalreg(sd, "CLONE_SKILL_LV", 0); + } + + if(sd->reproduceskill_id) { + if( sd->status.skill[sd->reproduceskill_id].flag == SKILL_FLAG_PLAGIARIZED ) { + sd->status.skill[sd->reproduceskill_id].id = 0; + sd->status.skill[sd->reproduceskill_id].lv = 0; + sd->status.skill[sd->reproduceskill_id].flag = 0; + clif_deleteskill(sd,sd->reproduceskill_id); + } + sd->reproduceskill_id = 0; + pc_setglobalreg(sd, "REPRODUCE_SKILL",0); + pc_setglobalreg(sd, "REPRODUCE_SKILL_LV",0); + } + + if ( (b_class&MAPID_UPPERMASK) != (sd->class_&MAPID_UPPERMASK) ) { //Things to remove when changing class tree. + const int class_ = pc_class2idx(sd->status.class_); + short id; + for(i = 0; i < MAX_SKILL_TREE && (id = skill_tree[class_][i].id) > 0; i++) { + //Remove status specific to your current tree skills. + enum sc_type sc = status_skill2sc(id); + if (sc > SC_COMMON_MAX && sd->sc.data[sc]) + status_change_end(&sd->bl, sc, INVALID_TIMER); + } + } + + sd->status.class_ = job; + fame_flag = pc_famerank(sd->status.char_id,sd->class_&MAPID_UPPERMASK); + sd->class_ = (unsigned short)b_class; + sd->status.job_level=1; + sd->status.job_exp=0; + + if (sd->status.base_level > pc_maxbaselv(sd)) { + sd->status.base_level = pc_maxbaselv(sd); + sd->status.base_exp=0; + pc_resetstate(sd); + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_NEXTBASEEXP); + } + + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + + for(i=0;i<EQI_MAX;i++) { + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],2); // unequip invalid item for class + } + + //Change look, if disguised, you need to undisguise + //to correctly calculate new job sprite without + if (sd->disguise) + pc_disguise(sd, 0); + + status_set_viewdata(&sd->bl, job); + clif_changelook(&sd->bl,LOOK_BASE,sd->vd.class_); // move sprite update to prevent client crashes with incompatible equipment [Valaris] + if(sd->vd.cloth_color) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->vd.cloth_color); + + //Update skill tree. + pc_calc_skilltree(sd); + clif_skillinfoblock(sd); + + if (sd->ed) + elemental_delete(sd->ed, 0); + if (sd->state.vending) + vending_closevending(sd); + + map_foreachinmap(jobchange_killclone, sd->bl.m, BL_MOB, sd->bl.id); + + //Remove peco/cart/falcon + i = sd->sc.option; + if( i&OPTION_RIDING && !pc_checkskill(sd, KN_RIDING) ) + i&=~OPTION_RIDING; + if( i&OPTION_FALCON && !pc_checkskill(sd, HT_FALCON) ) + i&=~OPTION_FALCON; + if( i&OPTION_DRAGON && !pc_checkskill(sd,RK_DRAGONTRAINING) ) + i&=~OPTION_DRAGON; + if( i&OPTION_WUGRIDER && !pc_checkskill(sd,RA_WUGMASTERY) ) + i&=~OPTION_WUGRIDER; + if( i&OPTION_WUG && !pc_checkskill(sd,RA_WUGMASTERY) ) + i&=~OPTION_WUG; + if( i&OPTION_MADOGEAR ) //You do not need a skill for this. + i&=~OPTION_MADOGEAR; +#ifndef NEW_CARTS + if( i&OPTION_CART && !pc_checkskill(sd, MC_PUSHCART) ) + i&=~OPTION_CART; +#else + if( sd->sc.data[SC_PUSH_CART] && !pc_checkskill(sd, MC_PUSHCART) ) + pc_setcart(sd, 0); +#endif + if(i != sd->sc.option) + pc_setoption(sd, i); + + if(merc_is_hom_active(sd->hd) && !pc_checkskill(sd, AM_CALLHOMUN)) + merc_hom_vaporize(sd, 0); + + if(sd->status.manner < 0) + clif_changestatus(sd,SP_MANNER,sd->status.manner); + + status_calc_pc(sd,0); + pc_checkallowskill(sd); + pc_equiplookall(sd); + + //if you were previously famous, not anymore. + if (fame_flag) { + chrif_save(sd,0); + chrif_buildfamelist(); + } else if (sd->status.fame > 0) { + //It may be that now they are famous? + switch (sd->class_&MAPID_UPPERMASK) { + case MAPID_BLACKSMITH: + case MAPID_ALCHEMIST: + case MAPID_TAEKWON: + chrif_save(sd,0); + chrif_buildfamelist(); + break; + } + } + + return 0; +} + +/*========================================== + * Tell client player sd has change equipement + *------------------------------------------*/ +int pc_equiplookall(struct map_session_data *sd) +{ + nullpo_ret(sd); + + clif_changelook(&sd->bl,LOOK_WEAPON,0); + clif_changelook(&sd->bl,LOOK_SHOES,0); + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + clif_changelook(&sd->bl, LOOK_ROBE, sd->status.robe); + + return 0; +} + +/*========================================== + * Tell client player sd has change look (hair,equip...) + *------------------------------------------*/ +int pc_changelook(struct map_session_data *sd,int type,int val) +{ + nullpo_ret(sd); + + switch(type){ + case LOOK_HAIR: //Use the battle_config limits! [Skotlex] + val = cap_value(val, MIN_HAIR_STYLE, MAX_HAIR_STYLE); + + if (sd->status.hair != val) + { + sd->status.hair=val; + if (sd->status.guild_id) //Update Guild Window. [Skotlex] + intif_guild_change_memberinfo(sd->status.guild_id,sd->status.account_id,sd->status.char_id, + GMI_HAIR,&sd->status.hair,sizeof(sd->status.hair)); + } + break; + case LOOK_WEAPON: + sd->status.weapon=val; + break; + case LOOK_HEAD_BOTTOM: + sd->status.head_bottom=val; + break; + case LOOK_HEAD_TOP: + sd->status.head_top=val; + break; + case LOOK_HEAD_MID: + sd->status.head_mid=val; + break; + case LOOK_HAIR_COLOR: //Use the battle_config limits! [Skotlex] + val = cap_value(val, MIN_HAIR_COLOR, MAX_HAIR_COLOR); + + if (sd->status.hair_color != val) + { + sd->status.hair_color=val; + if (sd->status.guild_id) //Update Guild Window. [Skotlex] + intif_guild_change_memberinfo(sd->status.guild_id,sd->status.account_id,sd->status.char_id, + GMI_HAIR_COLOR,&sd->status.hair_color,sizeof(sd->status.hair_color)); + } + break; + case LOOK_CLOTHES_COLOR: //Use the battle_config limits! [Skotlex] + val = cap_value(val, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + + sd->status.clothes_color=val; + break; + case LOOK_SHIELD: + sd->status.shield=val; + break; + case LOOK_SHOES: + break; + case LOOK_ROBE: + sd->status.robe = val; + break; + } + clif_changelook(&sd->bl,type,val); + return 0; +} + +/*========================================== + * Give an option (type) to player (sd) and display it to client + *------------------------------------------*/ +int pc_setoption(struct map_session_data *sd,int type) +{ + int p_type, new_look=0; + nullpo_ret(sd); + p_type = sd->sc.option; + + //Option has to be changed client-side before the class sprite or it won't always work (eg: Wedding sprite) [Skotlex] + sd->sc.option=type; + clif_changeoption(&sd->bl); + + if( (type&OPTION_RIDING && !(p_type&OPTION_RIDING)) || (type&OPTION_DRAGON && !(p_type&OPTION_DRAGON) && pc_checkskill(sd,RK_DRAGONTRAINING) > 0) ) + { // Mounting + clif_status_load(&sd->bl,SI_RIDING,1); + status_calc_pc(sd,0); + } + else if( (!(type&OPTION_RIDING) && p_type&OPTION_RIDING) || (!(type&OPTION_DRAGON) && p_type&OPTION_DRAGON && pc_checkskill(sd,RK_DRAGONTRAINING) > 0) ) + { // Dismount + clif_status_load(&sd->bl,SI_RIDING,0); + status_calc_pc(sd,0); + } + +#ifndef NEW_CARTS + if( type&OPTION_CART && !( p_type&OPTION_CART ) ) { //Cart On + clif_cartlist(sd); + clif_updatestatus(sd, SP_CARTINFO); + if(pc_checkskill(sd, MC_PUSHCART) < 10) + status_calc_pc(sd,0); //Apply speed penalty. + } else if( !( type&OPTION_CART ) && p_type&OPTION_CART ){ //Cart Off + clif_clearcart(sd->fd); + if(pc_checkskill(sd, MC_PUSHCART) < 10) + status_calc_pc(sd,0); //Remove speed penalty. + } +#endif + + if (type&OPTION_MOUNTING && !(p_type&OPTION_MOUNTING) ) { + clif_status_load_notick(&sd->bl,SI_ALL_RIDING,2,1,0,0); + status_calc_pc(sd,0); + } else if (!(type&OPTION_MOUNTING) && p_type&OPTION_MOUNTING) { + clif_status_load_notick(&sd->bl,SI_ALL_RIDING,0,0,0,0); + status_calc_pc(sd,0); + } + + + if (type&OPTION_FALCON && !(p_type&OPTION_FALCON)) //Falcon ON + clif_status_load(&sd->bl,SI_FALCON,1); + else if (!(type&OPTION_FALCON) && p_type&OPTION_FALCON) //Falcon OFF + clif_status_load(&sd->bl,SI_FALCON,0); + + if( (sd->class_&MAPID_THIRDMASK) == MAPID_RANGER ) { + if( type&OPTION_WUGRIDER && !(p_type&OPTION_WUGRIDER) ) { // Mounting + clif_status_load(&sd->bl,SI_WUGRIDER,1); + status_calc_pc(sd,0); + } else if( !(type&OPTION_WUGRIDER) && p_type&OPTION_WUGRIDER ) { // Dismount + clif_status_load(&sd->bl,SI_WUGRIDER,0); + status_calc_pc(sd,0); + } + } + if( (sd->class_&MAPID_THIRDMASK) == MAPID_MECHANIC ) { + if( type&OPTION_MADOGEAR && !(p_type&OPTION_MADOGEAR) ) { + status_calc_pc(sd, 0); + status_change_end(&sd->bl,SC_MAXIMIZEPOWER,INVALID_TIMER); + status_change_end(&sd->bl,SC_OVERTHRUST,INVALID_TIMER); + status_change_end(&sd->bl,SC_WEAPONPERFECTION,INVALID_TIMER); + status_change_end(&sd->bl,SC_ADRENALINE,INVALID_TIMER); + status_change_end(&sd->bl,SC_CARTBOOST,INVALID_TIMER); + status_change_end(&sd->bl,SC_MELTDOWN,INVALID_TIMER); + status_change_end(&sd->bl,SC_MAXOVERTHRUST,INVALID_TIMER); + } else if( !(type&OPTION_MADOGEAR) && p_type&OPTION_MADOGEAR ) { + status_calc_pc(sd, 0); + status_change_end(&sd->bl,SC_SHAPESHIFT,INVALID_TIMER); + status_change_end(&sd->bl,SC_HOVERING,INVALID_TIMER); + status_change_end(&sd->bl,SC_ACCELERATION,INVALID_TIMER); + status_change_end(&sd->bl,SC_OVERHEAT_LIMITPOINT,INVALID_TIMER); + status_change_end(&sd->bl,SC_OVERHEAT,INVALID_TIMER); + } + } + + if (type&OPTION_FLYING && !(p_type&OPTION_FLYING)) + new_look = JOB_STAR_GLADIATOR2; + else if (!(type&OPTION_FLYING) && p_type&OPTION_FLYING) + new_look = -1; + + if (type&OPTION_WEDDING && !(p_type&OPTION_WEDDING)) + new_look = JOB_WEDDING; + else if (!(type&OPTION_WEDDING) && p_type&OPTION_WEDDING) + new_look = -1; + + if (type&OPTION_XMAS && !(p_type&OPTION_XMAS)) + new_look = JOB_XMAS; + else if (!(type&OPTION_XMAS) && p_type&OPTION_XMAS) + new_look = -1; + + if (type&OPTION_SUMMER && !(p_type&OPTION_SUMMER)) + new_look = JOB_SUMMER; + else if (!(type&OPTION_SUMMER) && p_type&OPTION_SUMMER) + new_look = -1; + + if (sd->disguise || !new_look) + return 0; //Disguises break sprite changes + + if (new_look < 0) { //Restore normal look. + status_set_viewdata(&sd->bl, sd->status.class_); + new_look = sd->vd.class_; + } + + pc_stop_attack(sd); //Stop attacking on new view change (to prevent wedding/santa attacks. + clif_changelook(&sd->bl,LOOK_BASE,new_look); + if (sd->vd.cloth_color) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->vd.cloth_color); + clif_skillinfoblock(sd); // Skill list needs to be updated after base change. + + return 0; +} + +/*========================================== + * Give player a cart + *------------------------------------------*/ +int pc_setcart(struct map_session_data *sd,int type) { +#ifndef NEW_CARTS + int cart[6] = {0x0000,OPTION_CART1,OPTION_CART2,OPTION_CART3,OPTION_CART4,OPTION_CART5}; + int option; +#endif + nullpo_ret(sd); + + if( type < 0 || type > MAX_CARTS ) + return 1;// Never trust the values sent by the client! [Skotlex] + + if( pc_checkskill(sd,MC_PUSHCART) <= 0 && type != 0 ) + return 1;// Push cart is required + + if( type == 0 && pc_iscarton(sd) ) + status_change_end(&sd->bl,SC_GN_CARTBOOST,INVALID_TIMER); + +#ifdef NEW_CARTS + + switch( type ) { + case 0: + if( !sd->sc.data[SC_PUSH_CART] ) + return 0; + status_change_end(&sd->bl,SC_PUSH_CART,INVALID_TIMER); + clif_clearcart(sd->fd); + break; + default:/* everything else is an allowed ID so we can move on */ + if( !sd->sc.data[SC_PUSH_CART] ) /* first time, so fill cart data */ + clif_cartlist(sd); + clif_updatestatus(sd, SP_CARTINFO); + sc_start(&sd->bl, SC_PUSH_CART, 100, type, 0); + clif_status_load_notick(&sd->bl, SI_ON_PUSH_CART, 2 , type, 0, 0); + if( sd->sc.data[SC_PUSH_CART] )/* forcefully update */ + sd->sc.data[SC_PUSH_CART]->val1 = type; + break; + } + + if(pc_checkskill(sd, MC_PUSHCART) < 10) + status_calc_pc(sd,0); //Recalc speed penalty. +#else + // Update option + option = sd->sc.option; + option &= ~OPTION_CART;// clear cart bits + option |= cart[type]; // set cart + pc_setoption(sd, option); +#endif + + return 0; +} + +/*========================================== + * Give player a falcon + *------------------------------------------*/ +int pc_setfalcon(TBL_PC* sd, int flag) +{ + if( flag ){ + if( pc_checkskill(sd,HT_FALCON)>0 ) // add falcon if he have the skill + pc_setoption(sd,sd->sc.option|OPTION_FALCON); + } else if( pc_isfalcon(sd) ){ + pc_setoption(sd,sd->sc.option&~OPTION_FALCON); // remove falcon + } + + return 0; +} + +/*========================================== + * Set player riding + *------------------------------------------*/ +int pc_setriding(TBL_PC* sd, int flag) +{ + if( flag ){ + if( pc_checkskill(sd,KN_RIDING) > 0 ) // add peco + pc_setoption(sd, sd->sc.option|OPTION_RIDING); + } else if( pc_isriding(sd) ){ + pc_setoption(sd, sd->sc.option&~OPTION_RIDING); + } + + return 0; +} + +/*========================================== + * Give player a mado + *------------------------------------------*/ +int pc_setmadogear(TBL_PC* sd, int flag) +{ + if( flag ){ + if( pc_checkskill(sd,NC_MADOLICENCE) > 0 ) + pc_setoption(sd, sd->sc.option|OPTION_MADOGEAR); + } else if( pc_ismadogear(sd) ){ + pc_setoption(sd, sd->sc.option&~OPTION_MADOGEAR); + } + + return 0; +} + +/*========================================== + * Check if player can drop an item + *------------------------------------------*/ +int pc_candrop(struct map_session_data *sd, struct item *item) +{ + if( item && item->expire_time ) + return 0; + if( !pc_can_give_items(sd) ) //check if this GM level can drop items + return 0; + return (itemdb_isdropable(item, pc_get_group_level(sd))); +} + +/*========================================== + * Read ram register for player sd + * get val (int) from reg for player sd + *------------------------------------------*/ +int pc_readreg(struct map_session_data* sd, int reg) +{ + int i; + + nullpo_ret(sd); + + ARR_FIND( 0, sd->reg_num, i, sd->reg[i].index == reg ); + return ( i < sd->reg_num ) ? sd->reg[i].data : 0; +} +/*========================================== + * Set ram register for player sd + * memo val(int) at reg for player sd + *------------------------------------------*/ +int pc_setreg(struct map_session_data* sd, int reg, int val) +{ + int i; + + nullpo_ret(sd); + + ARR_FIND( 0, sd->reg_num, i, sd->reg[i].index == reg ); + if( i < sd->reg_num ) + {// overwrite existing entry + sd->reg[i].data = val; + return 1; + } + + ARR_FIND( 0, sd->reg_num, i, sd->reg[i].data == 0 ); + if( i == sd->reg_num ) + {// nothing free, increase size + sd->reg_num++; + RECREATE(sd->reg, struct script_reg, sd->reg_num); + } + sd->reg[i].index = reg; + sd->reg[i].data = val; + + return 1; +} + +/*========================================== + * Read ram register for player sd + * get val (str) from reg for player sd + *------------------------------------------*/ +char* pc_readregstr(struct map_session_data* sd, int reg) +{ + int i; + + nullpo_ret(sd); + + ARR_FIND( 0, sd->regstr_num, i, sd->regstr[i].index == reg ); + return ( i < sd->regstr_num ) ? sd->regstr[i].data : NULL; +} +/*========================================== + * Set ram register for player sd + * memo val(str) at reg for player sd + *------------------------------------------*/ +int pc_setregstr(struct map_session_data* sd, int reg, const char* str) +{ + int i; + + nullpo_ret(sd); + + ARR_FIND( 0, sd->regstr_num, i, sd->regstr[i].index == reg ); + if( i < sd->regstr_num ) + {// found entry, update + if( str == NULL || *str == '\0' ) + {// empty string + if( sd->regstr[i].data != NULL ) + aFree(sd->regstr[i].data); + sd->regstr[i].data = NULL; + } + else if( sd->regstr[i].data ) + {// recreate + size_t len = strlen(str)+1; + RECREATE(sd->regstr[i].data, char, len); + memcpy(sd->regstr[i].data, str, len*sizeof(char)); + } + else + {// create + sd->regstr[i].data = aStrdup(str); + } + return 1; + } + + if( str == NULL || *str == '\0' ) + return 1;// nothing to add, empty string + + ARR_FIND( 0, sd->regstr_num, i, sd->regstr[i].data == NULL ); + if( i == sd->regstr_num ) + {// nothing free, increase size + sd->regstr_num++; + RECREATE(sd->regstr, struct script_regstr, sd->regstr_num); + } + sd->regstr[i].index = reg; + sd->regstr[i].data = aStrdup(str); + + return 1; +} + +int pc_readregistry(struct map_session_data *sd,const char *reg,int type) +{ + struct global_reg *sd_reg; + int i,max; + + nullpo_ret(sd); + switch (type) { + case 3: //Char reg + sd_reg = sd->save_reg.global; + max = sd->save_reg.global_num; + break; + case 2: //Account reg + sd_reg = sd->save_reg.account; + max = sd->save_reg.account_num; + break; + case 1: //Account2 reg + sd_reg = sd->save_reg.account2; + max = sd->save_reg.account2_num; + break; + default: + return 0; + } + if (max == -1) { + ShowError("pc_readregistry: Trying to read reg value %s (type %d) before it's been loaded!\n", reg, type); + //This really shouldn't happen, so it's possible the data was lost somewhere, we should request it again. + intif_request_registry(sd,type==3?4:type); + return 0; + } + + ARR_FIND( 0, max, i, strcmp(sd_reg[i].str,reg) == 0 ); + return ( i < max ) ? atoi(sd_reg[i].value) : 0; +} + +char* pc_readregistry_str(struct map_session_data *sd,const char *reg,int type) +{ + struct global_reg *sd_reg; + int i,max; + + nullpo_ret(sd); + switch (type) { + case 3: //Char reg + sd_reg = sd->save_reg.global; + max = sd->save_reg.global_num; + break; + case 2: //Account reg + sd_reg = sd->save_reg.account; + max = sd->save_reg.account_num; + break; + case 1: //Account2 reg + sd_reg = sd->save_reg.account2; + max = sd->save_reg.account2_num; + break; + default: + return NULL; + } + if (max == -1) { + ShowError("pc_readregistry: Trying to read reg value %s (type %d) before it's been loaded!\n", reg, type); + //This really shouldn't happen, so it's possible the data was lost somewhere, we should request it again. + intif_request_registry(sd,type==3?4:type); + return NULL; + } + + ARR_FIND( 0, max, i, strcmp(sd_reg[i].str,reg) == 0 ); + return ( i < max ) ? sd_reg[i].value : NULL; +} + +int pc_setregistry(struct map_session_data *sd,const char *reg,int val,int type) +{ + struct global_reg *sd_reg; + int i,*max, regmax; + + nullpo_ret(sd); + + switch( type ) + { + case 3: //Char reg + if( !strcmp(reg,"PC_DIE_COUNTER") && sd->die_counter != val ) + { + i = (!sd->die_counter && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE); + sd->die_counter = val; + if( i ) + status_calc_pc(sd,0); // Lost the bonus. + } + else if( !strcmp(reg,"COOK_MASTERY") && sd->cook_mastery != val ) + { + val = cap_value(val, 0, 1999); + sd->cook_mastery = val; + } + sd_reg = sd->save_reg.global; + max = &sd->save_reg.global_num; + regmax = GLOBAL_REG_NUM; + break; + case 2: //Account reg + if( !strcmp(reg,"#CASHPOINTS") && sd->cashPoints != val ) + { + val = cap_value(val, 0, MAX_ZENY); + sd->cashPoints = val; + } + else if( !strcmp(reg,"#KAFRAPOINTS") && sd->kafraPoints != val ) + { + val = cap_value(val, 0, MAX_ZENY); + sd->kafraPoints = val; + } + sd_reg = sd->save_reg.account; + max = &sd->save_reg.account_num; + regmax = ACCOUNT_REG_NUM; + break; + case 1: //Account2 reg + sd_reg = sd->save_reg.account2; + max = &sd->save_reg.account2_num; + regmax = ACCOUNT_REG2_NUM; + break; + default: + return 0; + } + if (*max == -1) { + ShowError("pc_setregistry : refusing to set %s (type %d) until vars are received.\n", reg, type); + return 1; + } + + // delete reg + if (val == 0) { + ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 ); + if( i < *max ) + { + if (i != *max - 1) + memcpy(&sd_reg[i], &sd_reg[*max - 1], sizeof(struct global_reg)); + memset(&sd_reg[*max - 1], 0, sizeof(struct global_reg)); + (*max)--; + sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved" + } + return 1; + } + // change value if found + ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 ); + if( i < *max ) + { + safesnprintf(sd_reg[i].value, sizeof(sd_reg[i].value), "%d", val); + sd->state.reg_dirty |= 1<<(type-1); + return 1; + } + + // add value if not found + if (i < regmax) { + memset(&sd_reg[i], 0, sizeof(struct global_reg)); + safestrncpy(sd_reg[i].str, reg, sizeof(sd_reg[i].str)); + safesnprintf(sd_reg[i].value, sizeof(sd_reg[i].value), "%d", val); + (*max)++; + sd->state.reg_dirty |= 1<<(type-1); + return 1; + } + + ShowError("pc_setregistry : couldn't set %s, limit of registries reached (%d)\n", reg, regmax); + + return 0; +} + +int pc_setregistry_str(struct map_session_data *sd,const char *reg,const char *val,int type) +{ + struct global_reg *sd_reg; + int i,*max, regmax; + + nullpo_ret(sd); + if (reg[strlen(reg)-1] != '$') { + ShowError("pc_setregistry_str : reg %s must be string (end in '$') to use this!\n", reg); + return 0; + } + + switch (type) { + case 3: //Char reg + sd_reg = sd->save_reg.global; + max = &sd->save_reg.global_num; + regmax = GLOBAL_REG_NUM; + break; + case 2: //Account reg + sd_reg = sd->save_reg.account; + max = &sd->save_reg.account_num; + regmax = ACCOUNT_REG_NUM; + break; + case 1: //Account2 reg + sd_reg = sd->save_reg.account2; + max = &sd->save_reg.account2_num; + regmax = ACCOUNT_REG2_NUM; + break; + default: + return 0; + } + if (*max == -1) { + ShowError("pc_setregistry_str : refusing to set %s (type %d) until vars are received.\n", reg, type); + return 0; + } + + // delete reg + if (!val || strcmp(val,"")==0) + { + ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 ); + if( i < *max ) + { + if (i != *max - 1) + memcpy(&sd_reg[i], &sd_reg[*max - 1], sizeof(struct global_reg)); + memset(&sd_reg[*max - 1], 0, sizeof(struct global_reg)); + (*max)--; + sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved" + if (type!=3) intif_saveregistry(sd,type); + } + return 1; + } + + // change value if found + ARR_FIND( 0, *max, i, strcmp(sd_reg[i].str, reg) == 0 ); + if( i < *max ) + { + safestrncpy(sd_reg[i].value, val, sizeof(sd_reg[i].value)); + sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved" + if (type!=3) intif_saveregistry(sd,type); + return 1; + } + + // add value if not found + if (i < regmax) { + memset(&sd_reg[i], 0, sizeof(struct global_reg)); + safestrncpy(sd_reg[i].str, reg, sizeof(sd_reg[i].str)); + safestrncpy(sd_reg[i].value, val, sizeof(sd_reg[i].value)); + (*max)++; + sd->state.reg_dirty |= 1<<(type-1); //Mark this registry as "need to be saved" + if (type!=3) intif_saveregistry(sd,type); + return 1; + } + + ShowError("pc_setregistry : couldn't set %s, limit of registries reached (%d)\n", reg, regmax); + + return 0; +} + +/*========================================== + * Exec eventtimer for player sd (retrieved from map_session (id)) + *------------------------------------------*/ +static int pc_eventtimer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=map_id2sd(id); + char *p = (char *)data; + int i; + if(sd==NULL) + return 0; + + ARR_FIND( 0, MAX_EVENTTIMER, i, sd->eventtimer[i] == tid ); + if( i < MAX_EVENTTIMER ) + { + sd->eventtimer[i] = INVALID_TIMER; + sd->eventcount--; + npc_event(sd,p,0); + } + else + ShowError("pc_eventtimer: no such event timer\n"); + + if (p) aFree(p); + return 0; +} + +/*========================================== + * Add eventtimer for player sd ? + *------------------------------------------*/ +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name) +{ + int i; + nullpo_ret(sd); + + ARR_FIND( 0, MAX_EVENTTIMER, i, sd->eventtimer[i] == INVALID_TIMER ); + if( i == MAX_EVENTTIMER ) + return 0; + + sd->eventtimer[i] = add_timer(gettick()+tick, pc_eventtimer, sd->bl.id, (intptr_t)aStrdup(name)); + sd->eventcount++; + + return 1; +} + +/*========================================== + * Del eventtimer for player sd ? + *------------------------------------------*/ +int pc_deleventtimer(struct map_session_data *sd,const char *name) +{ + char* p = NULL; + int i; + + nullpo_ret(sd); + + if (sd->eventcount <= 0) + return 0; + + // find the named event timer + ARR_FIND( 0, MAX_EVENTTIMER, i, + sd->eventtimer[i] != INVALID_TIMER && + (p = (char *)(get_timer(sd->eventtimer[i])->data)) != NULL && + strcmp(p, name) == 0 + ); + if( i == MAX_EVENTTIMER ) + return 0; // not found + + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i] = INVALID_TIMER; + sd->eventcount--; + aFree(p); + + return 1; +} + +/*========================================== + * Update eventtimer count for player sd + *------------------------------------------*/ +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick) +{ + int i; + + nullpo_ret(sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i] != INVALID_TIMER && strcmp( + (char *)(get_timer(sd->eventtimer[i])->data), name)==0 ){ + addtick_timer(sd->eventtimer[i],tick); + break; + } + + return 0; +} + +/*========================================== + * Remove all eventtimer for player sd + *------------------------------------------*/ +int pc_cleareventtimer(struct map_session_data *sd) +{ + int i; + + nullpo_ret(sd); + + if (sd->eventcount <= 0) + return 0; + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i] != INVALID_TIMER ){ + char *p = (char *)(get_timer(sd->eventtimer[i])->data); + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i] = INVALID_TIMER; + sd->eventcount--; + if (p) aFree(p); + } + return 0; +} +/* called when a item with combo is worn */ +int pc_checkcombo(struct map_session_data *sd, struct item_data *data ) { + int i, j, k, z; + int index, idx, success = 0; + + for( i = 0; i < data->combos_count; i++ ) { + + /* ensure this isn't a duplicate combo */ + if( sd->combos.bonus != NULL ) { + int x; + ARR_FIND( 0, sd->combos.count, x, sd->combos.id[x] == data->combos[i]->id ); + + /* found a match, skip this combo */ + if( x < sd->combos.count ) + continue; + } + + for( j = 0; j < data->combos[i]->count; j++ ) { + int id = data->combos[i]->nameid[j]; + bool found = false; + + for( k = 0; k < EQI_MAX; k++ ) { + index = sd->equip_index[k]; + if( index < 0 ) continue; + if( k == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index ) continue; + if( k == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index ) continue; + if( k == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index) ) continue; + + if(!sd->inventory_data[index]) + continue; + + if ( itemdb_type(id) != IT_CARD ) { + if ( sd->inventory_data[index]->nameid != id ) + continue; + + found = true; + break; + } else { //Cards + if ( sd->inventory_data[index]->slot == 0 || itemdb_isspecial(sd->status.inventory[index].card[0]) ) + continue; + + for (z = 0; z < sd->inventory_data[index]->slot; z++) { + + if (sd->status.inventory[index].card[z] != id) + continue; + + // We have found a match + found = true; + break; + } + } + + } + + if( !found ) + break;/* we haven't found all the ids for this combo, so we can return */ + } + + /* means we broke out of the count loop w/o finding all ids, we can move to the next combo */ + if( j < data->combos[i]->count ) + continue; + + /* we got here, means all items in the combo are matching */ + + idx = sd->combos.count; + + if( sd->combos.bonus == NULL ) { + CREATE(sd->combos.bonus, struct script_code *, 1); + CREATE(sd->combos.id, unsigned short, 1); + sd->combos.count = 1; + } else { + RECREATE(sd->combos.bonus, struct script_code *, ++sd->combos.count); + RECREATE(sd->combos.id, unsigned short, sd->combos.count); + } + + /* we simply copy the pointer */ + sd->combos.bonus[idx] = data->combos[i]->script; + /* save this combo's id */ + sd->combos.id[idx] = data->combos[i]->id; + + success++; + } + return success; +} + +/* called when a item with combo is removed */ +int pc_removecombo(struct map_session_data *sd, struct item_data *data ) { + int i, retval = 0; + + if( sd->combos.bonus == NULL ) + return 0;/* nothing to do here, player has no combos */ + for( i = 0; i < data->combos_count; i++ ) { + /* check if this combo exists in this user */ + int x = 0, cursor = 0, j; + ARR_FIND( 0, sd->combos.count, x, sd->combos.id[x] == data->combos[i]->id ); + /* no match, skip this combo */ + if( !(x < sd->combos.count) ) + continue; + + sd->combos.bonus[x] = NULL; + sd->combos.id[x] = 0; + retval++; + for( j = 0, cursor = 0; j < sd->combos.count; j++ ) { + if( sd->combos.bonus[j] == NULL ) + continue; + + if( cursor != j ) { + sd->combos.bonus[cursor] = sd->combos.bonus[j]; + sd->combos.id[cursor] = sd->combos.id[j]; + } + + cursor++; + } + + /* it's empty, we can clear all the memory */ + if( (sd->combos.count = cursor) == 0 ) { + aFree(sd->combos.bonus); + aFree(sd->combos.id); + sd->combos.bonus = NULL; + sd->combos.id = NULL; + return retval; /* we also can return at this point for we have no more combos to check */ + } + + } + + return retval; +} +int pc_load_combo(struct map_session_data *sd) { + int i, ret = 0; + for( i = 0; i < EQI_MAX; i++ ) { + struct item_data *id = NULL; + int idx = sd->equip_index[i]; + if( sd->equip_index[i] < 0 || !(id = sd->inventory_data[idx] ) ) + continue; + if( id->combos_count ) + ret += pc_checkcombo(sd,id); + if(!itemdb_isspecial(sd->status.inventory[idx].card[0])) { + struct item_data *data; + int j; + for( j = 0; j < id->slot; j++ ) { + if (!sd->status.inventory[idx].card[j]) + continue; + if ( ( data = itemdb_exists(sd->status.inventory[idx].card[j]) ) != NULL ) { + if( data->combos_count ) + ret += pc_checkcombo(sd,data); + } + } + } + } + return ret; +} +/*========================================== + * Equip item on player sd at req_pos from inventory index n + *------------------------------------------*/ +int pc_equipitem(struct map_session_data *sd,int n,int req_pos) +{ + int i,pos,flag=0; + struct item_data *id; + + nullpo_ret(sd); + + if( n < 0 || n >= MAX_INVENTORY ) { + clif_equipitemack(sd,0,0,0); + return 0; + } + + if( DIFF_TICK(sd->canequip_tick,gettick()) > 0 ) + { + clif_equipitemack(sd,n,0,0); + return 0; + } + + id = sd->inventory_data[n]; + pos = pc_equippoint(sd,n); //With a few exceptions, item should go in all specified slots. + + if(battle_config.battle_log) + ShowInfo("equip %d(%d) %x:%x\n",sd->status.inventory[n].nameid,n,id?id->equip:0,req_pos); + if(!pc_isequip(sd,n) || !(pos&req_pos) || sd->status.inventory[n].equip != 0 || sd->status.inventory[n].attribute==1 ) { // [Valaris] + // FIXME: pc_isequip: equip level failure uses 2 instead of 0 + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + + if (sd->sc.data[SC_BERSERK] || sd->sc.data[SC_SATURDAYNIGHTFEVER] || sd->sc.data[SC__BLOODYLUST]) + { + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + + if(pos == EQP_ACC) { //Accesories should only go in one of the two, + pos = req_pos&EQP_ACC; + if (pos == EQP_ACC) //User specified both slots.. + pos = sd->equip_index[EQI_ACC_R] >= 0 ? EQP_ACC_L : EQP_ACC_R; + } + + if(pos == EQP_ARMS && id->equip == EQP_HAND_R) + { //Dual wield capable weapon. + pos = (req_pos&EQP_ARMS); + if (pos == EQP_ARMS) //User specified both slots, pick one for them. + pos = sd->equip_index[EQI_HAND_R] >= 0 ? EQP_HAND_L : EQP_HAND_R; + } + + if (pos&EQP_HAND_R && battle_config.use_weapon_skill_range&BL_PC) + { //Update skill-block range database when weapon range changes. [Skotlex] + i = sd->equip_index[EQI_HAND_R]; + if (i < 0 || !sd->inventory_data[i]) //No data, or no weapon equipped + flag = 1; + else + flag = id->range != sd->inventory_data[i]->range; + } + + for(i=0;i<EQI_MAX;i++) { + if(pos & equip_pos[i]) { + if(sd->equip_index[i] >= 0) //Slot taken, remove item from there. + pc_unequipitem(sd,sd->equip_index[i],2); + + sd->equip_index[i] = n; + } + } + + if(pos==EQP_AMMO){ + clif_arrowequip(sd,n); + clif_arrow_fail(sd,3); + } + else + clif_equipitemack(sd,n,pos,1); + + sd->status.inventory[n].equip=pos; + + if(pos & EQP_HAND_R) { + if(id) + sd->weapontype1 = id->look; + else + sd->weapontype1 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + } + if(pos & EQP_HAND_L) { + if(id) { + if(id->type == IT_WEAPON) { + sd->status.shield = 0; + sd->weapontype2 = id->look; + } + else + if(id->type == IT_ARMOR) { + sd->status.shield = id->look; + sd->weapontype2 = 0; + } + } + else + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + //Added check to prevent sending the same look on multiple slots -> + //causes client to redraw item on top of itself. (suggested by Lupus) + if(pos & EQP_HEAD_LOW && pc_checkequip(sd,EQP_COSTUME_HEAD_LOW) == -1) { + if(id && !(pos&(EQP_HEAD_TOP|EQP_HEAD_MID))) + sd->status.head_bottom = id->look; + else + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(pos & EQP_HEAD_TOP && pc_checkequip(sd,EQP_COSTUME_HEAD_TOP) == -1) { + if(id) + sd->status.head_top = id->look; + else + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(pos & EQP_HEAD_MID && pc_checkequip(sd,EQP_COSTUME_HEAD_MID) == -1) { + if(id && !(pos&EQP_HEAD_TOP)) + sd->status.head_mid = id->look; + else + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(pos & EQP_COSTUME_HEAD_TOP) { + if(id){ + sd->status.head_top = id->look; + } else + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(pos & EQP_COSTUME_HEAD_MID) { + if(id && !(pos&EQP_HEAD_TOP)){ + sd->status.head_mid = id->look; + } else + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(pos & EQP_COSTUME_HEAD_LOW) { + if(id && !(pos&(EQP_HEAD_TOP|EQP_HEAD_MID))){ + sd->status.head_bottom = id->look; + } else + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(pos & EQP_SHOES) + clif_changelook(&sd->bl,LOOK_SHOES,0); + if( pos&EQP_GARMENT ) + { + sd->status.robe = id ? id->look : 0; + clif_changelook(&sd->bl, LOOK_ROBE, sd->status.robe); + } + + pc_checkallowskill(sd); //Check if status changes should be halted. + + /* check for combos (MUST be before status_calc_pc) */ + if ( id ) { + struct item_data *data; + if( id->combos_count ) + pc_checkcombo(sd,id); + if(itemdb_isspecial(sd->status.inventory[n].card[0])) + ; //No cards + else { + for( i = 0; i < id->slot; i++ ) { + if (!sd->status.inventory[n].card[i]) + continue; + if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) { + if( data->combos_count ) + pc_checkcombo(sd,data); + } + } + } + } + + status_calc_pc(sd,0); + if (flag) //Update skill data + clif_skillinfoblock(sd); + + //OnEquip script [Skotlex] + if (id) { + struct item_data *data; + if (id->equip_script) + run_script(id->equip_script,0,sd->bl.id,fake_nd->bl.id); + if(itemdb_isspecial(sd->status.inventory[n].card[0])) + ; //No cards + else { + for( i = 0; i < id->slot; i++ ) { + if (!sd->status.inventory[n].card[i]) + continue; + if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) { + if( data->equip_script ) + run_script(data->equip_script,0,sd->bl.id,fake_nd->bl.id); + } + } + } + } + return 0; +} + +/*========================================== + * Called when attemting to unequip an item from player + * type: + * 0 - only unequip + * 1 - calculate status after unequipping + * 2 - force unequip + *------------------------------------------*/ +int pc_unequipitem(struct map_session_data *sd,int n,int flag) { + int i; + bool status_cacl = false; + nullpo_ret(sd); + + if( n < 0 || n >= MAX_INVENTORY ) { + clif_unequipitemack(sd,0,0,0); + return 0; + } + + // if player is berserk then cannot unequip + if (!(flag & 2) && sd->sc.count && (sd->sc.data[SC_BERSERK] || sd->sc.data[SC_SATURDAYNIGHTFEVER] || sd->sc.data[SC__BLOODYLUST])) + { + clif_unequipitemack(sd,n,0,0); + return 0; + } + + if( !(flag&2) && sd->sc.count && sd->sc.data[SC_KYOUGAKU] ) + { + clif_unequipitemack(sd,n,0,0); + return 0; + } + + if(battle_config.battle_log) + ShowInfo("unequip %d %x:%x\n",n,pc_equippoint(sd,n),sd->status.inventory[n].equip); + + if(!sd->status.inventory[n].equip){ //Nothing to unequip + clif_unequipitemack(sd,n,0,0); + return 0; + } + for(i=0;i<EQI_MAX;i++) { + if(sd->status.inventory[n].equip & equip_pos[i]) + sd->equip_index[i] = -1; + } + + if(sd->status.inventory[n].equip & EQP_HAND_R) { + sd->weapontype1 = 0; + sd->status.weapon = sd->weapontype2; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + if( !battle_config.dancing_weaponswitch_fix ) + status_change_end(&sd->bl, SC_DANCING, INVALID_TIMER); // Unequipping => stop dancing. + } + if(sd->status.inventory[n].equip & EQP_HAND_L) { + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + if(sd->status.inventory[n].equip & EQP_HEAD_LOW && pc_checkequip(sd,EQP_COSTUME_HEAD_LOW) == -1 ) { + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(sd->status.inventory[n].equip & EQP_HEAD_TOP && pc_checkequip(sd,EQP_COSTUME_HEAD_TOP) == -1 ) { + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(sd->status.inventory[n].equip & EQP_HEAD_MID && pc_checkequip(sd,EQP_COSTUME_HEAD_MID) == -1 ) { + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + + if(sd->status.inventory[n].equip & EQP_COSTUME_HEAD_TOP) { + sd->status.head_top = ( pc_checkequip(sd,EQP_HEAD_TOP) >= 0 ) ? sd->inventory_data[pc_checkequip(sd,EQP_HEAD_TOP)]->look : 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + + if(sd->status.inventory[n].equip & EQP_COSTUME_HEAD_MID) { + sd->status.head_mid = ( pc_checkequip(sd,EQP_HEAD_MID) >= 0 ) ? sd->inventory_data[pc_checkequip(sd,EQP_HEAD_MID)]->look : 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + + if(sd->status.inventory[n].equip & EQP_COSTUME_HEAD_LOW) { + sd->status.head_bottom = ( pc_checkequip(sd,EQP_HEAD_LOW) >= 0 ) ? sd->inventory_data[pc_checkequip(sd,EQP_HEAD_LOW)]->look : 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + + if(sd->status.inventory[n].equip & EQP_SHOES) + clif_changelook(&sd->bl,LOOK_SHOES,0); + if( sd->status.inventory[n].equip&EQP_GARMENT ) + { + sd->status.robe = 0; + clif_changelook(&sd->bl, LOOK_ROBE, 0); + } + + clif_unequipitemack(sd,n,sd->status.inventory[n].equip,1); + + if((sd->status.inventory[n].equip & EQP_ARMS) && + sd->weapontype1 == 0 && sd->weapontype2 == 0 && (!sd->sc.data[SC_SEVENWIND] || sd->sc.data[SC_ASPERSIO])) //Check for seven wind (but not level seven!) + skill_enchant_elemental_end(&sd->bl,-1); + + if(sd->status.inventory[n].equip & EQP_ARMOR) { + // On Armor Change... + status_change_end(&sd->bl, SC_BENEDICTIO, INVALID_TIMER); + status_change_end(&sd->bl, SC_ARMOR_RESIST, INVALID_TIMER); + } + + if( sd->state.autobonus&sd->status.inventory[n].equip ) + sd->state.autobonus &= ~sd->status.inventory[n].equip; //Check for activated autobonus [Inkfish] + + sd->status.inventory[n].equip=0; + + /* check for combos (MUST be before status_calc_pc) */ + if ( sd->inventory_data[n] ) { + struct item_data *data; + + if( sd->inventory_data[n]->combos_count ) { + if( pc_removecombo(sd,sd->inventory_data[n]) ) + status_cacl = true; + } if(itemdb_isspecial(sd->status.inventory[n].card[0])) + ; //No cards + else { + for( i = 0; i < sd->inventory_data[n]->slot; i++ ) { + if (!sd->status.inventory[n].card[i]) + continue; + if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) { + if( data->combos_count ) { + if( pc_removecombo(sd,data) ) + status_cacl = true; + } + } + } + } + } + + if(flag&1 || status_cacl) { + pc_checkallowskill(sd); + status_calc_pc(sd,0); + } + + if(sd->sc.data[SC_SIGNUMCRUCIS] && !battle_check_undead(sd->battle_status.race,sd->battle_status.def_ele)) + status_change_end(&sd->bl, SC_SIGNUMCRUCIS, INVALID_TIMER); + + //OnUnEquip script [Skotlex] + if (sd->inventory_data[n]) { + struct item_data *data; + if (sd->inventory_data[n]->unequip_script) + run_script(sd->inventory_data[n]->unequip_script,0,sd->bl.id,fake_nd->bl.id); + if(itemdb_isspecial(sd->status.inventory[n].card[0])) + ; //No cards + else { + for( i = 0; i < sd->inventory_data[n]->slot; i++ ) { + if (!sd->status.inventory[n].card[i]) + continue; + + if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) { + if( data->unequip_script ) + run_script(data->unequip_script,0,sd->bl.id,fake_nd->bl.id); + } + + } + } + } + + return 0; +} + +/*========================================== + * Checking if player (sd) have unauthorize, invalide item + * on inventory, cart, equiped for the map (item_noequip) + *------------------------------------------*/ +int pc_checkitem(struct map_session_data *sd) +{ + int i,id,calc_flag = 0; + struct item_data *it=NULL; + + nullpo_ret(sd); + + if( sd->state.vending ) //Avoid reorganizing items when we are vending, as that leads to exploits (pointed out by End of Exam) + return 0; + + if( battle_config.item_check ) + {// check for invalid(ated) items + for( i = 0; i < MAX_INVENTORY; i++ ) + { + id = sd->status.inventory[i].nameid; + + if( id && !itemdb_available(id) ) + { + ShowWarning("Removed invalid/disabled item id %d from inventory (amount=%d, char_id=%d).\n", id, sd->status.inventory[i].amount, sd->status.char_id); + pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_OTHER); + } + } + + for( i = 0; i < MAX_CART; i++ ) + { + id = sd->status.cart[i].nameid; + + if( id && !itemdb_available(id) ) + { + ShowWarning("Removed invalid/disabled item id %d from cart (amount=%d, char_id=%d).\n", id, sd->status.cart[i].amount, sd->status.char_id); + pc_cart_delitem(sd, i, sd->status.cart[i].amount, 0, LOG_TYPE_OTHER); + } + } + } + + for( i = 0; i < MAX_INVENTORY; i++) + { + it = sd->inventory_data[i]; + + if( sd->status.inventory[i].nameid == 0 ) + continue; + + if( !sd->status.inventory[i].equip ) + continue; + + if( sd->status.inventory[i].equip&~pc_equippoint(sd,i) ) + { + pc_unequipitem(sd, i, 2); + calc_flag = 1; + continue; + } + + if( it ) + { // check for forbiden items. + int flag = + (map[sd->bl.m].flag.restricted?(8*map[sd->bl.m].zone):0) + | (!map_flag_vs(sd->bl.m)?1:0) + | (map[sd->bl.m].flag.pvp?2:0) + | (map_flag_gvg(sd->bl.m)?4:0) + | (map[sd->bl.m].flag.battleground?8:0); + if( flag && (it->flag.no_equip&flag || !pc_isAllowedCardOn(sd,it->slot,i,flag)) ) + { + pc_unequipitem(sd, i, 2); + calc_flag = 1; + } + } + } + + if( calc_flag && sd->state.active ) + { + pc_checkallowskill(sd); + status_calc_pc(sd,0); + } + + return 0; +} + +/*========================================== + * Update PVP rank for sd1 in cmp to sd2 + *------------------------------------------*/ +int pc_calc_pvprank_sub(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd1,*sd2=NULL; + + sd1=(struct map_session_data *)bl; + sd2=va_arg(ap,struct map_session_data *); + + if( sd1->sc.option&OPTION_INVISIBLE || sd2->sc.option&OPTION_INVISIBLE ) + {// cannot register pvp rank for hidden GMs + return 0; + } + + if( sd1->pvp_point > sd2->pvp_point ) + sd2->pvp_rank++; + return 0; +} +/*========================================== + * Calculate new rank beetween all present players (map_foreachinarea) + * and display result + *------------------------------------------*/ +int pc_calc_pvprank(struct map_session_data *sd) +{ + int old; + struct map_data *m; + m=&map[sd->bl.m]; + old=sd->pvp_rank; + sd->pvp_rank=1; + map_foreachinmap(pc_calc_pvprank_sub,sd->bl.m,BL_PC,sd); + if(old!=sd->pvp_rank || sd->pvp_lastusers!=m->users_pvp) + clif_pvpset(sd,sd->pvp_rank,sd->pvp_lastusers=m->users_pvp,0); + return sd->pvp_rank; +} +/*========================================== + * Calculate next sd ranking calculation from config + *------------------------------------------*/ +int pc_calc_pvprank_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=NULL; + + sd=map_id2sd(id); + if(sd==NULL) + return 0; + sd->pvp_timer = INVALID_TIMER; + + if( sd->sc.option&OPTION_INVISIBLE ) + {// do not calculate the pvp rank for a hidden GM + return 0; + } + + if( pc_calc_pvprank(sd) > 0 ) + sd->pvp_timer = add_timer(gettick()+PVP_CALCRANK_INTERVAL,pc_calc_pvprank_timer,id,data); + return 0; +} + +/*========================================== + * Checking if sd is married + * Return: + * partner_id = yes + * 0 = no + *------------------------------------------*/ +int pc_ismarried(struct map_session_data *sd) +{ + if(sd == NULL) + return -1; + if(sd->status.partner_id > 0) + return sd->status.partner_id; + else + return 0; +} +/*========================================== + * Marry player sd to player dstsd + * Return: + * -1 = fail + * 0 = success + *------------------------------------------*/ +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd) +{ + if(sd == NULL || dstsd == NULL || + sd->status.partner_id > 0 || dstsd->status.partner_id > 0 || + (sd->class_&JOBL_BABY) || (dstsd->class_&JOBL_BABY)) + return -1; + sd->status.partner_id = dstsd->status.char_id; + dstsd->status.partner_id = sd->status.char_id; + return 0; +} + +/*========================================== + * Divorce sd from its partner + * Return: + * -1 = fail + * 0 = success + *------------------------------------------*/ +int pc_divorce(struct map_session_data *sd) +{ + struct map_session_data *p_sd; + int i; + + if( sd == NULL || !pc_ismarried(sd) ) + return -1; + + if( !sd->status.partner_id ) + return -1; // Char is not married + + if( (p_sd = map_charid2sd(sd->status.partner_id)) == NULL ) + { // Lets char server do the divorce + if( chrif_divorce(sd->status.char_id, sd->status.partner_id) ) + return -1; // No char server connected + + return 0; + } + + // Both players online, lets do the divorce manually + sd->status.partner_id = 0; + p_sd->status.partner_id = 0; + for( i = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F ) + pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); + if( p_sd->status.inventory[i].nameid == WEDDING_RING_M || p_sd->status.inventory[i].nameid == WEDDING_RING_F ) + pc_delitem(p_sd, i, 1, 0, 0, LOG_TYPE_OTHER); + } + + clif_divorced(sd, p_sd->status.name); + clif_divorced(p_sd, sd->status.name); + + return 0; +} + +/*========================================== + * Get sd partner charid. (Married partner) + *------------------------------------------*/ +struct map_session_data *pc_get_partner(struct map_session_data *sd) +{ + if (sd && pc_ismarried(sd)) + // charid2sd returns NULL if not found + return map_charid2sd(sd->status.partner_id); + + return NULL; +} + +/*========================================== + * Get sd father charid. (Need to be baby) + *------------------------------------------*/ +struct map_session_data *pc_get_father (struct map_session_data *sd) +{ + if (sd && sd->class_&JOBL_BABY && sd->status.father > 0) + // charid2sd returns NULL if not found + return map_charid2sd(sd->status.father); + + return NULL; +} + +/*========================================== + * Get sd mother charid. (Need to be baby) + *------------------------------------------*/ +struct map_session_data *pc_get_mother (struct map_session_data *sd) +{ + if (sd && sd->class_&JOBL_BABY && sd->status.mother > 0) + // charid2sd returns NULL if not found + return map_charid2sd(sd->status.mother); + + return NULL; +} + +/*========================================== + * Get sd children charid. (Need to be married) + *------------------------------------------*/ +struct map_session_data *pc_get_child (struct map_session_data *sd) +{ + if (sd && pc_ismarried(sd) && sd->status.child > 0) + // charid2sd returns NULL if not found + return map_charid2sd(sd->status.child); + + return NULL; +} + +/*========================================== + * Set player sd to bleed. (losing hp and/or sp each diff_tick) + *------------------------------------------*/ +void pc_bleeding (struct map_session_data *sd, unsigned int diff_tick) +{ + int hp = 0, sp = 0; + + if( pc_isdead(sd) ) + return; + + if (sd->hp_loss.value) { + sd->hp_loss.tick += diff_tick; + while (sd->hp_loss.tick >= sd->hp_loss.rate) { + hp += sd->hp_loss.value; + sd->hp_loss.tick -= sd->hp_loss.rate; + } + if(hp >= sd->battle_status.hp) + hp = sd->battle_status.hp-1; //Script drains cannot kill you. + } + + if (sd->sp_loss.value) { + sd->sp_loss.tick += diff_tick; + while (sd->sp_loss.tick >= sd->sp_loss.rate) { + sp += sd->sp_loss.value; + sd->sp_loss.tick -= sd->sp_loss.rate; + } + } + + if (hp > 0 || sp > 0) + status_zap(&sd->bl, hp, sp); + + return; +} + +//Character regen. Flag is used to know which types of regen can take place. +//&1: HP regen +//&2: SP regen +void pc_regen (struct map_session_data *sd, unsigned int diff_tick) +{ + int hp = 0, sp = 0; + + if (sd->hp_regen.value) { + sd->hp_regen.tick += diff_tick; + while (sd->hp_regen.tick >= sd->hp_regen.rate) { + hp += sd->hp_regen.value; + sd->hp_regen.tick -= sd->hp_regen.rate; + } + } + + if (sd->sp_regen.value) { + sd->sp_regen.tick += diff_tick; + while (sd->sp_regen.tick >= sd->sp_regen.rate) { + sp += sd->sp_regen.value; + sd->sp_regen.tick -= sd->sp_regen.rate; + } + } + + if (hp > 0 || sp > 0) + status_heal(&sd->bl, hp, sp, 0); + + return; +} + +/*========================================== + * Memo player sd savepoint. (map,x,y) + *------------------------------------------*/ +int pc_setsavepoint(struct map_session_data *sd, short mapindex,int x,int y) +{ + nullpo_ret(sd); + + sd->status.save_point.map = mapindex; + sd->status.save_point.x = x; + sd->status.save_point.y = y; + + return 0; +} + +/*========================================== + * Save 1 player data at autosave intervalle + *------------------------------------------*/ +int pc_autosave(int tid, unsigned int tick, int id, intptr_t data) +{ + int interval; + struct s_mapiterator* iter; + struct map_session_data* sd; + static int last_save_id = 0, save_flag = 0; + + if(save_flag == 2) //Someone was saved on last call, normal cycle + save_flag = 0; + else + save_flag = 1; //Noone was saved, so save first found char. + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if(sd->bl.id == last_save_id && save_flag != 1) { + save_flag = 1; + continue; + } + + if(save_flag != 1) //Not our turn to save yet. + continue; + + //Save char. + last_save_id = sd->bl.id; + save_flag = 2; + + chrif_save(sd,0); + break; + } + mapit_free(iter); + + interval = autosave_interval/(map_usercount()+1); + if(interval < minsave_interval) + interval = minsave_interval; + add_timer(gettick()+interval,pc_autosave,0,0); + + return 0; +} + +static int pc_daynight_timer_sub(struct map_session_data *sd,va_list ap) +{ + if (sd->state.night != night_flag && map[sd->bl.m].flag.nightenabled) + { //Night/day state does not match. + clif_status_load(&sd->bl, SI_NIGHT, night_flag); //New night effect by dynamix [Skotlex] + sd->state.night = night_flag; + return 1; + } + return 0; +} +/*================================================ + * timer to do the day [Yor] + * data: 0 = called by timer, 1 = gmcommand/script + *------------------------------------------------*/ +int map_day_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + char tmp_soutput[1024]; + + if (data == 0 && battle_config.day_duration <= 0) // if we want a day + return 0; + + if (!night_flag) + return 0; //Already day. + + night_flag = 0; // 0=day, 1=night [Yor] + map_foreachpc(pc_daynight_timer_sub); + strcpy(tmp_soutput, (data == 0) ? msg_txt(502) : msg_txt(60)); // The day has arrived! + intif_broadcast(tmp_soutput, strlen(tmp_soutput) + 1, 0); + return 0; +} + +/*================================================ + * timer to do the night [Yor] + * data: 0 = called by timer, 1 = gmcommand/script + *------------------------------------------------*/ +int map_night_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + char tmp_soutput[1024]; + + if (data == 0 && battle_config.night_duration <= 0) // if we want a night + return 0; + + if (night_flag) + return 0; //Already nigth. + + night_flag = 1; // 0=day, 1=night [Yor] + map_foreachpc(pc_daynight_timer_sub); + strcpy(tmp_soutput, (data == 0) ? msg_txt(503) : msg_txt(59)); // The night has fallen... + intif_broadcast(tmp_soutput, strlen(tmp_soutput) + 1, 0); + return 0; +} + +void pc_setstand(struct map_session_data *sd){ + nullpo_retv(sd); + + status_change_end(&sd->bl, SC_TENSIONRELAX, INVALID_TIMER); + clif_status_load(&sd->bl,SI_SIT,0); + //Reset sitting tick. + sd->ssregen.tick.hp = sd->ssregen.tick.sp = 0; + sd->state.dead_sit = sd->vd.dead_sit = 0; +} + +/** + * Mechanic (MADO GEAR) + **/ +void pc_overheat(struct map_session_data *sd, int val) { + int heat = val, skill, + limit[] = { 10, 20, 28, 46, 66 }; + + if( !pc_ismadogear(sd) || sd->sc.data[SC_OVERHEAT] ) + return; // already burning + + skill = cap_value(pc_checkskill(sd,NC_MAINFRAME),0,4); + if( sd->sc.data[SC_OVERHEAT_LIMITPOINT] ) { + heat += sd->sc.data[SC_OVERHEAT_LIMITPOINT]->val1; + status_change_end(&sd->bl,SC_OVERHEAT_LIMITPOINT,INVALID_TIMER); + } + + heat = max(0,heat); // Avoid negative HEAT + if( heat >= limit[skill] ) + sc_start(&sd->bl,SC_OVERHEAT,100,0,1000); + else + sc_start(&sd->bl,SC_OVERHEAT_LIMITPOINT,100,heat,30000); + + return; +} + +/** + * Check if player is autolooting given itemID. + */ +bool pc_isautolooting(struct map_session_data *sd, int nameid) +{ + int i; + if( !sd->state.autolooting ) + return false; + ARR_FIND(0, AUTOLOOTITEM_SIZE, i, sd->state.autolootid[i] == nameid); + return (i != AUTOLOOTITEM_SIZE); +} + +/** + * Checks if player can use @/#command + * @param sd Player map session data + * @param command Command name without @/# and params + * @param type is it atcommand or charcommand + */ +bool pc_can_use_command(struct map_session_data *sd, const char *command, AtCommandType type) +{ + return pc_group_can_use_command(pc_get_group_id(sd), command, type); +} + +/** + * Checks if commands used by a player should be logged + * according to their group setting. + * @param sd Player map session data + */ +bool pc_should_log_commands(struct map_session_data *sd) +{ + return pc_group_should_log_commands(pc_get_group_id(sd)); +} + +static int pc_talisman_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + int i, type; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + ARR_FIND(1, 5, type, sd->talisman[type] > 0); + + if( sd->talisman[type] <= 0 ) + { + ShowError("pc_talisman_timer: %d talisman's available. (aid=%d cid=%d tid=%d)\n", sd->talisman[type], sd->status.account_id, sd->status.char_id, tid); + sd->talisman[type] = 0; + return 0; + } + + ARR_FIND(0, sd->talisman[type], i, sd->talisman_timer[type][i] == tid); + if( i == sd->talisman[type] ) + { + ShowError("pc_talisman_timer: timer not found (aid=%d cid=%d tid=%d)\n", sd->status.account_id, sd->status.char_id, tid); + return 0; + } + + sd->talisman[type]--; + if( i != sd->talisman[type] ) + memmove(sd->talisman_timer[type]+i, sd->talisman_timer[type]+i+1, (sd->talisman[type]-i)*sizeof(int)); + sd->talisman_timer[type][sd->talisman[type]] = INVALID_TIMER; + + clif_talisman(sd, type); + + return 0; +} + +int pc_add_talisman(struct map_session_data *sd,int interval,int max,int type) +{ + int tid, i; + + nullpo_ret(sd); + + if(max > 10) + max = 10; + if(sd->talisman[type] < 0) + sd->talisman[type] = 0; + + if( sd->talisman[type] && sd->talisman[type] >= max ) + { + if(sd->talisman_timer[type][0] != INVALID_TIMER) + delete_timer(sd->talisman_timer[type][0],pc_talisman_timer); + sd->talisman[type]--; + if( sd->talisman[type] != 0 ) + memmove(sd->talisman_timer[type]+0, sd->talisman_timer[type]+1, (sd->talisman[type])*sizeof(int)); + sd->talisman_timer[type][sd->talisman[type]] = INVALID_TIMER; + } + + tid = add_timer(gettick()+interval, pc_talisman_timer, sd->bl.id, 0); + ARR_FIND(0, sd->talisman[type], i, sd->talisman_timer[type][i] == INVALID_TIMER || DIFF_TICK(get_timer(tid)->tick, get_timer(sd->talisman_timer[type][i])->tick) < 0); + if( i != sd->talisman[type] ) + memmove(sd->talisman_timer[type]+i+1, sd->talisman_timer[type]+i, (sd->talisman[type]-i)*sizeof(int)); + sd->talisman_timer[type][i] = tid; + sd->talisman[type]++; + + clif_talisman(sd, type); + return 0; +} + +int pc_del_talisman(struct map_session_data *sd,int count,int type) +{ + int i; + + nullpo_ret(sd); + + if( sd->talisman[type] <= 0 ) { + sd->talisman[type] = 0; + return 0; + } + + if( count <= 0 ) + return 0; + if( count > sd->talisman[type] ) + count = sd->talisman[type]; + sd->talisman[type] -= count; + if( count > 10 ) + count = 10; + + for(i = 0; i < count; i++) { + if(sd->talisman_timer[type][i] != INVALID_TIMER) { + delete_timer(sd->talisman_timer[type][i],pc_talisman_timer); + sd->talisman_timer[type][i] = INVALID_TIMER; + } + } + for(i = count; i < 10; i++) { + sd->talisman_timer[type][i-count] = sd->talisman_timer[type][i]; + sd->talisman_timer[type][i] = INVALID_TIMER; + } + + clif_talisman(sd, type); + return 0; +} +#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) +/*========================================== + * Renewal EXP/Itemdrop rate modifier base on level penalty + * 1=exp 2=itemdrop + *------------------------------------------*/ +int pc_level_penalty_mod(struct map_session_data *sd, struct mob_data *md, int type) +{ + int diff, rate = 100, i; + + nullpo_ret(sd); + nullpo_ret(md); + + diff = md->level - sd->status.base_level; + + if( diff < 0 ) + diff = MAX_LEVEL + ( ~diff + 1 ); + + for(i=0; i<RC_MAX; i++){ + int tmp; + + if( md->status.race != i ){ + if( md->status.mode&MD_BOSS && i < RC_BOSS ) + i = RC_BOSS; + else if( i <= RC_BOSS ) + continue; + } + + if( (tmp=level_penalty[type][i][diff]) > 0 ){ + rate = tmp; + break; + } + } + + return rate; +} +#endif +int pc_split_str(char *str,char **val,int num) +{ + int i; + + for (i=0; i<num && str; i++){ + val[i] = str; + str = strchr(str,','); + if (str && i<num-1) //Do not remove a trailing comma. + *str++=0; + } + return i; +} + +int pc_split_atoi(char* str, int* val, char sep, int max) +{ + int i,j; + for (i=0; i<max; i++) { + if (!str) break; + val[i] = atoi(str); + str = strchr(str,sep); + if (str) + *str++=0; + } + //Zero up the remaining. + for(j=i; j < max; j++) + val[j] = 0; + return i; +} + +int pc_split_atoui(char* str, unsigned int* val, char sep, int max) +{ + static int warning=0; + int i,j; + double f; + for (i=0; i<max; i++) { + if (!str) break; + f = atof(str); + if (f < 0) + val[i] = 0; + else if (f > UINT_MAX) { + val[i] = UINT_MAX; + if (!warning) { + warning = 1; + ShowWarning("pc_readdb (exp.txt): Required exp per level is capped to %u\n", UINT_MAX); + } + } else + val[i] = (unsigned int)f; + str = strchr(str,sep); + if (str) + *str++=0; + } + //Zero up the remaining. + for(j=i; j < max; j++) + val[j] = 0; + return i; +} + +/*========================================== + * sub DB reading. + * Function used to read skill_tree.txt + *------------------------------------------*/ +static bool pc_readdb_skilltree(char* fields[], int columns, int current) +{ + unsigned char joblv = 0, skill_lv; + uint16 skill_id; + int idx, class_; + unsigned int i, offset = 3, skill_idx; + + class_ = atoi(fields[0]); + skill_id = (uint16)atoi(fields[1]); + skill_lv = (unsigned char)atoi(fields[2]); + + if(columns==4+MAX_PC_SKILL_REQUIRE*2) + {// job level requirement extra column + joblv = (unsigned char)atoi(fields[3]); + offset++; + } + + if(!pcdb_checkid(class_)) + { + ShowWarning("pc_readdb_skilltree: Invalid job class %d specified.\n", class_); + return false; + } + idx = pc_class2idx(class_); + + //This is to avoid adding two lines for the same skill. [Skotlex] + ARR_FIND( 0, MAX_SKILL_TREE, skill_idx, skill_tree[idx][skill_idx].id == 0 || skill_tree[idx][skill_idx].id == skill_id ); + if( skill_idx == MAX_SKILL_TREE ) + { + ShowWarning("pc_readdb_skilltree: Unable to load skill %hu into job %d's tree. Maximum number of skills per class has been reached.\n", skill_id, class_); + return false; + } + else if(skill_tree[idx][skill_idx].id) + { + ShowNotice("pc_readdb_skilltree: Overwriting skill %hu for job class %d.\n", skill_id, class_); + } + + skill_tree[idx][skill_idx].id = skill_id; + skill_tree[idx][skill_idx].max = skill_lv; + skill_tree[idx][skill_idx].joblv = joblv; + + for(i = 0; i < MAX_PC_SKILL_REQUIRE; i++) + { + skill_tree[idx][skill_idx].need[i].id = atoi(fields[i*2+offset]); + skill_tree[idx][skill_idx].need[i].lv = atoi(fields[i*2+offset+1]); + } + return true; +} +#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) +static bool pc_readdb_levelpenalty(char* fields[], int columns, int current) +{ + int type, race, diff; + + type = atoi(fields[0]); + race = atoi(fields[1]); + diff = atoi(fields[2]); + + if( type != 1 && type != 2 ){ + ShowWarning("pc_readdb_levelpenalty: Invalid type %d specified.\n", type); + return false; + } + + if( race < 0 && race > RC_MAX ){ + ShowWarning("pc_readdb_levelpenalty: Invalid race %d specified.\n", race); + return false; + } + + diff = min(diff, MAX_LEVEL); + + if( diff < 0 ) + diff = min(MAX_LEVEL + ( ~(diff) + 1 ), MAX_LEVEL*2); + + level_penalty[type][race][diff] = atoi(fields[3]); + + return true; +} +#endif + +/*========================================== + * pc DB reading. + * exp.txt - required experience values + * skill_tree.txt - skill tree for every class + * attr_fix.txt - elemental adjustment table + *------------------------------------------*/ +int pc_readdb(void) +{ + int i,j,k,tmp=0; + FILE *fp; + char line[24000],*p; + + //reset + memset(exp_table,0,sizeof(exp_table)); + memset(max_level,0,sizeof(max_level)); + + sprintf(line, "%s/"DBPATH"exp.txt", db_path); + + fp=fopen(line, "r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + int jobs[CLASS_COUNT], job_count, job, job_id; + int type; + unsigned int ui,maxlv; + char *split[4]; + if(line[0]=='/' && line[1]=='/') + continue; + if (pc_split_str(line,split,4) < 4) + continue; + + job_count = pc_split_atoi(split[1],jobs,':',CLASS_COUNT); + if (job_count < 1) + continue; + job_id = jobs[0]; + if (!pcdb_checkid(job_id)) { + ShowError("pc_readdb: Invalid job ID %d.\n", job_id); + continue; + } + type = atoi(split[2]); + if (type < 0 || type > 1) { + ShowError("pc_readdb: Invalid type %d (must be 0 for base levels, 1 for job levels).\n", type); + continue; + } + maxlv = atoi(split[0]); + if (maxlv > MAX_LEVEL) { + ShowWarning("pc_readdb: Specified max level %u for job %d is beyond server's limit (%u).\n ", maxlv, job_id, MAX_LEVEL); + maxlv = MAX_LEVEL; + } + + job = jobs[0] = pc_class2idx(job_id); + //We send one less and then one more because the last entry in the exp array should hold 0. + max_level[job][type] = pc_split_atoui(split[3], exp_table[job][type],',',maxlv-1)+1; + //Reverse check in case the array has a bunch of trailing zeros... [Skotlex] + //The reasoning behind the -2 is this... if the max level is 5, then the array + //should look like this: + //0: x, 1: x, 2: x: 3: x 4: 0 <- last valid value is at 3. + while ((ui = max_level[job][type]) >= 2 && exp_table[job][type][ui-2] <= 0) + max_level[job][type]--; + if (max_level[job][type] < maxlv) { + ShowWarning("pc_readdb: Specified max %u for job %d, but that job's exp table only goes up to level %u.\n", maxlv, job_id, max_level[job][type]); + ShowInfo("Filling the missing values with the last exp entry.\n"); + //Fill the requested values with the last entry. + ui = (max_level[job][type] <= 2? 0: max_level[job][type]-2); + for (; ui+2 < maxlv; ui++) + exp_table[job][type][ui] = exp_table[job][type][ui-1]; + max_level[job][type] = maxlv; + } +// ShowDebug("%s - Class %d: %d\n", type?"Job":"Base", job_id, max_level[job][type]); + for (i = 1; i < job_count; i++) { + job_id = jobs[i]; + if (!pcdb_checkid(job_id)) { + ShowError("pc_readdb: Invalid job ID %d.\n", job_id); + continue; + } + job = pc_class2idx(job_id); + memcpy(exp_table[job][type], exp_table[jobs[0]][type], sizeof(exp_table[0][0])); + max_level[job][type] = maxlv; +// ShowDebug("%s - Class %d: %u\n", type?"Job":"Base", job_id, max_level[job][type]); + } + } + fclose(fp); + for (i = 0; i < JOB_MAX; i++) { + if (!pcdb_checkid(i)) continue; + if (i == JOB_WEDDING || i == JOB_XMAS || i == JOB_SUMMER) + continue; //Classes that do not need exp tables. + j = pc_class2idx(i); + if (!max_level[j][0]) + ShowWarning("Class %s (%d) does not has a base exp table.\n", job_name(i), i); + if (!max_level[j][1]) + ShowWarning("Class %s (%d) does not has a job exp table.\n", job_name(i), i); + } + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n","exp.txt"); + + // Reset and read skilltree + memset(skill_tree,0,sizeof(skill_tree)); + sv_readdb(db_path, DBPATH"skill_tree.txt", ',', 3+MAX_PC_SKILL_REQUIRE*2, 4+MAX_PC_SKILL_REQUIRE*2, -1, &pc_readdb_skilltree); + +#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) + sv_readdb(db_path, "re/level_penalty.txt", ',', 4, 4, -1, &pc_readdb_levelpenalty); + for( k=1; k < 3; k++ ){ // fill in the blanks + for( j = 0; j < RC_MAX; j++ ){ + tmp = 0; + for( i = 0; i < MAX_LEVEL*2; i++ ){ + if( i == MAX_LEVEL+1 ) + tmp = level_penalty[k][j][0];// reset + if( level_penalty[k][j][i] > 0 ) + tmp = level_penalty[k][j][i]; + else + level_penalty[k][j][i] = tmp; + } + } + } +#endif + + // Reset then read attr_fix + for(i=0;i<4;i++) + for(j=0;j<ELE_MAX;j++) + for(k=0;k<ELE_MAX;k++) + attr_fix_table[i][j][k]=100; + + sprintf(line, "%s/"DBPATH"attr_fix.txt", db_path); + + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + char *split[10]; + int lv,n; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<3 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if( j < 2 ) + continue; + + lv=atoi(split[0]); + n=atoi(split[1]); + + for(i=0;i<n && i<ELE_MAX;){ + if( !fgets(line, sizeof(line), fp) ) + break; + if(line[0]=='/' && line[1]=='/') + continue; + + for(j=0,p=line;j<n && j<ELE_MAX && p;j++){ + while(*p==32 && *p>0) + p++; + attr_fix_table[lv-1][i][j]=atoi(p); + if(battle_config.attr_recover == 0 && attr_fix_table[lv-1][i][j] < 0) + attr_fix_table[lv-1][i][j] = 0; + p=strchr(p,','); + if(p) *p++=0; + } + + i++; + } + } + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n","attr_fix.txt"); + + // reset then read statspoint + memset(statp,0,sizeof(statp)); + i=1; + + sprintf(line, "%s/"DBPATH"statpoint.txt", db_path); + fp=fopen(line,"r"); + if(fp == NULL){ + ShowWarning("Can't read '"CL_WHITE"%s"CL_RESET"'... Generating DB.\n",line); + //return 1; + } else { + while(fgets(line, sizeof(line), fp)) + { + int stat; + if(line[0]=='/' && line[1]=='/') + continue; + if ((stat=strtoul(line,NULL,10))<0) + stat=0; + if (i > MAX_LEVEL) + break; + statp[i]=stat; + i++; + } + fclose(fp); + + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n","statpoint.txt"); + } + // generate the remaining parts of the db if necessary + k = battle_config.use_statpoint_table; //save setting + battle_config.use_statpoint_table = 0; //temporarily disable to force pc_gets_status_point use default values + statp[0] = 45; // seed value + for (; i <= MAX_LEVEL; i++) + statp[i] = statp[i-1] + pc_gets_status_point(i-1); + battle_config.use_statpoint_table = k; //restore setting + + return 0; +} + +// Read MOTD on startup. [Valaris] +int pc_read_motd(void) +{ + FILE* fp; + + // clear old MOTD + memset(motd_text, 0, sizeof(motd_text)); + + // read current MOTD + if( ( fp = fopen(motd_txt, "r") ) != NULL ) + { + char* buf, * ptr; + unsigned int lines = 0, entries = 0; + size_t len; + + while( entries < MOTD_LINE_SIZE && fgets(motd_text[entries], sizeof(motd_text[entries]), fp) ) + { + lines++; + + buf = motd_text[entries]; + + if( buf[0] == '/' && buf[1] == '/' ) + { + continue; + } + + len = strlen(buf); + + while( len && ( buf[len-1] == '\r' || buf[len-1] == '\n' ) ) + {// strip trailing EOL characters + len--; + } + + if( len ) + { + buf[len] = 0; + + if( ( ptr = strstr(buf, " :") ) != NULL && ptr-buf >= NAME_LENGTH ) + {// crashes newer clients + ShowWarning("Found sequence '"CL_WHITE" :"CL_RESET"' on line '"CL_WHITE"%u"CL_RESET"' in '"CL_WHITE"%s"CL_RESET"'. This can cause newer clients to crash.\n", lines, motd_txt); + } + } + else + {// empty line + buf[0] = ' '; + buf[1] = 0; + } + entries++; + } + fclose(fp); + + ShowStatus("Done reading '"CL_WHITE"%u"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, motd_txt); + } + else + { + ShowWarning("File '"CL_WHITE"%s"CL_RESET"' not found.\n", motd_txt); + } + + return 0; +} +void pc_itemcd_do(struct map_session_data *sd, bool load) { + int i,cursor = 0; + struct item_cd* cd = NULL; + + if( load ) { + if( !(cd = idb_get(itemcd_db, sd->status.char_id)) ) { + // no skill cooldown is associated with this character + return; + } + for(i = 0; i < MAX_ITEMDELAYS; i++) { + if( cd->nameid[i] && DIFF_TICK(gettick(),cd->tick[i]) < 0 ) { + sd->item_delay[cursor].tick = cd->tick[i]; + sd->item_delay[cursor].nameid = cd->nameid[i]; + cursor++; + } + } + idb_remove(itemcd_db,sd->status.char_id); + } else { + if( !(cd = idb_get(itemcd_db,sd->status.char_id)) ) { + // create a new skill cooldown object for map storage + CREATE( cd, struct item_cd, 1 ); + idb_put( itemcd_db, sd->status.char_id, cd ); + } + for(i = 0; i < MAX_ITEMDELAYS; i++) { + if( sd->item_delay[i].nameid && DIFF_TICK(gettick(),sd->item_delay[i].tick) < 0 ) { + cd->tick[cursor] = sd->item_delay[i].tick; + cd->nameid[cursor] = sd->item_delay[i].nameid; + cursor++; + } + } + } + return; +} +/*========================================== + * pc Init/Terminate + *------------------------------------------*/ +void do_final_pc(void) { + + db_destroy(itemcd_db); + + do_final_pc_groups(); + return; +} + +int do_init_pc(void) { + + itemcd_db = idb_alloc(DB_OPT_RELEASE_DATA); + + pc_readdb(); + pc_read_motd(); // Read MOTD [Valaris] + + add_timer_func_list(pc_invincible_timer, "pc_invincible_timer"); + add_timer_func_list(pc_eventtimer, "pc_eventtimer"); + add_timer_func_list(pc_inventory_rental_end, "pc_inventory_rental_end"); + add_timer_func_list(pc_calc_pvprank_timer, "pc_calc_pvprank_timer"); + add_timer_func_list(pc_autosave, "pc_autosave"); + add_timer_func_list(pc_spiritball_timer, "pc_spiritball_timer"); + add_timer_func_list(pc_follow_timer, "pc_follow_timer"); + add_timer_func_list(pc_endautobonus, "pc_endautobonus"); + add_timer_func_list(pc_talisman_timer, "pc_talisman_timer"); + + add_timer(gettick() + autosave_interval, pc_autosave, 0, 0); + + // 0=day, 1=night [Yor] + night_flag = battle_config.night_at_start ? 1 : 0; + + if (battle_config.day_duration > 0 && battle_config.night_duration > 0) { + int day_duration = battle_config.day_duration; + int night_duration = battle_config.night_duration; + // add night/day timer [Yor] + add_timer_func_list(map_day_timer, "map_day_timer"); + add_timer_func_list(map_night_timer, "map_night_timer"); + + day_timer_tid = add_timer_interval(gettick() + (night_flag ? 0 : day_duration) + night_duration, map_day_timer, 0, 0, day_duration + night_duration); + night_timer_tid = add_timer_interval(gettick() + day_duration + (night_flag ? night_duration : 0), map_night_timer, 0, 0, day_duration + night_duration); + } + + do_init_pc_groups(); + + return 0; +} diff --git a/src/map/pc.h b/src/map/pc.h new file mode 100644 index 000000000..3027c5f10 --- /dev/null +++ b/src/map/pc.h @@ -0,0 +1,927 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PC_H_ +#define _PC_H_ + +#include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus +#include "../common/timer.h" // INVALID_TIMER +#include "atcommand.h" // AtCommandType +#include "battle.h" // battle_config +#include "buyingstore.h" // struct s_buyingstore +#include "itemdb.h" // MAX_ITEMGROUP +#include "map.h" // RC_MAX +#include "script.h" // struct script_reg, struct script_regstr +#include "searchstore.h" // struct s_search_store_info +#include "status.h" // OPTION_*, struct weapon_atk +#include "unit.h" // unit_stop_attack(), unit_stop_walking() +#include "vending.h" // struct s_vending +#include "mob.h" +#include "log.h" +#include "pc_groups.h" + +#define MAX_PC_BONUS 10 +#define MAX_PC_SKILL_REQUIRE 5 +#define MAX_PC_FEELHATE 3 + +struct weapon_data { + int atkmods[3]; + // all the variables except atkmods get zero'ed in each call of status_calc_pc + // NOTE: if you want to add a non-zeroed variable, you need to update the memset call + // in status_calc_pc as well! All the following are automatically zero'ed. [Skotlex] + int overrefine; + int star; + int ignore_def_ele; + int ignore_def_race; + int def_ratio_atk_ele; + int def_ratio_atk_race; + int addele[ELE_MAX]; + int addrace[RC_MAX]; + int addrace2[RC2_MAX]; + int addsize[3]; + + struct drain_data { + short rate; + short per; + short value; + unsigned type:1; + } hp_drain[RC_MAX], sp_drain[RC_MAX]; + + struct { + short class_, rate; + } add_dmg[MAX_PC_BONUS]; + + struct { + short flag, rate; + unsigned char ele; + } addele2[MAX_PC_BONUS]; +}; + +struct s_autospell { + short id, lv, rate, card_id, flag; + bool lock; // bAutoSpellOnSkill: blocks autospell from triggering again, while being executed +}; + +struct s_addeffect { + enum sc_type id; + short rate, arrow_rate; + unsigned char flag; +}; + +struct s_addeffectonskill { + enum sc_type id; + short rate, skill; + unsigned char target; +}; + +struct s_add_drop { + short id, group; + int race, rate; +}; + +struct s_autobonus { + short rate,atk_type; + unsigned int duration; + char *bonus_script, *other_script; + int active; + unsigned short pos; +}; + +struct map_session_data { + struct block_list bl; + struct unit_data ud; + struct view_data vd; + struct status_data base_status, battle_status; + struct status_change sc; + struct regen_data regen; + struct regen_data_sub sregen, ssregen; + //NOTE: When deciding to add a flag to state or special_state, take into consideration that state is preserved in + //status_calc_pc, while special_state is recalculated in each call. [Skotlex] + struct { + unsigned int active : 1; //Marks active player (not active is logging in/out, or changing map servers) + unsigned int menu_or_input : 1;// if a script is waiting for feedback from the player + unsigned int dead_sit : 2; + unsigned int lr_flag : 3;//1: left h. weapon; 2: arrow; 3: shield + unsigned int connect_new : 1; + unsigned int arrow_atk : 1; + unsigned int gangsterparadise : 1; + unsigned int rest : 1; + unsigned int storage_flag : 2; //0: closed, 1: Normal Storage open, 2: guild storage open [Skotlex] + unsigned int snovice_dead_flag : 1; //Explosion spirits on death: 0 off, 1 used. + unsigned int abra_flag : 2; // Abracadabra bugfix by Aru + unsigned int autocast : 1; // Autospell flag [Inkfish] + unsigned int autotrade : 1; //By Fantik + unsigned int reg_dirty : 4; //By Skotlex (marks whether registry variables have been saved or not yet) + unsigned int showdelay :1; + unsigned int showexp :1; + unsigned int showzeny :1; + unsigned int mainchat :1; //[LuzZza] + unsigned int noask :1; // [LuzZza] + unsigned int trading :1; //[Skotlex] is 1 only after a trade has started. + unsigned int deal_locked :2; //1: Clicked on OK. 2: Clicked on TRADE + unsigned int monster_ignore :1; // for monsters to ignore a character [Valaris] [zzo] + unsigned int size :2; // for tiny/large types + unsigned int night :1; //Holds whether or not the player currently has the SI_NIGHT effect on. [Skotlex] + unsigned int blockedmove :1; + unsigned int using_fake_npc :1; + unsigned int rewarp :1; //Signals that a player should warp as soon as he is done loading a map. [Skotlex] + unsigned int killer : 1; + unsigned int killable : 1; + unsigned int doridori : 1; + unsigned int ignoreAll : 1; + unsigned int debug_remove_map : 1; // temporary state to track double remove_map's [FlavioJS] + unsigned int buyingstore : 1; + unsigned int lesseffect : 1; + unsigned int vending : 1; + unsigned int noks : 3; // [Zeph Kill Steal Protection] + unsigned int changemap : 1; + unsigned int callshop : 1; // flag to indicate that a script used callshop; on a shop + short pmap; // Previous map on Map Change + unsigned short autoloot; + unsigned short autolootid[AUTOLOOTITEM_SIZE]; // [Zephyrus] + unsigned int autolooting : 1; //performance-saver, autolooting state for @alootid + unsigned short autobonus; //flag to indicate if an autobonus is activated. [Inkfish] + struct guild *gmaster_flag; + unsigned int prevend : 1;//used to flag wheather you've spent 40sp to open the vending or not. + unsigned int warping : 1;//states whether you're in the middle of a warp processing + } state; + struct { + unsigned char no_weapon_damage, no_magic_damage, no_misc_damage; + unsigned int restart_full_recover : 1; + unsigned int no_castcancel : 1; + unsigned int no_castcancel2 : 1; + unsigned int no_sizefix : 1; + unsigned int no_gemstone : 1; + unsigned int intravision : 1; // Maya Purple Card effect [DracoRPG] + unsigned int perfect_hiding : 1; // [Valaris] + unsigned int no_knockback : 1; + unsigned int bonus_coma : 1; + } special_state; + int login_id1, login_id2; + unsigned short class_; //This is the internal job ID used by the map server to simplify comparisons/queries/etc. [Skotlex] + int group_id, group_pos, group_level; + unsigned int permissions;/* group permissions */ + + int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 ... 18 + struct mmo_charstatus status; + struct registry save_reg; + + struct item_data* inventory_data[MAX_INVENTORY]; // direct pointers to itemdb entries (faster than doing item_id lookups) + short equip_index[14]; + unsigned int weight,max_weight; + int cart_weight,cart_num,cart_weight_max; + int fd; + unsigned short mapindex; + unsigned char head_dir; //0: Look forward. 1: Look right, 2: Look left. + unsigned int client_tick; + int npc_id,areanpc_id,npc_shopid,touching_id; //for script follow scriptoid; ,npcid + int npc_item_flag; //Marks the npc_id with which you can use items during interactions with said npc (see script command enable_itemuse) + int npc_menu; // internal variable, used in npc menu handling + int npc_amount; + struct script_state *st; + char npc_str[CHATBOX_SIZE]; // for passing npc input box text to script engine + int npc_timer_id; //For player attached npc timers. [Skotlex] + unsigned int chatID; + time_t idletime; + + struct{ + int npc_id; + unsigned int timeout; + } progressbar; //Progress Bar [Inkfish] + + struct{ + char name[NAME_LENGTH]; + } ignore[MAX_IGNORE_LIST]; + + int followtimer; // [MouseJstr] + int followtarget; + + time_t emotionlasttime; // to limit flood with emotion packets + + short skillitem,skillitemlv; + uint16 skill_id_old,skill_lv_old; + uint16 skill_id_dance,skill_lv_dance; + short cook_mastery; // range: [0,1999] [Inkfish] + unsigned char blockskill[MAX_SKILL]; + int cloneskill_id, reproduceskill_id; + int menuskill_id, menuskill_val, menuskill_val2; + + int invincible_timer; + unsigned int canlog_tick; + unsigned int canuseitem_tick; // [Skotlex] + unsigned int canusecashfood_tick; + unsigned int canequip_tick; // [Inkfish] + unsigned int cantalk_tick; + unsigned int canskill_tick; // used to prevent abuse from no-delay ACT files + unsigned int cansendmail_tick; // [Mail System Flood Protection] + unsigned int ks_floodprotect_tick; // [Kill Steal Protection] + unsigned int bloodylust_tick; // bloodylust player timer [out/in re full-heal protection] + + struct { + short nameid; + unsigned int tick; + } item_delay[MAX_ITEMDELAYS]; // [Paradox924X] + + short weapontype1,weapontype2; + short disguise; // [Valaris] + + struct weapon_data right_weapon, left_weapon; + + // here start arrays to be globally zeroed at the beginning of status_calc_pc() + int param_bonus[6],param_equip[6]; //Stores card/equipment bonuses. + int subele[ELE_MAX]; + int subrace[RC_MAX]; + int subrace2[RC2_MAX]; + int subsize[3]; + int reseff[SC_COMMON_MAX-SC_COMMON_MIN+1]; + int weapon_coma_ele[ELE_MAX]; + int weapon_coma_race[RC_MAX]; + int weapon_atk[16]; + int weapon_atk_rate[16]; + int arrow_addele[ELE_MAX]; + int arrow_addrace[RC_MAX]; + int arrow_addsize[3]; + int magic_addele[ELE_MAX]; + int magic_addrace[RC_MAX]; + int magic_addsize[3]; + int magic_atk_ele[ELE_MAX]; + int critaddrace[RC_MAX]; + int expaddrace[RC_MAX]; + int ignore_mdef[RC_MAX]; + int ignore_def[RC_MAX]; + int itemgrouphealrate[MAX_ITEMGROUP]; + short sp_gain_race[RC_MAX]; + short sp_gain_race_attack[RC_MAX]; + short hp_gain_race_attack[RC_MAX]; + // zeroed arrays end here. + // zeroed structures start here + struct s_autospell autospell[15], autospell2[15], autospell3[15]; + struct s_addeffect addeff[MAX_PC_BONUS], addeff2[MAX_PC_BONUS]; + struct s_addeffectonskill addeff3[MAX_PC_BONUS]; + + struct { //skillatk raises bonus dmg% of skills, skillheal increases heal%, skillblown increases bonus blewcount for some skills. + unsigned short id; + short val; + } skillatk[MAX_PC_BONUS], skillusesprate[MAX_PC_BONUS], skillusesp[MAX_PC_BONUS], skillheal[5], skillheal2[5], skillblown[MAX_PC_BONUS], skillcast[MAX_PC_BONUS], skillcooldown[MAX_PC_BONUS], skillfixcast[MAX_PC_BONUS], skillvarcast[MAX_PC_BONUS]; + struct { + short value; + int rate; + int tick; + } hp_loss, sp_loss, hp_regen, sp_regen; + struct { + short class_, rate; + } add_def[MAX_PC_BONUS], add_mdef[MAX_PC_BONUS], add_mdmg[MAX_PC_BONUS]; + struct s_add_drop add_drop[MAX_PC_BONUS]; + struct { + int nameid; + int rate; + } itemhealrate[MAX_PC_BONUS]; + struct { + short flag, rate; + unsigned char ele; + } subele2[MAX_PC_BONUS]; + // zeroed structures end here + // manually zeroed structures start here. + struct s_autobonus autobonus[MAX_PC_BONUS], autobonus2[MAX_PC_BONUS], autobonus3[MAX_PC_BONUS]; //Auto script on attack, when attacked, on skill usage + // manually zeroed structures end here. + // zeroed vars start here. + struct { + int atk_rate; + int arrow_atk,arrow_ele,arrow_cri,arrow_hit; + int nsshealhp,nsshealsp; + int critical_def,double_rate; + int long_attack_atk_rate; //Long range atk rate, not weapon based. [Skotlex] + int near_attack_def_rate,long_attack_def_rate,magic_def_rate,misc_def_rate; + int ignore_mdef_ele; + int ignore_mdef_race; + int perfect_hit; + int perfect_hit_add; + int get_zeny_rate; + int get_zeny_num; //Added Get Zeny Rate [Skotlex] + int double_add_rate; + int short_weapon_damage_return,long_weapon_damage_return; + int magic_damage_return; // AppleGirl Was Here + int break_weapon_rate,break_armor_rate; + int crit_atk_rate; + int classchange; // [Valaris] + int speed_rate, speed_add_rate, aspd_add; + int itemhealrate2; // [Epoque] Increase heal rate of all healing items. + int shieldmdef;//royal guard's + unsigned int setitem_hash, setitem_hash2; //Split in 2 because shift operations only work on int ranges. [Skotlex] + + short splash_range, splash_add_range; + short add_steal_rate; + short add_heal_rate, add_heal2_rate; + short sp_gain_value, hp_gain_value, magic_sp_gain_value, magic_hp_gain_value; + short sp_vanish_rate; + short sp_vanish_per; + unsigned short unbreakable; // chance to prevent ANY equipment breaking [celest] + unsigned short unbreakable_equip; //100% break resistance on certain equipment + unsigned short unstripable_equip; + int fixcastrate,varcastrate; + int ematk; // matk bonus from equipment +// int eatk; // atk bonus from equipment + } bonus; + + // zeroed vars end here. + + int castrate,delayrate,hprate,sprate,dsprate; + int hprecov_rate,sprecov_rate; + int matk_rate; + int critical_rate,hit_rate,flee_rate,flee2_rate,def_rate,def2_rate,mdef_rate,mdef2_rate; + + int itemid; + short itemindex; //Used item's index in sd->inventory [Skotlex] + + short catch_target_class; // pet catching, stores a pet class to catch (short now) [zzo] + + short spiritball, spiritball_old; + int spirit_timer[MAX_SKILL_LEVEL]; + short talisman[ELE_POISON+1]; // There are actually 5 talisman Fire, Ice, Wind, Earth & Poison maybe because its color violet. + int talisman_timer[ELE_POISON+1][10]; + + unsigned char potion_success_counter; //Potion successes in row counter + unsigned char mission_count; //Stores the bounty kill count for TK_MISSION + short mission_mobid; //Stores the target mob_id for TK_MISSION + int die_counter; //Total number of times you've died + int devotion[5]; //Stores the account IDs of chars devoted to. + int reg_num; //Number of registries (type numeric) + int regstr_num; //Number of registries (type string) + + struct script_reg *reg; + struct script_regstr *regstr; + + int trade_partner; + struct { + struct { + short index, amount; + } item[10]; + int zeny, weight; + } deal; + + bool party_creating; // whether the char is requesting party creation + bool party_joining; // whether the char is accepting party invitation + int party_invite, party_invite_account; // for handling party invitation (holds party id and account id) + int adopt_invite; // Adoption + + int guild_invite,guild_invite_account; + int guild_emblem_id,guild_alliance,guild_alliance_account; + short guild_x,guild_y; // For guildmate position display. [Skotlex] should be short [zzo] + int guildspy; // [Syrus22] + int partyspy; // [Syrus22] + + int vended_id; + int vender_id; + int vend_num; + char message[MESSAGE_SIZE]; + struct s_vending vending[MAX_VENDING]; + + unsigned int buyer_id; // uid of open buying store + struct s_buyingstore buyingstore; + + struct s_search_store_info searchstore; + + struct pet_data *pd; + struct homun_data *hd; // [blackhole89] + struct mercenary_data *md; + struct elemental_data *ed; + + struct{ + int m; //-1 - none, other: map index corresponding to map name. + unsigned short index; //map index + }feel_map[3];// 0 - Sun; 1 - Moon; 2 - Stars + short hate_mob[3]; + + int pvp_timer; + short pvp_point; + unsigned short pvp_rank, pvp_lastusers; + unsigned short pvp_won, pvp_lost; + + char eventqueue[MAX_EVENTQUEUE][EVENT_NAME_LENGTH]; + int eventtimer[MAX_EVENTTIMER]; + unsigned short eventcount; // [celest] + + unsigned char change_level_2nd; // job level when changing from 1st to 2nd class [jobchange_level in global_reg_value] + unsigned char change_level_3rd; // job level when changing from 2nd to 3rd class [jobchange_level_3rd in global_reg_value] + + char fakename[NAME_LENGTH]; // fake names [Valaris] + + int duel_group; // duel vars [LuzZza] + int duel_invite; + + int killerrid, killedrid; + + int cashPoints, kafraPoints; + int rental_timer; + + // Auction System [Zephyrus] + struct { + int index, amount; + } auction; + + // Mail System [Zephyrus] + struct { + short nameid; + int index, amount, zeny; + struct mail_data inbox; + bool changed; // if true, should sync with charserver on next mailbox request + } mail; + + //Quest log system [Kevin] [Inkfish] + int num_quests; + int avail_quests; + int quest_index[MAX_QUEST_DB]; + struct quest quest_log[MAX_QUEST_DB]; + bool save_quest; + + // temporary debug [flaviojs] + const char* debug_file; + int debug_line; + const char* debug_func; + + unsigned int bg_id; + unsigned short user_font; + + /** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + /** + * ID of the timer + * @info + * - value is -1 (INVALID_TIMER constant) when not being used + * - timer is cancelled upon closure of the current npc's instance + **/ + int npc_idle_timer; + /** + * Tick on the last recorded NPC iteration (next/menu/whatever) + * @info + * - It is updated on every NPC iteration as mentioned above + **/ + unsigned int npc_idle_tick; +#endif + + struct { + struct script_code **bonus;/* the script */ + unsigned short *id;/* array of combo ids */ + unsigned char count; + } combos; + + /** + * Guarantees your friend request is legit (for bugreport:4629) + **/ + int friend_req; + + int shadowform_id; + + // temporary debugging of bug #3504 + const char* delunit_prevfile; + int delunit_prevline; + +}; + +//Update this max as necessary. 55 is the value needed for Super Baby currently +//Raised to 84 since Expanded Super Novice needs it. +#define MAX_SKILL_TREE 84 +//Total number of classes (for data storage) +#define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC) + +enum weapon_type { + W_FIST, //Bare hands + W_DAGGER, //1 + W_1HSWORD, //2 + W_2HSWORD, //3 + W_1HSPEAR, //4 + W_2HSPEAR, //5 + W_1HAXE, //6 + W_2HAXE, //7 + W_MACE, //8 + W_2HMACE, //9 (unused) + W_STAFF, //10 + W_BOW, //11 + W_KNUCKLE, //12 + W_MUSICAL, //13 + W_WHIP, //14 + W_BOOK, //15 + W_KATAR, //16 + W_REVOLVER, //17 + W_RIFLE, //18 + W_GATLING, //19 + W_SHOTGUN, //20 + W_GRENADE, //21 + W_HUUMA, //22 + W_2HSTAFF, //23 + MAX_WEAPON_TYPE, + // dual-wield constants + W_DOUBLE_DD, // 2 daggers + W_DOUBLE_SS, // 2 swords + W_DOUBLE_AA, // 2 axes + W_DOUBLE_DS, // dagger + sword + W_DOUBLE_DA, // dagger + axe + W_DOUBLE_SA, // sword + axe +}; + +enum ammo_type { + A_ARROW = 1, + A_DAGGER, //2 + A_BULLET, //3 + A_SHELL, //4 + A_GRENADE, //5 + A_SHURIKEN, //6 + A_KUNAI, //7 + A_CANNONBALL, //8 + A_THROWWEAPON //9 +}; + +//Equip position constants +enum equip_pos { + EQP_HEAD_LOW = 0x0001, + EQP_HEAD_MID = 0x0200, //512 + EQP_HEAD_TOP = 0x0100, //256 + EQP_HAND_R = 0x0002, + EQP_HAND_L = 0x0020, //32 + EQP_ARMOR = 0x0010, //16 + EQP_SHOES = 0x0040, //64 + EQP_GARMENT = 0x0004, + EQP_ACC_L = 0x0008, + EQP_ACC_R = 0x0080, //128 + EQP_COSTUME_HEAD_TOP = 0x0400, + EQP_COSTUME_HEAD_MID = 0x0800, + EQP_COSTUME_HEAD_LOW = 0x1000, + EQP_AMMO = 0x8000, //32768 +}; + +#define EQP_WEAPON EQP_HAND_R +#define EQP_SHIELD EQP_HAND_L +#define EQP_ARMS (EQP_HAND_R|EQP_HAND_L) +#define EQP_HELM (EQP_HEAD_LOW|EQP_HEAD_MID|EQP_HEAD_TOP) +#define EQP_ACC (EQP_ACC_L|EQP_ACC_R) +#define EQP_COSTUME (EQP_COSTUME_HEAD_TOP|EQP_COSTUME_HEAD_MID|EQP_COSTUME_HEAD_LOW) + +/// Equip positions that use a visible sprite +#if PACKETVER < 20110111 + #define EQP_VISIBLE EQP_HELM +#else + #define EQP_VISIBLE (EQP_HELM|EQP_GARMENT|EQP_COSTUME) +#endif + +//Equip indexes constants. (eg: sd->equip_index[EQI_AMMO] returns the index +//where the arrows are equipped) +enum equip_index { + EQI_ACC_L = 0, + EQI_ACC_R, + EQI_SHOES, + EQI_GARMENT, + EQI_HEAD_LOW, + EQI_HEAD_MID, + EQI_HEAD_TOP, + EQI_ARMOR, + EQI_HAND_L, + EQI_HAND_R, + EQI_COSTUME_TOP, + EQI_COSTUME_MID, + EQI_COSTUME_LOW, + EQI_AMMO, + EQI_MAX +}; + +#define pc_setdead(sd) ( (sd)->state.dead_sit = (sd)->vd.dead_sit = 1 ) +#define pc_setsit(sd) ( (sd)->state.dead_sit = (sd)->vd.dead_sit = 2 ) +#define pc_isdead(sd) ( (sd)->state.dead_sit == 1 ) +#define pc_issit(sd) ( (sd)->vd.dead_sit == 2 ) +#define pc_isidle(sd) ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share ) +#define pc_istrading(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->state.trading ) +#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->chatID || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag ) +#define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) ) +#define pc_setchatid(sd,n) ( (sd)->chatID = n ) +#define pc_ishiding(sd) ( (sd)->sc.option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) ) +#define pc_iscloaking(sd) ( !((sd)->sc.option&OPTION_CHASEWALK) && ((sd)->sc.option&OPTION_CLOAK) ) +#define pc_ischasewalk(sd) ( (sd)->sc.option&OPTION_CHASEWALK ) + +#ifdef NEW_CARTS + #define pc_iscarton(sd) ( (sd)->sc.data[SC_PUSH_CART] ) +#else + #define pc_iscarton(sd) ( (sd)->sc.option&OPTION_CART ) +#endif + +#define pc_isfalcon(sd) ( (sd)->sc.option&OPTION_FALCON ) +#define pc_isriding(sd) ( (sd)->sc.option&OPTION_RIDING ) +#define pc_isinvisible(sd) ( (sd)->sc.option&OPTION_INVISIBLE ) +#define pc_is50overweight(sd) ( (sd)->weight*100 >= (sd)->max_weight*battle_config.natural_heal_weight_rate ) +#define pc_is90overweight(sd) ( (sd)->weight*10 >= (sd)->max_weight*9 ) +#define pc_maxparameter(sd) ( ((((sd)->class_&MAPID_UPPERMASK) == MAPID_KAGEROUOBORO) || (sd)->class_&JOBL_THIRD ? ((sd)->class_&JOBL_BABY ? battle_config.max_baby_third_parameter : battle_config.max_third_parameter) : ((sd)->class_&JOBL_BABY ? battle_config.max_baby_parameter : battle_config.max_parameter)) ) +/** + * Ranger + **/ +#define pc_iswug(sd) ( (sd)->sc.option&OPTION_WUG ) +#define pc_isridingwug(sd) ( (sd)->sc.option&OPTION_WUGRIDER ) +// Mechanic Magic Gear +#define pc_ismadogear(sd) ( (sd)->sc.option&OPTION_MADOGEAR ) +// Rune Knight Dragon +#define pc_isridingdragon(sd) ( (sd)->sc.option&OPTION_DRAGON ) + +#define pc_stop_walking(sd, type) unit_stop_walking(&(sd)->bl, type) +#define pc_stop_attack(sd) unit_stop_attack(&(sd)->bl) + +//Weapon check considering dual wielding. +#define pc_check_weapontype(sd, type) ((type)&((sd)->status.weapon < MAX_WEAPON_TYPE? \ + 1<<(sd)->status.weapon:(1<<(sd)->weapontype1)|(1<<(sd)->weapontype2)|(1<<(sd)->status.weapon))) +//Checks if the given class value corresponds to a player class. [Skotlex] +//JOB_NOVICE isn't checked for class_ is supposed to be unsigned +#define pcdb_checkid_sub(class_) \ +( \ + ( (class_) < JOB_MAX_BASIC ) \ +|| ( (class_) >= JOB_NOVICE_HIGH && (class_) <= JOB_DARK_COLLECTOR ) \ +|| ( (class_) >= JOB_RUNE_KNIGHT && (class_) <= JOB_MECHANIC_T2 ) \ +|| ( (class_) >= JOB_BABY_RUNE && (class_) <= JOB_BABY_MECHANIC2 ) \ +|| ( (class_) >= JOB_SUPER_NOVICE_E && (class_) <= JOB_SUPER_BABY_E ) \ +|| ( (class_) >= JOB_KAGEROU && (class_) < JOB_MAX ) \ +) +#define pcdb_checkid(class_) pcdb_checkid_sub((unsigned int)class_) + +// clientside display macros (values to the left/right of the "+") +#ifdef RENEWAL + #define pc_leftside_atk(sd) ((sd)->battle_status.batk) + #define pc_rightside_atk(sd) ((sd)->battle_status.rhw.atk + (sd)->battle_status.lhw.atk + (sd)->battle_status.rhw.atk2 + (sd)->battle_status.lhw.atk2) + #define pc_leftside_def(sd) ((sd)->battle_status.def2) + #define pc_rightside_def(sd) ((sd)->battle_status.def) + #define pc_leftside_mdef(sd) ((sd)->battle_status.mdef2) + #define pc_rightside_mdef(sd) ((sd)->battle_status.mdef) +#define pc_leftside_matk(sd) (status_base_matk(status_get_status_data(&(sd)->bl), (sd)->status.base_level)) +#define pc_rightside_matk(sd) ((sd)->battle_status.rhw.matk+(sd)->bonus.ematk) +#else + #define pc_leftside_atk(sd) ((sd)->battle_status.batk + (sd)->battle_status.rhw.atk + (sd)->battle_status.lhw.atk) + #define pc_rightside_atk(sd) ((sd)->battle_status.rhw.atk2 + (sd)->battle_status.lhw.atk2) + #define pc_leftside_def(sd) ((sd)->battle_status.def) + #define pc_rightside_def(sd) ((sd)->battle_status.def2) + #define pc_leftside_mdef(sd) ((sd)->battle_status.mdef) + #define pc_rightside_mdef(sd) ( (sd)->battle_status.mdef2 - ((sd)->battle_status.vit>>1) ) +#define pc_leftside_matk(sd) \ + (\ + ((sd)->sc.data[SC_MAGICPOWER] && (sd)->sc.data[SC_MAGICPOWER]->val4) \ + ?((sd)->battle_status.matk_min * 100 + 50) / ((sd)->sc.data[SC_MAGICPOWER]->val3+100) \ + :(sd)->battle_status.matk_min \ + ) +#define pc_rightside_matk(sd) \ + (\ + ((sd)->sc.data[SC_MAGICPOWER] && (sd)->sc.data[SC_MAGICPOWER]->val4) \ + ?((sd)->battle_status.matk_max * 100 + 50) / ((sd)->sc.data[SC_MAGICPOWER]->val3+100) \ + :(sd)->battle_status.matk_max \ + ) +#endif + +int pc_class2idx(int class_); +int pc_get_group_level(struct map_session_data *sd); +int pc_get_group_id(struct map_session_data *sd); +int pc_getrefinebonus(int lv,int type); +bool pc_can_give_items(struct map_session_data *sd); + +bool pc_can_use_command(struct map_session_data *sd, const char *command, AtCommandType type); +#define pc_has_permission(sd, permission) ( ((sd)->permissions&permission) != 0 ) +bool pc_should_log_commands(struct map_session_data *sd); + +int pc_setrestartvalue(struct map_session_data *sd,int type); +int pc_makesavestatus(struct map_session_data *); +void pc_respawn(struct map_session_data* sd, clr_type clrtype); +int pc_setnewpc(struct map_session_data*,int,int,int,unsigned int,int,int); +bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_time, int group_id, struct mmo_charstatus *st, bool changing_mapservers); +void pc_authfail(struct map_session_data *); +int pc_reg_received(struct map_session_data *sd); + +int pc_isequip(struct map_session_data *sd,int n); +int pc_equippoint(struct map_session_data *sd,int n); +int pc_setinventorydata(struct map_session_data *sd); + +int pc_checkskill(struct map_session_data *sd,uint16 skill_id); +int pc_checkallowskill(struct map_session_data *sd); +int pc_checkequip(struct map_session_data *sd,int pos); + +int pc_calc_skilltree(struct map_session_data *sd); +int pc_calc_skilltree_normalize_job(struct map_session_data *sd); +int pc_clean_skilltree(struct map_session_data *sd); + +#define pc_checkoverhp(sd) ((sd)->battle_status.hp == (sd)->battle_status.max_hp) +#define pc_checkoversp(sd) ((sd)->battle_status.sp == (sd)->battle_status.max_sp) + +int pc_setpos(struct map_session_data* sd, unsigned short mapindex, int x, int y, clr_type clrtype); +int pc_setsavepoint(struct map_session_data*,short,int,int); +int pc_randomwarp(struct map_session_data *sd,clr_type type); +int pc_memo(struct map_session_data* sd, int pos); + +int pc_checkadditem(struct map_session_data*,int,int); +int pc_inventoryblank(struct map_session_data*); +int pc_search_inventory(struct map_session_data *sd,int item_id); +int pc_payzeny(struct map_session_data*,int, enum e_log_pick_type type, struct map_session_data*); +int pc_additem(struct map_session_data*,struct item*,int,e_log_pick_type); +int pc_getzeny(struct map_session_data*,int, enum e_log_pick_type, struct map_session_data*); +int pc_delitem(struct map_session_data*,int,int,int,short,e_log_pick_type); + +// Special Shop System +int pc_paycash(struct map_session_data *sd, int price, int points); +int pc_getcash(struct map_session_data *sd, int cash, int points); + +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount,e_log_pick_type log_type); +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type,e_log_pick_type log_type); +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount); +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount); +int pc_cartitem_amount(struct map_session_data *sd,int idx,int amount); + +int pc_takeitem(struct map_session_data*,struct flooritem_data*); +int pc_dropitem(struct map_session_data*,int,int); + +bool pc_isequipped(struct map_session_data *sd, int nameid); +bool pc_can_Adopt(struct map_session_data *p1_sd, struct map_session_data *p2_sd, struct map_session_data *b_sd ); +bool pc_adoption(struct map_session_data *p1_sd, struct map_session_data *p2_sd, struct map_session_data *b_sd); + +int pc_updateweightstatus(struct map_session_data *sd); + +int pc_addautobonus(struct s_autobonus *bonus,char max,const char *script,short rate,unsigned int dur,short atk_type,const char *o_script,unsigned short pos,bool onskill); +int pc_exeautobonus(struct map_session_data* sd,struct s_autobonus *bonus); +int pc_endautobonus(int tid, unsigned int tick, int id, intptr_t data); +int pc_delautobonus(struct map_session_data* sd,struct s_autobonus *bonus,char max,bool restore); + +int pc_bonus(struct map_session_data*,int,int); +int pc_bonus2(struct map_session_data *sd,int,int,int); +int pc_bonus3(struct map_session_data *sd,int,int,int,int); +int pc_bonus4(struct map_session_data *sd,int,int,int,int,int); +int pc_bonus5(struct map_session_data *sd,int,int,int,int,int,int); +int pc_skill(struct map_session_data* sd, int id, int level, int flag); + +int pc_insert_card(struct map_session_data *sd,int idx_card,int idx_equip); + +int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skill_lv); +int pc_steal_coin(struct map_session_data *sd,struct block_list *bl); + +int pc_modifybuyvalue(struct map_session_data*,int); +int pc_modifysellvalue(struct map_session_data*,int); + +int pc_follow(struct map_session_data*, int); // [MouseJstr] +int pc_stop_following(struct map_session_data*); + +unsigned int pc_maxbaselv(struct map_session_data *sd); +unsigned int pc_maxjoblv(struct map_session_data *sd); +int pc_checkbaselevelup(struct map_session_data *sd); +int pc_checkjoblevelup(struct map_session_data *sd); +int pc_gainexp(struct map_session_data*,struct block_list*,unsigned int,unsigned int, bool); +unsigned int pc_nextbaseexp(struct map_session_data *); +unsigned int pc_thisbaseexp(struct map_session_data *); +unsigned int pc_nextjobexp(struct map_session_data *); +unsigned int pc_thisjobexp(struct map_session_data *); +int pc_gets_status_point(int); +int pc_need_status_point(struct map_session_data *,int,int); +int pc_statusup(struct map_session_data*,int); +int pc_statusup2(struct map_session_data*,int,int); +int pc_skillup(struct map_session_data*,uint16 skill_id); +int pc_allskillup(struct map_session_data*); +int pc_resetlvl(struct map_session_data*,int type); +int pc_resetstate(struct map_session_data*); +int pc_resetskill(struct map_session_data*, int); +int pc_resetfeel(struct map_session_data*); +int pc_resethate(struct map_session_data*); +int pc_equipitem(struct map_session_data*,int,int); +int pc_unequipitem(struct map_session_data*,int,int); +int pc_checkitem(struct map_session_data*); +int pc_useitem(struct map_session_data*,int); + +int pc_skillatk_bonus(struct map_session_data *sd, uint16 skill_id); +int pc_skillheal_bonus(struct map_session_data *sd, uint16 skill_id); +int pc_skillheal2_bonus(struct map_session_data *sd, uint16 skill_id); + +void pc_damage(struct map_session_data *sd,struct block_list *src,unsigned int hp, unsigned int sp); +int pc_dead(struct map_session_data *sd,struct block_list *src); +void pc_revive(struct map_session_data *sd,unsigned int hp, unsigned int sp); +void pc_heal(struct map_session_data *sd,unsigned int hp,unsigned int sp, int type); +int pc_itemheal(struct map_session_data *sd,int itemid, int hp,int sp); +int pc_percentheal(struct map_session_data *sd,int,int); +int pc_jobchange(struct map_session_data *,int, int); +int pc_setoption(struct map_session_data *,int); +int pc_setcart(struct map_session_data* sd, int type); +int pc_setfalcon(struct map_session_data* sd, int flag); +int pc_setriding(struct map_session_data* sd, int flag); +int pc_setmadogear(struct map_session_data* sd, int flag); +int pc_changelook(struct map_session_data *,int,int); +int pc_equiplookall(struct map_session_data *sd); + +int pc_readparam(struct map_session_data*,int); +int pc_setparam(struct map_session_data*,int,int); +int pc_readreg(struct map_session_data*,int); +int pc_setreg(struct map_session_data*,int,int); +char *pc_readregstr(struct map_session_data *sd,int reg); +int pc_setregstr(struct map_session_data *sd,int reg,const char *str); + +#define pc_readglobalreg(sd,reg) pc_readregistry(sd,reg,3) +#define pc_setglobalreg(sd,reg,val) pc_setregistry(sd,reg,val,3) +#define pc_readglobalreg_str(sd,reg) pc_readregistry_str(sd,reg,3) +#define pc_setglobalreg_str(sd,reg,val) pc_setregistry_str(sd,reg,val,3) +#define pc_readaccountreg(sd,reg) pc_readregistry(sd,reg,2) +#define pc_setaccountreg(sd,reg,val) pc_setregistry(sd,reg,val,2) +#define pc_readaccountregstr(sd,reg) pc_readregistry_str(sd,reg,2) +#define pc_setaccountregstr(sd,reg,val) pc_setregistry_str(sd,reg,val,2) +#define pc_readaccountreg2(sd,reg) pc_readregistry(sd,reg,1) +#define pc_setaccountreg2(sd,reg,val) pc_setregistry(sd,reg,val,1) +#define pc_readaccountreg2str(sd,reg) pc_readregistry_str(sd,reg,1) +#define pc_setaccountreg2str(sd,reg,val) pc_setregistry_str(sd,reg,val,1) +int pc_readregistry(struct map_session_data*,const char*,int); +int pc_setregistry(struct map_session_data*,const char*,int,int); +char *pc_readregistry_str(struct map_session_data*,const char*,int); +int pc_setregistry_str(struct map_session_data*,const char*,const char*,int); + +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name); +int pc_deleventtimer(struct map_session_data *sd,const char *name); +int pc_cleareventtimer(struct map_session_data *sd); +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick); + +int pc_calc_pvprank(struct map_session_data *sd); +int pc_calc_pvprank_timer(int tid, unsigned int tick, int id, intptr_t data); + +int pc_ismarried(struct map_session_data *sd); +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd); +int pc_divorce(struct map_session_data *sd); +struct map_session_data *pc_get_partner(struct map_session_data *sd); +struct map_session_data *pc_get_father(struct map_session_data *sd); +struct map_session_data *pc_get_mother(struct map_session_data *sd); +struct map_session_data *pc_get_child(struct map_session_data *sd); + +void pc_bleeding (struct map_session_data *sd, unsigned int diff_tick); +void pc_regen (struct map_session_data *sd, unsigned int diff_tick); + +void pc_setstand(struct map_session_data *sd); +int pc_candrop(struct map_session_data *sd,struct item *item); + +int pc_jobid2mapid(unsigned short b_class); // Skotlex +int pc_mapid2jobid(unsigned short class_, int sex); // Skotlex + +const char * job_name(int class_); + +struct skill_tree_entry { + short id; + unsigned char max; + unsigned char joblv; + struct { + short id; + unsigned char lv; + } need[MAX_PC_SKILL_REQUIRE]; +}; // Celest +extern struct skill_tree_entry skill_tree[CLASS_COUNT][MAX_SKILL_TREE]; + +struct sg_data { + short anger_id; + short bless_id; + short comfort_id; + char feel_var[NAME_LENGTH]; + char hate_var[NAME_LENGTH]; + int (*day_func)(void); +}; +extern const struct sg_data sg_info[MAX_PC_FEELHATE]; + +void pc_setinvincibletimer(struct map_session_data* sd, int val); +void pc_delinvincibletimer(struct map_session_data* sd); + +int pc_addspiritball(struct map_session_data *sd,int,int); +int pc_delspiritball(struct map_session_data *sd,int,int); +void pc_addfame(struct map_session_data *sd,int count); +unsigned char pc_famerank(int char_id, int job); +int pc_set_hate_mob(struct map_session_data *sd, int pos, struct block_list *bl); + +extern struct fame_list smith_fame_list[MAX_FAME_LIST]; +extern struct fame_list chemist_fame_list[MAX_FAME_LIST]; +extern struct fame_list taekwon_fame_list[MAX_FAME_LIST]; + +int pc_readdb(void); +int do_init_pc(void); +void do_final_pc(void); + +enum {ADDITEM_EXIST,ADDITEM_NEW,ADDITEM_OVERAMOUNT}; + +// timer for night.day +extern int day_timer_tid; +extern int night_timer_tid; +int map_day_timer(int tid, unsigned int tick, int id, intptr_t data); // by [yor] +int map_night_timer(int tid, unsigned int tick, int id, intptr_t data); // by [yor] + +// Rental System +void pc_inventory_rentals(struct map_session_data *sd); +int pc_inventory_rental_clear(struct map_session_data *sd); +void pc_inventory_rental_add(struct map_session_data *sd, int seconds); + +int pc_read_motd(void); // [Valaris] +int pc_disguise(struct map_session_data *sd, int class_); +bool pc_isautolooting(struct map_session_data *sd, int nameid); + +void pc_overheat(struct map_session_data *sd, int val); + +int pc_banding(struct map_session_data *sd, uint16 skill_lv); + +void pc_itemcd_do(struct map_session_data *sd, bool load); + +int pc_load_combo(struct map_session_data *sd); + +int pc_add_talisman(struct map_session_data *sd,int interval,int max,int type); +int pc_del_talisman(struct map_session_data *sd,int count,int type); + +void pc_baselevelchanged(struct map_session_data *sd); + +#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) +int pc_level_penalty_mod(struct map_session_data *sd, struct mob_data * md, int type); +#endif +#endif /* _PC_H_ */ diff --git a/src/map/pc_groups.c b/src/map/pc_groups.c new file mode 100644 index 000000000..3111e2788 --- /dev/null +++ b/src/map/pc_groups.c @@ -0,0 +1,468 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/conf.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" // strcmp +#include "../common/socket.h" + +#include "atcommand.h" // AtCommandType +#include "pc_groups.h" +#include "pc.h" // e_pc_permission + + +typedef struct GroupSettings GroupSettings; + +// Cached config settings/pointers for quick lookup +struct GroupSettings { + unsigned int id; // groups.[].id + int level; // groups.[].level + const char *name; // groups.[].name + config_setting_t *commands; // groups.[].commands + unsigned int e_permissions; // packed groups.[].permissions + bool log_commands; // groups.[].log_commands + /// Following are used only during config reading + config_setting_t *permissions; // groups.[].permissions + config_setting_t *inherit; // groups.[].inherit + bool inheritance_done; // have all inheritance rules been evaluated? + config_setting_t *root; // groups.[] + int group_pos;/* pos on load */ +}; + +int pc_group_max; /* known number of groups */ + +static config_t pc_group_config; +static DBMap* pc_group_db; // id -> GroupSettings +static DBMap* pc_groupname_db; // name -> GroupSettings + +/** + * @retval NULL if not found + * @private + */ +static inline GroupSettings* id2group(int group_id) +{ + return (GroupSettings*)idb_get(pc_group_db, group_id); +} + +/** + * @retval NULL if not found + * @private + */ +static inline GroupSettings* name2group(const char* group_name) +{ + return (GroupSettings*)strdb_get(pc_groupname_db, group_name); +} + +/** + * Loads group configuration from config file into memory. + * @private + */ +static void read_config(void) +{ + config_setting_t *groups = NULL; + const char *config_filename = "conf/groups.conf"; // FIXME hardcoded name + int group_count = 0; + + if (conf_read_file(&pc_group_config, config_filename)) + return; + + groups = config_lookup(&pc_group_config, "groups"); + + if (groups != NULL) { + GroupSettings *group_settings = NULL; + DBIterator *iter = NULL; + int i, loop = 0; + + group_count = config_setting_length(groups); + for (i = 0; i < group_count; ++i) { + int id = 0, level = 0; + const char *groupname = NULL; + int log_commands = 0; + config_setting_t *group = config_setting_get_elem(groups, i); + + if (!config_setting_lookup_int(group, "id", &id)) { + ShowConfigWarning(group, "pc_groups:read_config: \"groups\" list member #%d has undefined id, removing...", i); + config_setting_remove_elem(groups, i); + --i; + --group_count; + continue; + } + + if (id2group(id) != NULL) { + ShowConfigWarning(group, "pc_groups:read_config: duplicate group id %d, removing...", i); + config_setting_remove_elem(groups, i); + --i; + --group_count; + continue; + } + + config_setting_lookup_int(group, "level", &level); + config_setting_lookup_bool(group, "log_commands", &log_commands); + + if (!config_setting_lookup_string(group, "name", &groupname)) { + char temp[20]; + config_setting_t *name = NULL; + snprintf(temp, sizeof(temp), "Group %d", id); + if ((name = config_setting_add(group, "name", CONFIG_TYPE_STRING)) == NULL || + !config_setting_set_string(name, temp)) { + ShowError("pc_groups:read_config: failed to set missing group name, id=%d, skipping... (%s:%d)\n", + id, config_setting_source_file(group), config_setting_source_line(group)); + continue; + } + config_setting_lookup_string(group, "name", &groupname); // Retrieve the pointer + } + + if (name2group(groupname) != NULL) { + ShowConfigWarning(group, "pc_groups:read_config: duplicate group name %s, removing...", groupname); + config_setting_remove_elem(groups, i); + --i; + --group_count; + continue; + } + + CREATE(group_settings, GroupSettings, 1); + group_settings->id = id; + group_settings->level = level; + group_settings->name = groupname; + group_settings->log_commands = (bool)log_commands; + group_settings->inherit = config_setting_get_member(group, "inherit"); + group_settings->commands = config_setting_get_member(group, "commands"); + group_settings->permissions = config_setting_get_member(group, "permissions"); + group_settings->inheritance_done = false; + group_settings->root = group; + group_settings->group_pos = i; + + strdb_put(pc_groupname_db, groupname, group_settings); + idb_put(pc_group_db, id, group_settings); + + } + group_count = config_setting_length(groups); // Save number of groups + + // Check if all commands and permissions exist + iter = db_iterator(pc_group_db); + for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { + config_setting_t *commands = group_settings->commands, *permissions = group_settings->permissions; + int count = 0, i; + + // Make sure there is "commands" group + if (commands == NULL) + commands = group_settings->commands = config_setting_add(group_settings->root, "commands", CONFIG_TYPE_GROUP); + count = config_setting_length(commands); + + for (i = 0; i < count; ++i) { + config_setting_t *command = config_setting_get_elem(commands, i); + const char *name = config_setting_name(command); + if (!atcommand_exists(name)) { + ShowConfigWarning(command, "pc_groups:read_config: non-existent command name '%s', removing...", name); + config_setting_remove(commands, name); + --i; + --count; + } + } + + // Make sure there is "permissions" group + if (permissions == NULL) + permissions = group_settings->permissions = config_setting_add(group_settings->root, "permissions", CONFIG_TYPE_GROUP); + count = config_setting_length(permissions); + + for(i = 0; i < count; ++i) { + config_setting_t *permission = config_setting_get_elem(permissions, i); + const char *name = config_setting_name(permission); + int j; + + ARR_FIND(0, ARRAYLENGTH(pc_g_permission_name), j, strcmp(pc_g_permission_name[j].name, name) == 0); + if (j == ARRAYLENGTH(pc_g_permission_name)) { + ShowConfigWarning(permission, "pc_groups:read_config: non-existent permission name '%s', removing...", name); + config_setting_remove(permissions, name); + --i; + --count; + } + } + } + dbi_destroy(iter); + + // Apply inheritance + i = 0; // counter for processed groups + while (i < group_count) { + iter = db_iterator(pc_group_db); + for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { + config_setting_t *inherit = NULL, + *commands = group_settings->commands, + *permissions = group_settings->permissions; + int j, inherit_count = 0, done = 0; + + if (group_settings->inheritance_done) // group already processed + continue; + + if ((inherit = group_settings->inherit) == NULL || + (inherit_count = config_setting_length(inherit)) <= 0) { // this group does not inherit from others + ++i; + group_settings->inheritance_done = true; + continue; + } + + for (j = 0; j < inherit_count; ++j) { + GroupSettings *inherited_group = NULL; + const char *groupname = config_setting_get_string_elem(inherit, j); + + if (groupname == NULL) { + ShowConfigWarning(inherit, "pc_groups:read_config: \"inherit\" array member #%d is not a name, removing...", j); + config_setting_remove_elem(inherit,j); + continue; + } + if ((inherited_group = name2group(groupname)) == NULL) { + ShowConfigWarning(inherit, "pc_groups:read_config: non-existent group name \"%s\", removing...", groupname); + config_setting_remove_elem(inherit,j); + continue; + } + if (!inherited_group->inheritance_done) + continue; // we need to do that group first + + // Copy settings (commands/permissions) that are not defined yet + if (inherited_group->commands != NULL) { + int i = 0, commands_count = config_setting_length(inherited_group->commands); + for (i = 0; i < commands_count; ++i) + config_setting_copy(commands, config_setting_get_elem(inherited_group->commands, i)); + } + + if (inherited_group->permissions != NULL) { + int i = 0, permissions_count = config_setting_length(inherited_group->permissions); + for (i = 0; i < permissions_count; ++i) + config_setting_copy(permissions, config_setting_get_elem(inherited_group->permissions, i)); + } + + ++done; // copied commands and permissions from one of inherited groups + } + + if (done == inherit_count) { // copied commands from all of inherited groups + ++i; + group_settings->inheritance_done = true; // we're done with this group + } + } + dbi_destroy(iter); + + if (++loop > group_count) { + ShowWarning("pc_groups:read_config: Could not process inheritance rules, check your config '%s' for cycles...\n", + config_filename); + break; + } + } // while(i < group_count) + + // Pack permissions into GroupSettings.e_permissions for faster checking + iter = db_iterator(pc_group_db); + for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { + config_setting_t *permissions = group_settings->permissions; + int i, count = config_setting_length(permissions); + + for (i = 0; i < count; ++i) { + config_setting_t *perm = config_setting_get_elem(permissions, i); + const char *name = config_setting_name(perm); + int val = config_setting_get_bool(perm); + int j; + + if (val == 0) // does not have this permission + continue; + ARR_FIND(0, ARRAYLENGTH(pc_g_permission_name), j, strcmp(pc_g_permission_name[j].name, name) == 0); + group_settings->e_permissions |= pc_g_permission_name[j].permission; + } + } + dbi_destroy(iter); + } + + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' groups in '"CL_WHITE"%s"CL_RESET"'.\n", group_count, config_filename); + + + if( ( pc_group_max = group_count ) ) { + DBIterator *iter = db_iterator(pc_group_db); + GroupSettings *group_settings = NULL; + int* group_ids = aMalloc( pc_group_max * sizeof(int) ); + int i = 0; + for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { + group_ids[i++] = group_settings->id; + } + + atcommand_db_load_groups(group_ids); + + aFree(group_ids); + + dbi_destroy(iter); + } +} + +/** + * Removes group configuration from memory. + * @private + */ +static void destroy_config(void) +{ + config_destroy(&pc_group_config); +} + +/** + * In group configuration file, setting for each command is either + * <commandname> : <bool> (only atcommand), or + * <commandname> : [ <bool>, <bool> ] ([ atcommand, charcommand ]) + * Maps AtCommandType enums to indexes of <commandname> value array, + * COMMAND_ATCOMMAND (1) being index 0, COMMAND_CHARCOMMAND (2) being index 1. + * @private + */ +static inline int AtCommandType2idx(AtCommandType type) { return (type-1); } + +/** + * Checks if player group can use @/#command + * @param group_id ID of the group + * @param command Command name without @/# and params + * @param type enum AtCommanndType { COMMAND_ATCOMMAND = 1, COMMAND_CHARCOMMAND = 2 } + */ +bool pc_group_can_use_command(int group_id, const char *command, AtCommandType type) +{ + int result = 0; + config_setting_t *commands = NULL; + GroupSettings *group = NULL; + + if (pc_group_has_permission(group_id, PC_PERM_USE_ALL_COMMANDS)) + return true; + + if ((group = id2group(group_id)) == NULL) + return false; + + commands = group->commands; + if (commands != NULL) { + config_setting_t *cmd = NULL; + + // <commandname> : <bool> (only atcommand) + if (type == COMMAND_ATCOMMAND && config_setting_lookup_bool(commands, command, &result)) + return (bool)result; + + // <commandname> : [ <bool>, <bool> ] ([ atcommand, charcommand ]) + if ((cmd = config_setting_get_member(commands, command)) != NULL && + config_setting_is_aggregate(cmd) && config_setting_length(cmd) == 2) + return (bool)config_setting_get_bool_elem(cmd, AtCommandType2idx(type)); + } + return false; +} +void pc_group_pc_load(struct map_session_data * sd) { + GroupSettings *group = NULL; + if ((group = id2group(sd->group_id)) == NULL) { + ShowWarning("pc_group_pc_load: %s (AID:%d) logged in with unknown group id (%d)! kicking...\n", + sd->status.name, + sd->status.account_id, + sd->group_id); + set_eof(sd->fd); + return; + } + sd->permissions = group->e_permissions; + sd->group_pos = group->group_pos; + sd->group_level = group->level; +} +/** + * Checks if player group has a permission + * @param group_id ID of the group + * @param permission permission to check + */ +bool pc_group_has_permission(int group_id, int permission) +{ + GroupSettings *group = NULL; + if ((group = id2group(group_id)) == NULL) + return false; + return ((group->e_permissions&permission) != 0); +} + +/** + * Checks commands used by player group should be logged + * @param group_id ID of the group + */ +bool pc_group_should_log_commands(int group_id) +{ + GroupSettings *group = NULL; + if ((group = id2group(group_id)) == NULL) + return false; + return group->log_commands; +} + +/** + * Checks if player group with given ID exists. + * @param group_id group id + * @returns true if group exists, false otherwise + */ +bool pc_group_exists(int group_id) +{ + return idb_exists(pc_group_db, group_id); +} + +/** + * Group ID -> group name lookup. Used only in @who atcommands. + * @param group_id group id + * @return group name + * @public + */ +const char* pc_group_id2name(int group_id) +{ + GroupSettings *group = id2group(group_id); + if (group == NULL) + return "Non-existent group!"; + return group->name; +} + +/** + * Group ID -> group level lookup. A way to provide backward compatibility with GM level system. + * @param group id + * @return group level + * @public + */ +int pc_group_id2level(int group_id) +{ + GroupSettings *group = id2group(group_id); + if (group == NULL) + return 0; + return group->level; +} + +/** + * Initialize PC Groups: allocate DBMaps and read config. + * @public + */ +void do_init_pc_groups(void) +{ + pc_group_db = idb_alloc(DB_OPT_RELEASE_DATA); + pc_groupname_db = stridb_alloc(DB_OPT_DUP_KEY, 0); + read_config(); +} + +/** + * Finalize PC Groups: free DBMaps and config. + * @public + */ +void do_final_pc_groups(void) +{ + if (pc_group_db != NULL) + db_destroy(pc_group_db); + if (pc_groupname_db != NULL ) + db_destroy(pc_groupname_db); + destroy_config(); +} + +/** + * Reload PC Groups + * Used in @reloadatcommand + * @public + */ +void pc_groups_reload(void) { + struct map_session_data* sd = NULL; + struct s_mapiterator* iter = NULL; + + do_final_pc_groups(); + do_init_pc_groups(); + + /* refresh online users permissions */ + iter = mapit_getallusers(); + for (sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter)) { + pc_group_pc_load(sd); + } + mapit_free(iter); + + +} diff --git a/src/map/pc_groups.h b/src/map/pc_groups.h new file mode 100644 index 000000000..65c48935a --- /dev/null +++ b/src/map/pc_groups.h @@ -0,0 +1,75 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PC_GROUPS_H_ +#define _PC_GROUPS_H_ + +#include "atcommand.h" // AtCommandType + +extern int pc_group_max; + +bool pc_group_exists(int group_id); +bool pc_group_can_use_command(int group_id, const char *command, AtCommandType type); +bool pc_group_has_permission(int group_id, int permission); +bool pc_group_should_log_commands(int group_id); +const char* pc_group_id2name(int group_id); +int pc_group_id2level(int group_id); +void pc_group_pc_load(struct map_session_data *); + +void do_init_pc_groups(void); +void do_final_pc_groups(void); +void pc_groups_reload(void); + +enum e_pc_permission { + PC_PERM_NONE = 0, + PC_PERM_TRADE = 0x000001, + PC_PERM_PARTY = 0x000002, + PC_PERM_ALL_SKILL = 0x000004, + PC_PERM_USE_ALL_EQUIPMENT = 0x000008, + PC_PERM_SKILL_UNCONDITIONAL = 0x000010, + PC_PERM_JOIN_ALL_CHAT = 0x000020, + PC_PERM_NO_CHAT_KICK = 0x000040, + PC_PERM_HIDE_SESSION = 0x000080, + PC_PERM_WHO_DISPLAY_AID = 0x000100, + PC_PERM_RECEIVE_HACK_INFO = 0x000200, + PC_PERM_WARP_ANYWHERE = 0x000400, + PC_PERM_VIEW_HPMETER = 0x000800, + PC_PERM_VIEW_EQUIPMENT = 0x001000, + PC_PERM_USE_CHECK = 0x002000, + PC_PERM_USE_CHANGEMAPTYPE = 0x004000, + PC_PERM_USE_ALL_COMMANDS = 0x008000, + PC_PERM_RECEIVE_REQUESTS = 0x010000, + PC_PERM_SHOW_BOSS = 0x020000, + PC_PERM_DISABLE_PVM = 0x040000, + PC_PERM_DISABLE_PVP = 0x080000, + PC_PERM_DISABLE_CMD_DEAD = 0x100000, +}; + +static const struct { + const char *name; + unsigned int permission; +} pc_g_permission_name[] = { + { "can_trade", PC_PERM_TRADE }, + { "can_party", PC_PERM_PARTY }, + { "all_skill", PC_PERM_ALL_SKILL }, + { "all_equipment", PC_PERM_USE_ALL_EQUIPMENT }, + { "skill_unconditional", PC_PERM_SKILL_UNCONDITIONAL }, + { "join_chat", PC_PERM_JOIN_ALL_CHAT }, + { "kick_chat", PC_PERM_NO_CHAT_KICK }, + { "hide_session", PC_PERM_HIDE_SESSION }, + { "who_display_aid", PC_PERM_WHO_DISPLAY_AID }, + { "hack_info", PC_PERM_RECEIVE_HACK_INFO }, + { "any_warp", PC_PERM_WARP_ANYWHERE }, + { "view_hpmeter", PC_PERM_VIEW_HPMETER }, + { "view_equipment", PC_PERM_VIEW_EQUIPMENT }, + { "use_check", PC_PERM_USE_CHECK }, + { "use_changemaptype", PC_PERM_USE_CHANGEMAPTYPE }, + { "all_commands", PC_PERM_USE_ALL_COMMANDS }, + { "receive_requests", PC_PERM_RECEIVE_REQUESTS }, + { "show_bossmobs", PC_PERM_SHOW_BOSS }, + { "disable_pvm", PC_PERM_DISABLE_PVM }, + { "disable_pvp", PC_PERM_DISABLE_PVP }, + { "disable_commands_when_dead", PC_PERM_DISABLE_CMD_DEAD }, +}; + +#endif // _PC_GROUPS_H_ diff --git a/src/map/pet.c b/src/map/pet.c new file mode 100644 index 000000000..5c7f15151 --- /dev/null +++ b/src/map/pet.c @@ -0,0 +1,1388 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/db.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" + +#include "pc.h" +#include "status.h" +#include "map.h" +#include "path.h" +#include "intif.h" +#include "clif.h" +#include "chrif.h" +#include "pet.h" +#include "itemdb.h" +#include "battle.h" +#include "mob.h" +#include "npc.h" +#include "script.h" +#include "skill.h" +#include "unit.h" +#include "atcommand.h" // msg_txt() +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +#define MIN_PETTHINKTIME 100 + +struct s_pet_db pet_db[MAX_PET_DB]; + +static struct eri *item_drop_ers; //For loot drops delay structures. +static struct eri *item_drop_list_ers; + +int pet_hungry_val(struct pet_data *pd) +{ + nullpo_ret(pd); + + if(pd->pet.hungry > 90) + return 4; + else if(pd->pet.hungry > 75) + return 3; + else if(pd->pet.hungry > 25) + return 2; + else if(pd->pet.hungry > 10) + return 1; + else + return 0; +} + +void pet_set_intimate(struct pet_data *pd, int value) +{ + int intimate; + struct map_session_data *sd; + + nullpo_retv(pd); + intimate = pd->pet.intimate; + sd = pd->msd; + + pd->pet.intimate = value; + if( (intimate >= battle_config.pet_equip_min_friendly && pd->pet.intimate < battle_config.pet_equip_min_friendly) || (intimate < battle_config.pet_equip_min_friendly && pd->pet.intimate >= battle_config.pet_equip_min_friendly) ) + status_calc_pc(sd,0); +} + +int pet_create_egg(struct map_session_data *sd, int item_id) +{ + int pet_id = search_petDB_index(item_id, PET_EGG); + if (pet_id < 0) return 0; //No pet egg here. + sd->catch_target_class = pet_db[pet_id].class_; + intif_create_pet(sd->status.account_id, sd->status.char_id, + (short)pet_db[pet_id].class_, + (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, + (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + return 1; +} + +int pet_unlocktarget(struct pet_data *pd) +{ + nullpo_ret(pd); + + pd->target_id=0; + pet_stop_attack(pd); + pet_stop_walking(pd,1); + return 0; +} + +/*========================================== + * Pet Attack Skill [Skotlex] + *------------------------------------------*/ +int pet_attackskill(struct pet_data *pd, int target_id) +{ + struct block_list *bl; + + if (!battle_config.pet_status_support || !pd->a_skill || + (battle_config.pet_equip_required && !pd->pet.equip)) + return 0; + + if (DIFF_TICK(pd->ud.canact_tick, gettick()) > 0) + return 0; + + if (rnd()%100 < (pd->a_skill->rate +pd->pet.intimate*pd->a_skill->bonusrate/1000)) + { //Skotlex: Use pet's skill + int inf; + + bl=map_id2bl(target_id); + if(bl == NULL || pd->bl.m != bl->m || bl->prev == NULL || status_isdead(bl) || + !check_distance_bl(&pd->bl, bl, pd->db->range3)) + return 0; + + inf = skill_get_inf(pd->a_skill->id); + if (inf & INF_GROUND_SKILL) + unit_skilluse_pos(&pd->bl, bl->x, bl->y, pd->a_skill->id, pd->a_skill->lv); + else //Offensive self skill? Could be stuff like GX. + unit_skilluse_id(&pd->bl,(inf&INF_SELF_SKILL?pd->bl.id:bl->id), pd->a_skill->id, pd->a_skill->lv); + return 1; //Skill invoked. + } + return 0; +} + +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type) +{ + struct pet_data *pd; + int rate; + + pd = sd->pd; + + Assert((pd->msd == 0) || (pd->msd->pd == pd)); + + if(bl == NULL || bl->type != BL_MOB || bl->prev == NULL || + pd->pet.intimate < battle_config.pet_support_min_friendly || + pd->pet.hungry < 1 || + pd->pet.class_ == status_get_class(bl)) + return 0; + + if(pd->bl.m != bl->m || + !check_distance_bl(&pd->bl, bl, pd->db->range2)) + return 0; + + if (!status_check_skilluse(&pd->bl, bl, 0, 0)) + return 0; + + if(!type) { + rate = pd->petDB->attack_rate; + rate = rate * pd->rate_fix/1000; + if(pd->petDB->attack_rate > 0 && rate <= 0) + rate = 1; + } else { + rate = pd->petDB->defence_attack_rate; + rate = rate * pd->rate_fix/1000; + if(pd->petDB->defence_attack_rate > 0 && rate <= 0) + rate = 1; + } + if(rnd()%10000 < rate) + { + if(pd->target_id == 0 || rnd()%10000 < pd->petDB->change_target_rate) + pd->target_id = bl->id; + } + + return 0; +} +/*========================================== + * Pet SC Check [Skotlex] + *------------------------------------------*/ +int pet_sc_check(struct map_session_data *sd, int type) +{ + struct pet_data *pd; + + nullpo_ret(sd); + pd = sd->pd; + + if( pd == NULL + || (battle_config.pet_equip_required && pd->pet.equip == 0) + || pd->recovery == NULL + || pd->recovery->timer != INVALID_TIMER + || pd->recovery->type != type ) + return 1; + + pd->recovery->timer = add_timer(gettick()+pd->recovery->delay*1000,pet_recovery_timer,sd->bl.id,0); + + return 0; +} + +static int pet_hungry(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd; + struct pet_data *pd; + int interval; + + sd=map_id2sd(id); + if(!sd) + return 1; + + if(!sd->status.pet_id || !sd->pd) + return 1; + + pd = sd->pd; + if(pd->pet_hungry_timer != tid){ + ShowError("pet_hungry_timer %d != %d\n",pd->pet_hungry_timer,tid); + return 0; + } + pd->pet_hungry_timer = INVALID_TIMER; + + if (pd->pet.intimate <= 0) + return 1; //You lost the pet already, the rest is irrelevant. + + pd->pet.hungry--; + if( pd->pet.hungry < 0 ) + { + pet_stop_attack(pd); + pd->pet.hungry = 0; + pet_set_intimate(pd, pd->pet.intimate - battle_config.pet_hungry_friendly_decrease); + if( pd->pet.intimate <= 0 ) + { + pd->pet.intimate = 0; + pd->status.speed = pd->db->status.speed; + } + status_calc_pet(pd, 0); + clif_send_petdata(sd,pd,1,pd->pet.intimate); + } + clif_send_petdata(sd,pd,2,pd->pet.hungry); + + if(battle_config.pet_hungry_delay_rate != 100) + interval = (pd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = pd->petDB->hungry_delay; + if(interval <= 0) + interval = 1; + pd->pet_hungry_timer = add_timer(tick+interval,pet_hungry,sd->bl.id,0); + + return 0; +} + +int search_petDB_index(int key,int type) +{ + int i; + + for( i = 0; i < MAX_PET_DB; i++ ) + { + if(pet_db[i].class_ <= 0) + continue; + switch(type) { + case PET_CLASS: if(pet_db[i].class_ == key) return i; break; + case PET_CATCH: if(pet_db[i].itemID == key) return i; break; + case PET_EGG: if(pet_db[i].EggID == key) return i; break; + case PET_EQUIP: if(pet_db[i].AcceID == key) return i; break; + case PET_FOOD: if(pet_db[i].FoodID == key) return i; break; + default: + return -1; + } + } + return -1; +} + +int pet_hungry_timer_delete(struct pet_data *pd) +{ + nullpo_ret(pd); + if(pd->pet_hungry_timer != INVALID_TIMER) { + delete_timer(pd->pet_hungry_timer,pet_hungry); + pd->pet_hungry_timer = INVALID_TIMER; + } + + return 1; +} + +static int pet_performance(struct map_session_data *sd, struct pet_data *pd) +{ + int val; + + if (pd->pet.intimate > 900) + val = (pd->petDB->s_perfor > 0)? 4:3; + else if(pd->pet.intimate > 750) //TODO: this is way too high + val = 2; + else + val = 1; + + pet_stop_walking(pd,2000<<8); + clif_pet_performance(pd, rnd()%val + 1); + pet_lootitem_drop(pd,NULL); + return 1; +} + +static int pet_return_egg(struct map_session_data *sd, struct pet_data *pd) +{ + struct item tmp_item; + int flag; + + pet_lootitem_drop(pd,sd); + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = pd->petDB->EggID; + tmp_item.identify = 1; + tmp_item.card[0] = CARD0_PET; + tmp_item.card[1] = GetWord(pd->pet.pet_id,0); + tmp_item.card[2] = GetWord(pd->pet.pet_id,1); + tmp_item.card[3] = pd->pet.rename_flag; + if((flag = pc_additem(sd,&tmp_item,1,LOG_TYPE_OTHER))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + pd->pet.incuvate = 1; + unit_free(&pd->bl,CLR_OUTSIGHT); + + status_calc_pc(sd,0); + sd->status.pet_id = 0; + + return 1; +} + +int pet_data_init(struct map_session_data *sd, struct s_pet *pet) +{ + struct pet_data *pd; + int i=0,interval=0; + + nullpo_retr(1, sd); + + Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->msd == sd); + + if(sd->status.account_id != pet->account_id || sd->status.char_id != pet->char_id) { + sd->status.pet_id = 0; + return 1; + } + if (sd->status.pet_id != pet->pet_id) { + if (sd->status.pet_id) { + //Wrong pet?? Set incuvate to no and send it back for saving. + pet->incuvate = 1; + intif_save_petdata(sd->status.account_id,pet); + sd->status.pet_id = 0; + return 1; + } + //The pet_id value was lost? odd... restore it. + sd->status.pet_id = pet->pet_id; + } + + i = search_petDB_index(pet->class_,PET_CLASS); + if(i < 0) { + sd->status.pet_id = 0; + return 1; + } + sd->pd = pd = (struct pet_data *)aCalloc(1,sizeof(struct pet_data)); + pd->bl.type = BL_PET; + pd->bl.id = npc_get_new_npc_id(); + + pd->msd = sd; + pd->petDB = &pet_db[i]; + pd->db = mob_db(pet->class_); + memcpy(&pd->pet, pet, sizeof(struct s_pet)); + status_set_viewdata(&pd->bl, pet->class_); + unit_dataset(&pd->bl); + pd->ud.dir = sd->ud.dir; + + pd->bl.m = sd->bl.m; + pd->bl.x = sd->bl.x; + pd->bl.y = sd->bl.y; + unit_calc_pos(&pd->bl, sd->bl.x, sd->bl.y, sd->ud.dir); + pd->bl.x = pd->ud.to_x; + pd->bl.y = pd->ud.to_y; + + map_addiddb(&pd->bl); + status_calc_pet(pd,1); + + pd->last_thinktime = gettick(); + pd->state.skillbonus = 0; + if( battle_config.pet_status_support ) + run_script(pet_db[i].pet_script,0,sd->bl.id,0); + if( pd->petDB && pd->petDB->equip_script ) + status_calc_pc(sd,0); + + if( battle_config.pet_hungry_delay_rate != 100 ) + interval = (pd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = pd->petDB->hungry_delay; + if( interval <= 0 ) + interval = 1; + pd->pet_hungry_timer = add_timer(gettick() + interval, pet_hungry, sd->bl.id, 0); + return 0; +} + +int pet_birth_process(struct map_session_data *sd, struct s_pet *pet) +{ + nullpo_retr(1, sd); + + Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->msd == sd); + + if(sd->status.pet_id && pet->incuvate == 1) { + sd->status.pet_id = 0; + return 1; + } + + pet->incuvate = 0; + pet->account_id = sd->status.account_id; + pet->char_id = sd->status.char_id; + sd->status.pet_id = pet->pet_id; + if(pet_data_init(sd, pet)) { + sd->status.pet_id = 0; + return 1; + } + + intif_save_petdata(sd->status.account_id,pet); + if (save_settings&8) + chrif_save(sd,0); //is it REALLY Needed to save the char for hatching a pet? [Skotlex] + + if(sd->bl.prev != NULL) { + map_addblock(&sd->pd->bl); + clif_spawn(&sd->pd->bl); + clif_send_petdata(sd,sd->pd, 0,0); + clif_send_petdata(sd,sd->pd, 5,battle_config.pet_hair_style); + clif_pet_equip_area(sd->pd); + clif_send_petstatus(sd); + } + Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->msd == sd); + + return 0; +} + +int pet_recv_petdata(int account_id,struct s_pet *p,int flag) +{ + struct map_session_data *sd; + + sd = map_id2sd(account_id); + if(sd == NULL) + return 1; + if(flag == 1) { + sd->status.pet_id = 0; + return 1; + } + if(p->incuvate == 1) { + int i; + //Delete egg from inventory. [Skotlex] + for (i = 0; i < MAX_INVENTORY; i++) { + if(sd->status.inventory[i].card[0] == CARD0_PET && + p->pet_id == MakeDWord(sd->status.inventory[i].card[1], sd->status.inventory[i].card[2])) + break; + } + if(i >= MAX_INVENTORY) { + ShowError("pet_recv_petdata: Hatching pet (%d:%s) aborted, couldn't find egg in inventory for removal!\n",p->pet_id, p->name); + sd->status.pet_id = 0; + return 1; + } + if (!pet_birth_process(sd,p)) //Pet hatched. Delete egg. + pc_delitem(sd,i,1,0,0,LOG_TYPE_OTHER); + } else { + pet_data_init(sd,p); + if(sd->pd && sd->bl.prev != NULL) { + map_addblock(&sd->pd->bl); + clif_spawn(&sd->pd->bl); + clif_send_petdata(sd,sd->pd,0,0); + clif_send_petdata(sd,sd->pd,5,battle_config.pet_hair_style); + clif_pet_equip_area(sd->pd); + clif_send_petstatus(sd); + } + } + + return 0; +} + +int pet_select_egg(struct map_session_data *sd,short egg_index) +{ + nullpo_ret(sd); + + if(egg_index < 0 || egg_index >= MAX_INVENTORY) + return 0; //Forged packet! + + if(sd->status.inventory[egg_index].card[0] == CARD0_PET) + intif_request_petdata(sd->status.account_id, sd->status.char_id, MakeDWord(sd->status.inventory[egg_index].card[1], sd->status.inventory[egg_index].card[2]) ); + else + ShowError("wrong egg item inventory %d\n",egg_index); + + return 0; +} + +int pet_catch_process1(struct map_session_data *sd,int target_class) +{ + nullpo_ret(sd); + + sd->catch_target_class = target_class; + clif_catch_process(sd); + + return 0; +} + +int pet_catch_process2(struct map_session_data* sd, int target_id) +{ + struct mob_data* md; + int i = 0, pet_catch_rate = 0; + + nullpo_retr(1, sd); + + md = (struct mob_data*)map_id2bl(target_id); + if(!md || md->bl.type != BL_MOB || md->bl.prev == NULL) + { // Invalid inputs/state, abort capture. + clif_pet_roulette(sd,0); + sd->catch_target_class = -1; + sd->itemid = sd->itemindex = -1; + return 1; + } + + //FIXME: delete taming item here, if this was an item-invoked capture and the item was flagged as delay-consume [ultramage] + + i = search_petDB_index(md->class_,PET_CLASS); + //catch_target_class == 0 is used for universal lures (except bosses for now). [Skotlex] + if (sd->catch_target_class == 0 && !(md->status.mode&MD_BOSS)) + sd->catch_target_class = md->class_; + if(i < 0 || sd->catch_target_class != md->class_) { + clif_emotion(&md->bl, E_AG); //mob will do /ag if wrong lure is used on them. + clif_pet_roulette(sd,0); + sd->catch_target_class = -1; + return 1; + } + + pet_catch_rate = (pet_db[i].capture + (sd->status.base_level - md->level)*30 + sd->battle_status.luk*20)*(200 - get_percentage(md->status.hp, md->status.max_hp))/100; + + if(pet_catch_rate < 1) pet_catch_rate = 1; + if(battle_config.pet_catch_rate != 100) + pet_catch_rate = (pet_catch_rate*battle_config.pet_catch_rate)/100; + + if(rnd()%10000 < pet_catch_rate) + { + unit_remove_map(&md->bl,CLR_OUTSIGHT); + status_kill(&md->bl); + clif_pet_roulette(sd,1); + intif_create_pet(sd->status.account_id,sd->status.char_id,pet_db[i].class_,mob_db(pet_db[i].class_)->lv, + pet_db[i].EggID,0,pet_db[i].intimate,100,0,1,pet_db[i].jname); + } + else + { + clif_pet_roulette(sd,0); + sd->catch_target_class = -1; + } + + return 0; +} + +int pet_get_egg(int account_id,int pet_id,int flag) +{ //This function is invoked when a new pet has been created, and at no other time! + struct map_session_data *sd; + struct item tmp_item; + int i=0,ret=0; + + if(flag) + return 0; + + sd = map_id2sd(account_id); + if(sd == NULL) + return 0; + + i = search_petDB_index(sd->catch_target_class,PET_CLASS); + sd->catch_target_class = -1; + + if(i < 0) { + intif_delete_petdata(pet_id); + return 0; + } + + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = pet_db[i].EggID; + tmp_item.identify = 1; + tmp_item.card[0] = CARD0_PET; + tmp_item.card[1] = GetWord(pet_id,0); + tmp_item.card[2] = GetWord(pet_id,1); + tmp_item.card[3] = 0; //New pets are not named. + if((ret = pc_additem(sd,&tmp_item,1,LOG_TYPE_PICKDROP_PLAYER))) { + clif_additem(sd,0,0,ret); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + + return 1; +} + +static int pet_unequipitem(struct map_session_data *sd, struct pet_data *pd); +static int pet_food(struct map_session_data *sd, struct pet_data *pd); +static int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap); + +int pet_menu(struct map_session_data *sd,int menunum) +{ + nullpo_ret(sd); + if (sd->pd == NULL) + return 1; + + //You lost the pet already. + if(!sd->status.pet_id || sd->pd->pet.intimate <= 0 || sd->pd->pet.incuvate) + return 1; + + switch(menunum) { + case 0: + clif_send_petstatus(sd); + break; + case 1: + pet_food(sd, sd->pd); + break; + case 2: + pet_performance(sd, sd->pd); + break; + case 3: + pet_return_egg(sd, sd->pd); + break; + case 4: + pet_unequipitem(sd, sd->pd); + break; + } + return 0; +} + +int pet_change_name(struct map_session_data *sd,char *name) +{ + int i; + struct pet_data *pd; + nullpo_retr(1, sd); + + pd = sd->pd; + if((pd == NULL) || (pd->pet.rename_flag == 1 && !battle_config.pet_rename)) + return 1; + + for(i=0;i<NAME_LENGTH && name[i];i++){ + if( !(name[i]&0xe0) || name[i]==0x7f) + return 1; + } + + return intif_rename_pet(sd, name); +} + +int pet_change_name_ack(struct map_session_data *sd, char* name, int flag) +{ + struct pet_data *pd = sd->pd; + if (!pd) return 0; + + normalize_name(name," ");//bugreport:3032 + + if ( !flag || !strlen(name) ) { + clif_displaymessage(sd->fd, msg_txt(280)); // You cannot use this name for your pet. + clif_send_petstatus(sd); //Send status so client knows oet name change got rejected. + return 0; + } + memcpy(pd->pet.name, name, NAME_LENGTH); + clif_charnameack (0,&pd->bl); + pd->pet.rename_flag = 1; + clif_pet_equip_area(pd); + clif_send_petstatus(sd); + return 1; +} + +int pet_equipitem(struct map_session_data *sd,int index) +{ + struct pet_data *pd; + int nameid; + + nullpo_retr(1, sd); + pd = sd->pd; + if (!pd) return 1; + + nameid = sd->status.inventory[index].nameid; + + if(pd->petDB->AcceID == 0 || nameid != pd->petDB->AcceID || pd->pet.equip != 0) { + clif_equipitemack(sd,0,0,0); + return 1; + } + + pc_delitem(sd,index,1,0,0,LOG_TYPE_OTHER); + pd->pet.equip = nameid; + status_set_viewdata(&pd->bl, pd->pet.class_); //Updates view_data. + clif_pet_equip_area(pd); + if (battle_config.pet_equip_required) + { //Skotlex: start support timers if need + unsigned int tick = gettick(); + if (pd->s_skill && pd->s_skill->timer == INVALID_TIMER) + { + if (pd->s_skill->id) + pd->s_skill->timer=add_timer(tick+pd->s_skill->delay*1000, pet_skill_support_timer, sd->bl.id, 0); + else + pd->s_skill->timer=add_timer(tick+pd->s_skill->delay*1000, pet_heal_timer, sd->bl.id, 0); + } + if (pd->bonus && pd->bonus->timer == INVALID_TIMER) + pd->bonus->timer=add_timer(tick+pd->bonus->delay*1000, pet_skill_bonus_timer, sd->bl.id, 0); + } + + return 0; +} + +static int pet_unequipitem(struct map_session_data *sd, struct pet_data *pd) +{ + struct item tmp_item; + int nameid,flag; + + if(pd->pet.equip == 0) + return 1; + + nameid = pd->pet.equip; + pd->pet.equip = 0; + status_set_viewdata(&pd->bl, pd->pet.class_); + clif_pet_equip_area(pd); + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = nameid; + tmp_item.identify = 1; + if((flag = pc_additem(sd,&tmp_item,1,LOG_TYPE_OTHER))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + if( battle_config.pet_equip_required ) + { // Skotlex: halt support timers if needed + if( pd->state.skillbonus ) + { + pd->state.skillbonus = 0; + status_calc_pc(sd,0); + } + if( pd->s_skill && pd->s_skill->timer != INVALID_TIMER ) + { + if( pd->s_skill->id ) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + pd->s_skill->timer = INVALID_TIMER; + } + if( pd->bonus && pd->bonus->timer != INVALID_TIMER ) + { + delete_timer(pd->bonus->timer, pet_skill_bonus_timer); + pd->bonus->timer = INVALID_TIMER; + } + } + + return 0; +} + +static int pet_food(struct map_session_data *sd, struct pet_data *pd) +{ + int i,k; + + k=pd->petDB->FoodID; + i=pc_search_inventory(sd,k); + if(i < 0) { + clif_pet_food(sd,k,0); + return 1; + } + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); + + if( pd->pet.hungry > 90 ) + pet_set_intimate(pd, pd->pet.intimate - pd->petDB->r_full); + else + { + if( battle_config.pet_friendly_rate != 100 ) + k = (pd->petDB->r_hungry * battle_config.pet_friendly_rate)/100; + else + k = pd->petDB->r_hungry; + if( pd->pet.hungry > 75 ) + { + k = k >> 1; + if( k <= 0 ) + k = 1; + } + pet_set_intimate(pd, pd->pet.intimate + k); + } + if( pd->pet.intimate <= 0 ) + { + pd->pet.intimate = 0; + pet_stop_attack(pd); + pd->status.speed = pd->db->status.speed; + } + else if( pd->pet.intimate > 1000 ) + pd->pet.intimate = 1000; + status_calc_pet(pd, 0); + pd->pet.hungry += pd->petDB->fullness; + if( pd->pet.hungry > 100 ) + pd->pet.hungry = 100; + + clif_send_petdata(sd,pd,2,pd->pet.hungry); + clif_send_petdata(sd,pd,1,pd->pet.intimate); + clif_pet_food(sd,pd->petDB->FoodID,1); + + return 0; +} + +static int pet_randomwalk(struct pet_data *pd,unsigned int tick) +{ + const int retrycount=20; + + nullpo_ret(pd); + + Assert((pd->msd == 0) || (pd->msd->pd == pd)); + + if(DIFF_TICK(pd->next_walktime,tick) < 0 && unit_can_move(&pd->bl)) { + int i,x,y,c,d=12-pd->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ + int r=rnd(); + x=pd->bl.x+r%(d*2+1)-d; + y=pd->bl.y+r/(d*2+1)%(d*2+1)-d; + if(map_getcell(pd->bl.m,x,y,CELL_CHKPASS) && unit_walktoxy(&pd->bl,x,y,0)){ + pd->move_fail_count=0; + break; + } + if(i+1>=retrycount){ + pd->move_fail_count++; + if(pd->move_fail_count>1000){ + ShowWarning("PET can't move. hold position %d, class = %d\n",pd->bl.id,pd->pet.class_); + pd->move_fail_count=0; + pd->ud.canmove_tick = tick + 60000; + return 0; + } + } + } + for(i=c=0;i<pd->ud.walkpath.path_len;i++){ + if(pd->ud.walkpath.path[i]&1) + c+=pd->status.speed*14/10; + else + c+=pd->status.speed; + } + pd->next_walktime = tick+rnd()%3000+3000+c; + + return 1; + } + return 0; +} + +static int pet_ai_sub_hard(struct pet_data *pd, struct map_session_data *sd, unsigned int tick) +{ + struct block_list *target = NULL; + + if(pd->bl.prev == NULL || sd == NULL || sd->bl.prev == NULL) + return 0; + + if(DIFF_TICK(tick,pd->last_thinktime) < MIN_PETTHINKTIME) + return 0; + pd->last_thinktime=tick; + + if(pd->ud.attacktimer != INVALID_TIMER || pd->ud.skilltimer != INVALID_TIMER || pd->bl.m != sd->bl.m) + return 0; + + if(pd->ud.walktimer != INVALID_TIMER && pd->ud.walkpath.path_pos <= 2) + return 0; //No thinking when you just started to walk. + + if(pd->pet.intimate <= 0) { + //Pet should just... well, random walk. + pet_randomwalk(pd,tick); + return 0; + } + + if (!check_distance_bl(&sd->bl, &pd->bl, pd->db->range3)) { + //Master too far, chase. + if(pd->target_id) + pet_unlocktarget(pd); + if(pd->ud.walktimer != INVALID_TIMER && pd->ud.target == sd->bl.id) + return 0; //Already walking to him + if (DIFF_TICK(tick, pd->ud.canmove_tick) < 0) + return 0; //Can't move yet. + pd->status.speed = (sd->battle_status.speed>>1); + if(pd->status.speed <= 0) + pd->status.speed = 1; + if (!unit_walktobl(&pd->bl, &sd->bl, 3, 0)) + pet_randomwalk(pd,tick); + return 0; + } + + //Return speed to normal. + if (pd->status.speed != pd->petDB->speed) { + if (pd->ud.walktimer != INVALID_TIMER) + return 0; //Wait until the pet finishes walking back to master. + pd->status.speed = pd->petDB->speed; + pd->ud.state.change_walk_target = pd->ud.state.speed_changed = 1; + } + + if (pd->target_id) { + target= map_id2bl(pd->target_id); + if (!target || pd->bl.m != target->m || status_isdead(target) || + !check_distance_bl(&pd->bl, target, pd->db->range3)) + { + target = NULL; + pet_unlocktarget(pd); + } + } + + if(!target && pd->loot && pd->loot->count < pd->loot->max && DIFF_TICK(tick,pd->ud.canact_tick)>0) { + //Use half the pet's range of sight. + map_foreachinrange(pet_ai_sub_hard_lootsearch,&pd->bl, + pd->db->range2/2, BL_ITEM,pd,&target); + } + + if (!target) { + //Just walk around. + if (check_distance_bl(&sd->bl, &pd->bl, 3)) + return 0; //Already next to master. + + if(pd->ud.walktimer != INVALID_TIMER && check_distance_blxy(&sd->bl, pd->ud.to_x,pd->ud.to_y, 3)) + return 0; //Already walking to him + + unit_calc_pos(&pd->bl, sd->bl.x, sd->bl.y, sd->ud.dir); + if(!unit_walktoxy(&pd->bl,pd->ud.to_x,pd->ud.to_y,0)) + pet_randomwalk(pd,tick); + + return 0; + } + + if(pd->ud.target == target->id && + (pd->ud.attacktimer != INVALID_TIMER || pd->ud.walktimer != INVALID_TIMER)) + return 0; //Target already locked. + + if (target->type != BL_ITEM) + { //enemy targetted + if(!battle_check_range(&pd->bl,target,pd->status.rhw.range)) + { //Chase + if(!unit_walktobl(&pd->bl, target, pd->status.rhw.range, 2)) + pet_unlocktarget(pd); //Unreachable target. + return 0; + } + //Continuous attack. + unit_attack(&pd->bl, pd->target_id, 1); + } else { //Item Targeted, attempt loot + if (!check_distance_bl(&pd->bl, target, 1)) + { //Out of range + if(!unit_walktobl(&pd->bl, target, 1, 1)) //Unreachable target. + pet_unlocktarget(pd); + return 0; + } else{ + struct flooritem_data *fitem = (struct flooritem_data *)target; + if(pd->loot->count < pd->loot->max){ + memcpy(&pd->loot->item[pd->loot->count++],&fitem->item_data,sizeof(pd->loot->item[0])); + pd->loot->weight += itemdb_weight(fitem->item_data.nameid)*fitem->item_data.amount; + map_clearflooritem(target); + } + //Target is unlocked regardless of whether it was picked or not. + pet_unlocktarget(pd); + } + } + return 0; +} + +static int pet_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick = va_arg(ap,unsigned int); + if(sd->status.pet_id && sd->pd) + pet_ai_sub_hard(sd->pd,sd,tick); + + return 0; +} + +static int pet_ai_hard(int tid, unsigned int tick, int id, intptr_t data) +{ + map_foreachpc(pet_ai_sub_foreachclient,tick); + + return 0; +} + +static int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct pet_data* pd; + struct flooritem_data *fitem = (struct flooritem_data *)bl; + struct block_list **target; + int sd_charid =0; + + pd=va_arg(ap,struct pet_data *); + target=va_arg(ap,struct block_list**); + + sd_charid = fitem->first_get_charid; + + if(sd_charid && sd_charid != pd->msd->status.char_id) + return 0; + + if(unit_can_reach_bl(&pd->bl,bl, pd->db->range2, 1, NULL, NULL) && + ((*target) == NULL || //New target closer than previous one. + !check_distance_bl(&pd->bl, *target, distance_bl(&pd->bl, bl)))) + { + (*target) = bl; + pd->target_id = bl->id; + return 1; + } + + return 0; +} + +static int pet_delay_item_drop(int tid, unsigned int tick, int id, intptr_t data) +{ + struct item_drop_list *list; + struct item_drop *ditem, *ditem_prev; + list=(struct item_drop_list *)data; + ditem = list->item; + while (ditem) { + map_addflooritem(&ditem->item_data,ditem->item_data.amount, + list->m,list->x,list->y, + list->first_charid,list->second_charid,list->third_charid,0); + ditem_prev = ditem; + ditem = ditem->next; + ers_free(item_drop_ers, ditem_prev); + } + ers_free(item_drop_list_ers, list); + return 0; +} + +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd) +{ + int i,flag=0; + struct item_drop_list *dlist; + struct item_drop *ditem; + struct item *it; + if(!pd || !pd->loot || !pd->loot->count) + return 0; + dlist = ers_alloc(item_drop_list_ers, struct item_drop_list); + dlist->m = pd->bl.m; + dlist->x = pd->bl.x; + dlist->y = pd->bl.y; + dlist->first_charid = 0; + dlist->second_charid = 0; + dlist->third_charid = 0; + dlist->item = NULL; + + for(i=0;i<pd->loot->count;i++) { + it = &pd->loot->item[i]; + if(sd){ + if((flag = pc_additem(sd,it,it->amount,LOG_TYPE_PICKDROP_PLAYER))){ + clif_additem(sd,0,0,flag); + ditem = ers_alloc(item_drop_ers, struct item_drop); + memcpy(&ditem->item_data, it, sizeof(struct item)); + ditem->next = dlist->item; + dlist->item = ditem; + } + } + else { + ditem = ers_alloc(item_drop_ers, struct item_drop); + memcpy(&ditem->item_data, it, sizeof(struct item)); + ditem->next = dlist->item; + dlist->item = ditem; + } + } + //The smart thing to do is use pd->loot->max (thanks for pointing it out, Shinomori) + memset(pd->loot->item,0,pd->loot->max * sizeof(struct item)); + pd->loot->count = 0; + pd->loot->weight = 0; + pd->ud.canact_tick = gettick()+10000; //prevent picked up during 10*1000ms + + if (dlist->item) + add_timer(gettick()+540,pet_delay_item_drop,0,(intptr_t)dlist); + else + ers_free(item_drop_list_ers, dlist); + return 1; +} + +/*========================================== + * pet bonus giving skills [Valaris] / Rewritten by [Skotlex] + *------------------------------------------*/ +int pet_skill_bonus_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=map_id2sd(id); + struct pet_data *pd; + int bonus; + int timer = 0; + + if(sd == NULL || sd->pd==NULL || sd->pd->bonus == NULL) + return 1; + + pd=sd->pd; + + if(pd->bonus->timer != tid) { + ShowError("pet_skill_bonus_timer %d != %d\n",pd->bonus->timer,tid); + pd->bonus->timer = INVALID_TIMER; + return 0; + } + + // determine the time for the next timer + if (pd->state.skillbonus && pd->bonus->delay > 0) { + bonus = 0; + timer = pd->bonus->delay*1000; // the duration until pet bonuses will be reactivated again + } else if (pd->pet.intimate) { + bonus = 1; + timer = pd->bonus->duration*1000; // the duration for pet bonuses to be in effect + } else { //Lost pet... + pd->bonus->timer = INVALID_TIMER; + return 0; + } + + if (pd->state.skillbonus != bonus) { + pd->state.skillbonus = bonus; + status_calc_pc(sd, 0); + } + // wait for the next timer + pd->bonus->timer=add_timer(tick+timer,pet_skill_bonus_timer,sd->bl.id,0); + return 0; +} + +/*========================================== + * pet recovery skills [Valaris] / Rewritten by [Skotlex] + *------------------------------------------*/ +int pet_recovery_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=map_id2sd(id); + struct pet_data *pd; + + if(sd==NULL || sd->pd == NULL || sd->pd->recovery == NULL) + return 1; + + pd=sd->pd; + + if(pd->recovery->timer != tid) { + ShowError("pet_recovery_timer %d != %d\n",pd->recovery->timer,tid); + return 0; + } + + if(sd->sc.data[pd->recovery->type]) + { //Display a heal animation? + //Detoxify is chosen for now. + clif_skill_nodamage(&pd->bl,&sd->bl,TF_DETOXIFY,1,1); + status_change_end(&sd->bl, pd->recovery->type, INVALID_TIMER); + clif_emotion(&pd->bl, E_OK); + } + + pd->recovery->timer = INVALID_TIMER; + + return 0; +} + +int pet_heal_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=map_id2sd(id); + struct status_data *status; + struct pet_data *pd; + unsigned int rate = 100; + + if(sd==NULL || sd->pd == NULL || sd->pd->s_skill == NULL) + return 1; + + pd=sd->pd; + + if(pd->s_skill->timer != tid) { + ShowError("pet_heal_timer %d != %d\n",pd->s_skill->timer,tid); + return 0; + } + + status = status_get_status_data(&sd->bl); + + if(pc_isdead(sd) || + (rate = get_percentage(status->sp, status->max_sp)) > pd->s_skill->sp || + (rate = get_percentage(status->hp, status->max_hp)) > pd->s_skill->hp || + (rate = (pd->ud.skilltimer != INVALID_TIMER)) //Another skill is in effect + ) { //Wait (how long? 1 sec for every 10% of remaining) + pd->s_skill->timer=add_timer(gettick()+(rate>10?rate:10)*100,pet_heal_timer,sd->bl.id,0); + return 0; + } + pet_stop_attack(pd); + pet_stop_walking(pd,1); + clif_skill_nodamage(&pd->bl,&sd->bl,AL_HEAL,pd->s_skill->lv,1); + status_heal(&sd->bl, pd->s_skill->lv,0, 0); + pd->s_skill->timer=add_timer(tick+pd->s_skill->delay*1000,pet_heal_timer,sd->bl.id,0); + return 0; +} + +/*========================================== + * pet support skills [Skotlex] + *------------------------------------------*/ +int pet_skill_support_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd=map_id2sd(id); + struct pet_data *pd; + struct status_data *status; + short rate = 100; + if(sd==NULL || sd->pd == NULL || sd->pd->s_skill == NULL) + return 1; + + pd=sd->pd; + + if(pd->s_skill->timer != tid) { + ShowError("pet_skill_support_timer %d != %d\n",pd->s_skill->timer,tid); + return 0; + } + + status = status_get_status_data(&sd->bl); + + if (DIFF_TICK(pd->ud.canact_tick, tick) > 0) + { //Wait until the pet can act again. + pd->s_skill->timer=add_timer(pd->ud.canact_tick,pet_skill_support_timer,sd->bl.id,0); + return 0; + } + + if(pc_isdead(sd) || + (rate = get_percentage(status->sp, status->max_sp)) > pd->s_skill->sp || + (rate = get_percentage(status->hp, status->max_hp)) > pd->s_skill->hp || + (rate = (pd->ud.skilltimer != INVALID_TIMER)) //Another skill is in effect + ) { //Wait (how long? 1 sec for every 10% of remaining) + pd->s_skill->timer=add_timer(tick+(rate>10?rate:10)*100,pet_skill_support_timer,sd->bl.id,0); + return 0; + } + + pet_stop_attack(pd); + pet_stop_walking(pd,1); + pd->s_skill->timer=add_timer(tick+pd->s_skill->delay*1000,pet_skill_support_timer,sd->bl.id,0); + if (skill_get_inf(pd->s_skill->id) & INF_GROUND_SKILL) + unit_skilluse_pos(&pd->bl, sd->bl.x, sd->bl.y, pd->s_skill->id, pd->s_skill->lv); + else + unit_skilluse_id(&pd->bl, sd->bl.id, pd->s_skill->id, pd->s_skill->lv); + return 0; +} + +/*========================================== + * Pet read db data + * pet_db.txt + * pet_db2.txt + *------------------------------------------*/ +int read_petdb() +{ + char* filename[] = {"pet_db.txt","pet_db2.txt"}; + FILE *fp; + int nameid,i,j,k; + + // Remove any previous scripts in case reloaddb was invoked. + for( j = 0; j < MAX_PET_DB; j++ ) + { + if( pet_db[j].pet_script ) + { + script_free_code(pet_db[j].pet_script); + pet_db[j].pet_script = NULL; + } + if( pet_db[j].equip_script ) + { + script_free_code(pet_db[j].equip_script); + pet_db[j].pet_script = NULL; + } + } + + // clear database + memset(pet_db,0,sizeof(pet_db)); + + j = 0; // entry counter + for( i = 0; i < ARRAYLENGTH(filename); i++ ) + { + char line[1024]; + int lines, entries; + + sprintf(line, "%s/%s", db_path, filename[i]); + fp=fopen(line,"r"); + if( fp == NULL ) + { + if( i == 0 ) + ShowError("can't read %s\n",line); + continue; + } + + lines = entries = 0; + while( fgets(line, sizeof(line), fp) && j < MAX_PET_DB ) + { + char *str[22], *p; + lines++; + + if(line[0] == '/' && line[1] == '/') + continue; + memset(str, 0, sizeof(str)); + p = line; + while( ISSPACE(*p) ) + ++p; + if( *p == '\0' ) + continue; // empty line + for( k = 0; k < 20; ++k ) + { + str[k] = p; + p = strchr(p,','); + if( p == NULL ) + break; // comma not found + *p = '\0'; + ++p; + } + + if( p == NULL ) + { + ShowError("read_petdb: Insufficient columns in line %d, skipping.\n", lines); + continue; + } + + // Pet Script + if( *p != '{' ) + { + ShowError("read_petdb: Invalid format (Pet Script column) in line %d, skipping.\n", lines); + continue; + } + + str[20] = p; + p = strstr(p+1,"},"); + if( p == NULL ) + { + ShowError("read_petdb: Invalid format (Pet Script column) in line %d, skipping.\n", lines); + continue; + } + p[1] = '\0'; + p += 2; + + // Equip Script + if( *p != '{' ) + { + ShowError("read_petdb: Invalid format (Equip Script column) in line %d, skipping.\n", lines); + continue; + } + str[21] = p; + + if( (nameid = atoi(str[0])) <= 0 ) + continue; + + if( !mobdb_checkid(nameid) ) + { + ShowWarning("pet_db reading: Invalid mob-class %d, pet not read.\n", nameid); + continue; + } + + pet_db[j].class_ = nameid; + safestrncpy(pet_db[j].name,str[1],NAME_LENGTH); + safestrncpy(pet_db[j].jname,str[2],NAME_LENGTH); + pet_db[j].itemID=atoi(str[3]); + pet_db[j].EggID=atoi(str[4]); + pet_db[j].AcceID=atoi(str[5]); + pet_db[j].FoodID=atoi(str[6]); + pet_db[j].fullness=atoi(str[7]); + pet_db[j].hungry_delay=atoi(str[8])*1000; + pet_db[j].r_hungry=atoi(str[9]); + if( pet_db[j].r_hungry <= 0 ) + pet_db[j].r_hungry=1; + pet_db[j].r_full=atoi(str[10]); + pet_db[j].intimate=atoi(str[11]); + pet_db[j].die=atoi(str[12]); + pet_db[j].capture=atoi(str[13]); + pet_db[j].speed=atoi(str[14]); + pet_db[j].s_perfor=(char)atoi(str[15]); + pet_db[j].talk_convert_class=atoi(str[16]); + pet_db[j].attack_rate=atoi(str[17]); + pet_db[j].defence_attack_rate=atoi(str[18]); + pet_db[j].change_target_rate=atoi(str[19]); + pet_db[j].pet_script = NULL; + pet_db[j].equip_script = NULL; + + if( *str[20] ) + pet_db[j].pet_script = parse_script(str[20], filename[i], lines, 0); + if( *str[21] ) + pet_db[j].equip_script = parse_script(str[21], filename[i], lines, 0); + + j++; + entries++; + } + + if( j >= MAX_PET_DB ) + ShowWarning("read_petdb: Reached max number of pets [%d]. Remaining pets were not read.\n ", MAX_PET_DB); + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' pets in '"CL_WHITE"%s"CL_RESET"'.\n", entries, filename[i]); + } + return 0; +} + +/*========================================== + * Initialization process relationship skills + *------------------------------------------*/ +int do_init_pet(void) +{ + read_petdb(); + + item_drop_ers = ers_new(sizeof(struct item_drop),"pet.c::item_drop_ers",ERS_OPT_NONE); + item_drop_list_ers = ers_new(sizeof(struct item_drop_list),"pet.c::item_drop_list_ers",ERS_OPT_NONE); + + add_timer_func_list(pet_hungry,"pet_hungry"); + add_timer_func_list(pet_ai_hard,"pet_ai_hard"); + add_timer_func_list(pet_skill_bonus_timer,"pet_skill_bonus_timer"); // [Valaris] + add_timer_func_list(pet_delay_item_drop,"pet_delay_item_drop"); + add_timer_func_list(pet_skill_support_timer, "pet_skill_support_timer"); // [Skotlex] + add_timer_func_list(pet_recovery_timer,"pet_recovery_timer"); // [Valaris] + add_timer_func_list(pet_heal_timer,"pet_heal_timer"); // [Valaris] + add_timer_interval(gettick()+MIN_PETTHINKTIME,pet_ai_hard,0,0,MIN_PETTHINKTIME); + + return 0; +} + +int do_final_pet(void) +{ + int i; + for( i = 0; i < MAX_PET_DB; i++ ) + { + if( pet_db[i].pet_script ) + { + script_free_code(pet_db[i].pet_script); + pet_db[i].pet_script = NULL; + } + if( pet_db[i].equip_script ) + { + script_free_code(pet_db[i].equip_script); + pet_db[i].equip_script = NULL; + } + } + ers_destroy(item_drop_ers); + ers_destroy(item_drop_list_ers); + return 0; +} diff --git a/src/map/pet.h b/src/map/pet.h new file mode 100644 index 000000000..b46f55229 --- /dev/null +++ b/src/map/pet.h @@ -0,0 +1,136 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PET_H_ +#define _PET_H_ + +#define MAX_PET_DB 300 +#define MAX_PETLOOT_SIZE 30 + +struct s_pet_db { + short class_; + char name[NAME_LENGTH],jname[NAME_LENGTH]; + short itemID; + short EggID; + short AcceID; + short FoodID; + int fullness; + int hungry_delay; + int r_hungry; + int r_full; + int intimate; + int die; + int capture; + int speed; + char s_perfor; + int talk_convert_class; + int attack_rate; + int defence_attack_rate; + int change_target_rate; + struct script_code *equip_script; + struct script_code *pet_script; +}; +extern struct s_pet_db pet_db[MAX_PET_DB]; + +enum { PET_CLASS,PET_CATCH,PET_EGG,PET_EQUIP,PET_FOOD }; + +struct pet_recovery { //Stat recovery + enum sc_type type; //Status Change id + unsigned short delay; //How long before curing (secs). + int timer; +}; + +struct pet_bonus { + unsigned short type; //bStr, bVit? + unsigned short val; //Qty + unsigned short duration; //in secs + unsigned short delay; //Time before RENEWAL_CAST (secs) + int timer; +}; + +struct pet_skill_attack { //Attack Skill + unsigned short id; + unsigned short lv; + unsigned short div_; //0 = Normal skill. >0 = Fixed damage (lv), fixed div_. + unsigned short rate; //Base chance of skill ocurrance (10 = 10% of attacks) + unsigned short bonusrate; //How being 100% loyal affects cast rate (10 = At 1000 intimacy->rate+10% +}; + +struct pet_skill_support { //Support Skill + unsigned short id; + unsigned short lv; + unsigned short hp; //Max HP% for skill to trigger (50 -> 50% for Magnificat) + unsigned short sp; //Max SP% for skill to trigger (100 = no check) + unsigned short delay; //Time (secs) between being able to recast. + int timer; +}; + +struct pet_loot { + struct item *item; + unsigned short count; + unsigned short weight; + unsigned short max; +}; + +struct pet_data { + struct block_list bl; + struct unit_data ud; + struct view_data vd; + struct s_pet pet; + struct status_data status; + struct mob_db *db; + struct s_pet_db *petDB; + int pet_hungry_timer; + int target_id; + struct { + unsigned skillbonus : 1; + } state; + int move_fail_count; + unsigned int next_walktime,last_thinktime; + short rate_fix; //Support rate as modified by intimacy (1000 = 100%) [Skotlex] + + struct pet_recovery* recovery; + struct pet_bonus* bonus; + struct pet_skill_attack* a_skill; + struct pet_skill_support* s_skill; + struct pet_loot* loot; + + struct map_session_data *msd; +}; + + + +int pet_create_egg(struct map_session_data *sd, int item_id); +int pet_hungry_val(struct pet_data *pd); +void pet_set_intimate(struct pet_data *pd, int value); +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type); +int pet_unlocktarget(struct pet_data *pd); +int pet_sc_check(struct map_session_data *sd, int type); //Skotlex +int search_petDB_index(int key,int type); +int pet_hungry_timer_delete(struct pet_data *pd); +int pet_data_init(struct map_session_data *sd, struct s_pet *pet); +int pet_birth_process(struct map_session_data *sd, struct s_pet *pet); +int pet_recv_petdata(int account_id,struct s_pet *p,int flag); +int pet_select_egg(struct map_session_data *sd,short egg_index); +int pet_catch_process1(struct map_session_data *sd,int target_class); +int pet_catch_process2(struct map_session_data *sd,int target_id); +int pet_get_egg(int account_id,int pet_id,int flag); +int pet_menu(struct map_session_data *sd,int menunum); +int pet_change_name(struct map_session_data *sd,char *name); +int pet_change_name_ack(struct map_session_data *sd, char* name, int flag); +int pet_equipitem(struct map_session_data *sd,int index); +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd); +int pet_attackskill(struct pet_data *pd, int target_id); +int pet_skill_support_timer(int tid, unsigned int tick, int id, intptr_t data); // [Skotlex] +int pet_skill_bonus_timer(int tid, unsigned int tick, int id, intptr_t data); // [Valaris] +int pet_recovery_timer(int tid, unsigned int tick, int id, intptr_t data); // [Valaris] +int pet_heal_timer(int tid, unsigned int tick, int id, intptr_t data); // [Valaris] + +#define pet_stop_walking(pd, type) unit_stop_walking(&(pd)->bl, type) +#define pet_stop_attack(pd) unit_stop_attack(&(pd)->bl) + +int read_petdb(void); +int do_init_pet(void); +int do_final_pet(void); + +#endif /* _PET_H_ */ diff --git a/src/map/quest.c b/src/map/quest.c new file mode 100644 index 000000000..c7ca06514 --- /dev/null +++ b/src/map/quest.c @@ -0,0 +1,358 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" + +#include "map.h" +#include "pc.h" +#include "npc.h" +#include "itemdb.h" +#include "script.h" +#include "intif.h" +#include "battle.h" +#include "mob.h" +#include "party.h" +#include "unit.h" +#include "log.h" +#include "clif.h" +#include "quest.h" +#include "intif.h" +#include "chrif.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + + +struct s_quest_db quest_db[MAX_QUEST_DB]; + + +int quest_search_db(int quest_id) +{ + int i; + + ARR_FIND(0, MAX_QUEST_DB,i,quest_id == quest_db[i].id); + if( i == MAX_QUEST_DB ) + return -1; + + return i; +} + +//Send quest info on login +int quest_pc_login(TBL_PC * sd) +{ + if(sd->avail_quests == 0) + return 1; + + clif_quest_send_list(sd); + clif_quest_send_mission(sd); + + return 0; +} + +int quest_add(TBL_PC * sd, int quest_id) +{ + + int i, j; + + if( sd->num_quests >= MAX_QUEST_DB ) + { + ShowError("quest_add: Character %d has got all the quests.(max quests: %d)\n", sd->status.char_id, MAX_QUEST_DB); + return 1; + } + + if( quest_check(sd, quest_id, HAVEQUEST) >= 0 ) + { + ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id); + return -1; + } + + if( (j = quest_search_db(quest_id)) < 0 ) + { + ShowError("quest_add: quest %d not found in DB.\n", quest_id); + return -1; + } + + i = sd->avail_quests; + memmove(&sd->quest_log[i+1], &sd->quest_log[i], sizeof(struct quest)*(sd->num_quests-sd->avail_quests)); + memmove(sd->quest_index+i+1, sd->quest_index+i, sizeof(int)*(sd->num_quests-sd->avail_quests)); + + memset(&sd->quest_log[i], 0, sizeof(struct quest)); + sd->quest_log[i].quest_id = quest_db[j].id; + if( quest_db[j].time ) + sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time); + sd->quest_log[i].state = Q_ACTIVE; + + sd->quest_index[i] = j; + sd->num_quests++; + sd->avail_quests++; + sd->save_quest = true; + + clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]); + + if( save_settings&64 ) + chrif_save(sd,0); + + return 0; +} + +int quest_change(TBL_PC * sd, int qid1, int qid2) +{ + + int i, j; + + if( quest_check(sd, qid2, HAVEQUEST) >= 0 ) + { + ShowError("quest_change: Character %d already has quest %d.\n", sd->status.char_id, qid2); + return -1; + } + + if( quest_check(sd, qid1, HAVEQUEST) < 0 ) + { + ShowError("quest_change: Character %d doesn't have quest %d.\n", sd->status.char_id, qid1); + return -1; + } + + if( (j = quest_search_db(qid2)) < 0 ) + { + ShowError("quest_change: quest %d not found in DB.\n",qid2); + return -1; + } + + ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == qid1); + if(i == sd->avail_quests) + { + ShowError("quest_change: Character %d has completed quests %d.\n", sd->status.char_id, qid1); + return -1; + } + + memset(&sd->quest_log[i], 0, sizeof(struct quest)); + sd->quest_log[i].quest_id = quest_db[j].id; + if( quest_db[j].time ) + sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time); + sd->quest_log[i].state = Q_ACTIVE; + + sd->quest_index[i] = j; + sd->save_quest = true; + + clif_quest_delete(sd, qid1); + clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]); + + if( save_settings&64 ) + chrif_save(sd,0); + + return 0; +} + +int quest_delete(TBL_PC * sd, int quest_id) +{ + int i; + + //Search for quest + ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id); + if(i == sd->num_quests) + { + ShowError("quest_delete: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id); + return -1; + } + + if( sd->quest_log[i].state != Q_COMPLETE ) + sd->avail_quests--; + if( sd->num_quests-- < MAX_QUEST_DB && sd->quest_log[i+1].quest_id ) + { + memmove(&sd->quest_log[i], &sd->quest_log[i+1], sizeof(struct quest)*(sd->num_quests-i)); + memmove(sd->quest_index+i, sd->quest_index+i+1, sizeof(int)*(sd->num_quests-i)); + } + memset(&sd->quest_log[sd->num_quests], 0, sizeof(struct quest)); + sd->quest_index[sd->num_quests] = 0; + sd->save_quest = true; + + clif_quest_delete(sd, quest_id); + + if( save_settings&64 ) + chrif_save(sd,0); + + return 0; +} + +int quest_update_objective_sub(struct block_list *bl, va_list ap) +{ + struct map_session_data * sd; + int mob, party; + + nullpo_ret(bl); + nullpo_ret(sd = (struct map_session_data *)bl); + + party = va_arg(ap,int); + mob = va_arg(ap,int); + + if( !sd->avail_quests ) + return 0; + if( sd->status.party_id != party ) + return 0; + + quest_update_objective(sd, mob); + + return 1; +} + + +void quest_update_objective(TBL_PC * sd, int mob) { + int i,j; + + for( i = 0; i < sd->avail_quests; i++ ) { + if( sd->quest_log[i].state != Q_ACTIVE ) + continue; + + for( j = 0; j < MAX_QUEST_OBJECTIVES; j++ ) + if( quest_db[sd->quest_index[i]].mob[j] == mob && sd->quest_log[i].count[j] < quest_db[sd->quest_index[i]].count[j] ) { + sd->quest_log[i].count[j]++; + sd->save_quest = true; + clif_quest_update_objective(sd,&sd->quest_log[i],sd->quest_index[i]); + } + } +} + +int quest_update_status(TBL_PC * sd, int quest_id, quest_state status) { + int i; + + //Only status of active and inactive quests can be updated. Completed quests can't (for now). [Inkfish] + ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == quest_id); + if(i == sd->avail_quests) { + ShowError("quest_update_status: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id); + return -1; + } + + sd->quest_log[i].state = status; + sd->save_quest = true; + + if( status < Q_COMPLETE ) { + clif_quest_update_status(sd, quest_id, (bool)status); + return 0; + } + + if( i != (--sd->avail_quests) ) { + struct quest tmp_quest; + memcpy(&tmp_quest, &sd->quest_log[i],sizeof(struct quest)); + memcpy(&sd->quest_log[i], &sd->quest_log[sd->avail_quests],sizeof(struct quest)); + memcpy(&sd->quest_log[sd->avail_quests], &tmp_quest,sizeof(struct quest)); + } + + clif_quest_delete(sd, quest_id); + + if( save_settings&64 ) + chrif_save(sd,0); + + return 0; +} + +int quest_check(TBL_PC * sd, int quest_id, quest_check_type type) { + int i; + + ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id); + if( i == sd->num_quests ) + return -1; + + switch( type ) { + case HAVEQUEST: + return sd->quest_log[i].state; + case PLAYTIME: + return (sd->quest_log[i].time < (unsigned int)time(NULL) ? 2 : sd->quest_log[i].state == Q_COMPLETE ? 1 : 0); + case HUNTING: { + if( sd->quest_log[i].state == 0 || sd->quest_log[i].state == 1 ) { + int j; + ARR_FIND(0, MAX_QUEST_OBJECTIVES, j, sd->quest_log[i].count[j] < quest_db[sd->quest_index[i]].count[j]); + if( j == MAX_QUEST_OBJECTIVES ) + return 2; + if( sd->quest_log[i].time < (unsigned int)time(NULL) ) + return 1; + return 0; + } else + return 0; + } + default: + ShowError("quest_check_quest: Unknown parameter %d",type); + break; + } + + return -1; +} + +int quest_read_db(void) { + FILE *fp; + char line[1024]; + int i,j,k = 0; + char *str[20],*p,*np; + + sprintf(line, "%s/quest_db.txt", db_path); + if( (fp=fopen(line,"r"))==NULL ){ + ShowError("can't read %s\n", line); + return -1; + } + + while(fgets(line, sizeof(line), fp)) { + + if (k == MAX_QUEST_DB) { + ShowError("quest_read_db: Too many entries specified in %s/quest_db.txt!\n", db_path); + break; + } + + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + + for( j = 0, p = line; j < 8; j++ ) { + if( ( np = strchr(p,',') ) != NULL ) { + str[j] = p; + *np = 0; + p = np + 1; + } + else if (str[0] == NULL) + continue; + else { + ShowError("quest_read_db: insufficient columns in line %s\n", line); + continue; + } + } + if(str[0]==NULL) + continue; + + memset(&quest_db[k], 0, sizeof(quest_db[0])); + + quest_db[k].id = atoi(str[0]); + quest_db[k].time = atoi(str[1]); + + for( i = 0; i < MAX_QUEST_OBJECTIVES; i++ ) { + quest_db[k].mob[i] = atoi(str[2*i+2]); + quest_db[k].count[i] = atoi(str[2*i+3]); + + if( !quest_db[k].mob[i] || !quest_db[k].count[i] ) + break; + } + + quest_db[k].num_objectives = i; + + k++; + } + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", k, "quest_db.txt"); + return 0; +} + +void do_init_quest(void) { + quest_read_db(); +} + +void do_reload_quest(void) { + memset(&quest_db, 0, sizeof(quest_db)); + quest_read_db(); +} diff --git a/src/map/quest.h b/src/map/quest.h new file mode 100644 index 000000000..7f638a54c --- /dev/null +++ b/src/map/quest.h @@ -0,0 +1,34 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _QUEST_H_ +#define _QUEST_H_ + +struct s_quest_db { + int id; + unsigned int time; + int mob[MAX_QUEST_OBJECTIVES]; + int count[MAX_QUEST_OBJECTIVES]; + int num_objectives; + //char name[NAME_LENGTH]; +}; +extern struct s_quest_db quest_db[MAX_QUEST_DB]; + +typedef enum quest_check_type { HAVEQUEST, PLAYTIME, HUNTING } quest_check_type; + +int quest_pc_login(TBL_PC * sd); + +int quest_add(TBL_PC * sd, int quest_id); +int quest_delete(TBL_PC * sd, int quest_id); +int quest_change(TBL_PC * sd, int qid1, int qid2); +int quest_update_objective_sub(struct block_list *bl, va_list ap); +void quest_update_objective(TBL_PC * sd, int mob); +int quest_update_status(TBL_PC * sd, int quest_id, quest_state status); +int quest_check(TBL_PC * sd, int quest_id, quest_check_type type); + +int quest_search_db(int quest_id); + +void do_init_quest(); +void do_reload_quest(void); + +#endif diff --git a/src/map/script.c b/src/map/script.c new file mode 100644 index 000000000..a918bc853 --- /dev/null +++ b/src/map/script.c @@ -0,0 +1,17779 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +//#define DEBUG_DISP +//#define DEBUG_DISASM +//#define DEBUG_RUN +//#define DEBUG_HASH +//#define DEBUG_DUMP_STACK + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/md5calc.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/socket.h" // usage: getcharip +#include "../common/strlib.h" +#include "../common/timer.h" +#include "../common/utils.h" + +#include "map.h" +#include "path.h" +#include "clif.h" +#include "chrif.h" +#include "itemdb.h" +#include "pc.h" +#include "status.h" +#include "storage.h" +#include "mob.h" +#include "npc.h" +#include "pet.h" +#include "mapreg.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "intif.h" +#include "skill.h" +#include "status.h" +#include "chat.h" +#include "battle.h" +#include "battleground.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "log.h" +#include "unit.h" +#include "pet.h" +#include "mail.h" +#include "script.h" +#include "quest.h" +#include "elemental.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#ifndef WIN32 + #include <sys/time.h> +#endif +#include <time.h> +#include <setjmp.h> +#include <errno.h> + +#ifdef BETA_THREAD_TEST + #include "../common/atomic.h" + #include "../common/spinlock.h" + #include "../common/thread.h" + #include "../common/mutex.h" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +//## TODO possible enhancements: [FlavioJS] +// - 'callfunc' supporting labels in the current npc "::LabelName" +// - 'callfunc' supporting labels in other npcs "NpcName::LabelName" +// - 'function FuncName;' function declarations reverting to global functions +// if local label isn't found +// - join callfunc and callsub's functionality +// - remove dynamic allocation in add_word() +// - remove GETVALUE / SETVALUE +// - clean up the set_reg / set_val / setd_sub mess +// - detect invalid label references at parse-time + +// +// struct script_state* st; +// + +/// Returns the script_data at the target index +#define script_getdata(st,i) ( &((st)->stack->stack_data[(st)->start + (i)]) ) +/// Returns if the stack contains data at the target index +#define script_hasdata(st,i) ( (st)->end > (st)->start + (i) ) +/// Returns the index of the last data in the stack +#define script_lastdata(st) ( (st)->end - (st)->start - 1 ) +/// Pushes an int into the stack +#define script_pushint(st,val) push_val((st)->stack, C_INT, (val)) +/// Pushes a string into the stack (script engine frees it automatically) +#define script_pushstr(st,val) push_str((st)->stack, C_STR, (val)) +/// Pushes a copy of a string into the stack +#define script_pushstrcopy(st,val) push_str((st)->stack, C_STR, aStrdup(val)) +/// Pushes a constant string into the stack (must never change or be freed) +#define script_pushconststr(st,val) push_str((st)->stack, C_CONSTSTR, (val)) +/// Pushes a nil into the stack +#define script_pushnil(st) push_val((st)->stack, C_NOP, 0) +/// Pushes a copy of the data in the target index +#define script_pushcopy(st,i) push_copy((st)->stack, (st)->start + (i)) + +#define script_isstring(st,i) data_isstring(script_getdata(st,i)) +#define script_isint(st,i) data_isint(script_getdata(st,i)) + +#define script_getnum(st,val) conv_num(st, script_getdata(st,val)) +#define script_getstr(st,val) conv_str(st, script_getdata(st,val)) +#define script_getref(st,val) ( script_getdata(st,val)->ref ) + +// Note: "top" functions/defines use indexes relative to the top of the stack +// -1 is the index of the data at the top + +/// Returns the script_data at the target index relative to the top of the stack +#define script_getdatatop(st,i) ( &((st)->stack->stack_data[(st)->stack->sp + (i)]) ) +/// Pushes a copy of the data in the target index relative to the top of the stack +#define script_pushcopytop(st,i) push_copy((st)->stack, (st)->stack->sp + (i)) +/// Removes the range of values [start,end[ relative to the top of the stack +#define script_removetop(st,start,end) ( pop_stack((st), ((st)->stack->sp + (start)), (st)->stack->sp + (end)) ) + +// +// struct script_data* data; +// + +/// Returns if the script data is a string +#define data_isstring(data) ( (data)->type == C_STR || (data)->type == C_CONSTSTR ) +/// Returns if the script data is an int +#define data_isint(data) ( (data)->type == C_INT ) +/// Returns if the script data is a reference +#define data_isreference(data) ( (data)->type == C_NAME ) +/// Returns if the script data is a label +#define data_islabel(data) ( (data)->type == C_POS ) +/// Returns if the script data is an internal script function label +#define data_isfunclabel(data) ( (data)->type == C_USERFUNC_POS ) + +/// Returns if this is a reference to a constant +#define reference_toconstant(data) ( str_data[reference_getid(data)].type == C_INT ) +/// Returns if this a reference to a param +#define reference_toparam(data) ( str_data[reference_getid(data)].type == C_PARAM ) +/// Returns if this a reference to a variable +//##TODO confirm it's C_NAME [FlavioJS] +#define reference_tovariable(data) ( str_data[reference_getid(data)].type == C_NAME ) +/// Returns the unique id of the reference (id and index) +#define reference_getuid(data) ( (data)->u.num ) +/// Returns the id of the reference +#define reference_getid(data) ( (int32)(reference_getuid(data) & 0x00ffffff) ) +/// Returns the array index of the reference +#define reference_getindex(data) ( (int32)(((uint32)(reference_getuid(data) & 0xff000000)) >> 24) ) +/// Returns the name of the reference +#define reference_getname(data) ( str_buf + str_data[reference_getid(data)].str ) +/// Returns the linked list of uid-value pairs of the reference (can be NULL) +#define reference_getref(data) ( (data)->ref ) +/// Returns the value of the constant +#define reference_getconstant(data) ( str_data[reference_getid(data)].val ) +/// Returns the type of param +#define reference_getparamtype(data) ( str_data[reference_getid(data)].val ) + +/// Composes the uid of a reference from the id and the index +#define reference_uid(id,idx) ( (int32)((((uint32)(id)) & 0x00ffffff) | (((uint32)(idx)) << 24)) ) + +#define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'') +#define not_array_variable(prefix) ( (prefix) != '$' && (prefix) != '@' && (prefix) != '.' && (prefix) != '\'' ) +#define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' ) + +#define FETCH(n, t) \ + if( script_hasdata(st,n) ) \ + (t)=script_getnum(st,n); + +/// Maximum amount of elements in script arrays +#define SCRIPT_MAX_ARRAYSIZE 128 + +#define SCRIPT_BLOCK_SIZE 512 +enum { LABEL_NEXTLINE=1,LABEL_START }; + +/// temporary buffer for passing around compiled bytecode +/// @see add_scriptb, set_label, parse_script +static unsigned char* script_buf = NULL; +static int script_pos = 0, script_size = 0; + +static inline int GETVALUE(const unsigned char* buf, int i) +{ + return (int)MakeDWord(MakeWord(buf[i], buf[i+1]), MakeWord(buf[i+2], 0)); +} +static inline void SETVALUE(unsigned char* buf, int i, int n) +{ + buf[i] = GetByte(n, 0); + buf[i+1] = GetByte(n, 1); + buf[i+2] = GetByte(n, 2); +} + +// String buffer structures. +// str_data stores string information +static struct str_data_struct { + enum c_op type; + int str; + int backpatch; + int label; + int (*func)(struct script_state *st); + int val; + int next; +} *str_data = NULL; +static int str_data_size = 0; // size of the data +static int str_num = LABEL_START; // next id to be assigned + +// str_buf holds the strings themselves +static char *str_buf; +static int str_size = 0; // size of the buffer +static int str_pos = 0; // next position to be assigned + + +// Using a prime number for SCRIPT_HASH_SIZE should give better distributions +#define SCRIPT_HASH_SIZE 1021 +int str_hash[SCRIPT_HASH_SIZE]; +// Specifies which string hashing method to use +//#define SCRIPT_HASH_DJB2 +//#define SCRIPT_HASH_SDBM +#define SCRIPT_HASH_ELF + +static DBMap* scriptlabel_db=NULL; // const char* label_name -> int script_pos +static DBMap* userfunc_db=NULL; // const char* func_name -> struct script_code* +static int parse_options=0; +DBMap* script_get_label_db(void){ return scriptlabel_db; } +DBMap* script_get_userfunc_db(void){ return userfunc_db; } + +// important buildin function references for usage in scripts +static int buildin_set_ref = 0; +static int buildin_callsub_ref = 0; +static int buildin_callfunc_ref = 0; +static int buildin_getelementofarray_ref = 0; + +// Caches compiled autoscript item code. +// Note: This is not cleared when reloading itemdb. +static DBMap* autobonus_db=NULL; // char* script -> char* bytecode + +struct Script_Config script_config = { + 1, // warn_func_mismatch_argtypes + 1, 65535, 2048, //warn_func_mismatch_paramnum/check_cmdcount/check_gotocount + 0, INT_MAX, // input_min_value/input_max_value + "OnPCDieEvent", //die_event_name + "OnPCKillEvent", //kill_pc_event_name + "OnNPCKillEvent", //kill_mob_event_name + "OnPCLoginEvent", //login_event_name + "OnPCLogoutEvent", //logout_event_name + "OnPCLoadMapEvent", //loadmap_event_name + "OnPCBaseLvUpEvent", //baselvup_event_name + "OnPCJobLvUpEvent", //joblvup_event_name + "OnTouch_", //ontouch_name (runs on first visible char to enter area, picks another char if the first char leaves) + "OnTouch", //ontouch2_name (run whenever a char walks into the OnTouch area) +}; + +static jmp_buf error_jump; +static char* error_msg; +static const char* error_pos; +static int error_report; // if the error should produce output + +// for advanced scripting support ( nested if, switch, while, for, do-while, function, etc ) +// [Eoe / jA 1080, 1081, 1094, 1164] +enum curly_type { + TYPE_NULL = 0, + TYPE_IF, + TYPE_SWITCH, + TYPE_WHILE, + TYPE_FOR, + TYPE_DO, + TYPE_USERFUNC, + TYPE_ARGLIST // function argument list +}; + +enum e_arglist +{ + ARGLIST_UNDEFINED = 0, + ARGLIST_NO_PAREN = 1, + ARGLIST_PAREN = 2, +}; + +static struct { + struct { + enum curly_type type; + int index; + int count; + int flag; + struct linkdb_node *case_label; + } curly[256]; // Information right parenthesis + int curly_count; // The number of right brackets + int index; // Number of the syntax used in the script +} syntax; + +const char* parse_curly_close(const char* p); +const char* parse_syntax_close(const char* p); +const char* parse_syntax_close_sub(const char* p,int* flag); +const char* parse_syntax(const char* p); +static int parse_syntax_for_flag = 0; + +extern int current_equip_item_index; //for New CARDS Scripts. It contains Inventory Index of the EQUIP_SCRIPT caller item. [Lupus] +int potion_flag=0; //For use on Alchemist improved potions/Potion Pitcher. [Skotlex] +int potion_hp=0, potion_per_hp=0, potion_sp=0, potion_per_sp=0; +int potion_target=0; + + +c_op get_com(unsigned char *script,int *pos); +int get_num(unsigned char *script,int *pos); + +typedef struct script_function { + int (*func)(struct script_state *st); + const char *name; + const char *arg; +} script_function; + +extern script_function buildin_func[]; + +static struct linkdb_node* sleep_db;// int oid -> struct script_state* + +#ifdef BETA_THREAD_TEST +/** + * MySQL Query Slave + **/ +static SPIN_LOCK queryThreadLock; +static rAthread queryThread = NULL; +static ramutex queryThreadMutex = NULL; +static racond queryThreadCond = NULL; +static volatile int32 queryThreadTerminate = 0; + +struct queryThreadEntry { + bool ok; + bool type; /* main db or log db? */ + struct script_state *st; +}; + +/* Ladies and Gentleman the Manager! */ +struct { + struct queryThreadEntry **entry;/* array of structs */ + int count; + int timer;/* used to receive processed entries */ +} queryThreadData; +#endif + +/*========================================== + * (Only those needed) local declaration prototype + *------------------------------------------*/ +const char* parse_subexpr(const char* p,int limit); +int run_func(struct script_state *st); + +enum { + MF_NOMEMO, //0 + MF_NOTELEPORT, + MF_NOSAVE, + MF_NOBRANCH, + MF_NOPENALTY, + MF_NOZENYPENALTY, + MF_PVP, + MF_PVP_NOPARTY, + MF_PVP_NOGUILD, + MF_GVG, + MF_GVG_NOPARTY, //10 + MF_NOTRADE, + MF_NOSKILL, + MF_NOWARP, + MF_PARTYLOCK, + MF_NOICEWALL, + MF_SNOW, + MF_FOG, + MF_SAKURA, + MF_LEAVES, + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //MF_RAIN, //20 + // 21 free + MF_NOGO = 22, + MF_CLOUDS, + MF_CLOUDS2, + MF_FIREWORKS, + MF_GVG_CASTLE, + MF_GVG_DUNGEON, + MF_NIGHTENABLED, + MF_NOBASEEXP, + MF_NOJOBEXP, //30 + MF_NOMOBLOOT, + MF_NOMVPLOOT, + MF_NORETURN, + MF_NOWARPTO, + MF_NIGHTMAREDROP, + MF_RESTRICTED, + MF_NOCOMMAND, + MF_NODROP, + MF_JEXP, + MF_BEXP, //40 + MF_NOVENDING, + MF_LOADEVENT, + MF_NOCHAT, + MF_NOEXPPENALTY, + MF_GUILDLOCK, + MF_TOWN, + MF_AUTOTRADE, + MF_ALLOWKS, + MF_MONSTER_NOTELEPORT, + MF_PVP_NOCALCRANK, //50 + MF_BATTLEGROUND, + MF_RESET +}; + +const char* script_op2name(int op) +{ +#define RETURN_OP_NAME(type) case type: return #type + switch( op ) + { + RETURN_OP_NAME(C_NOP); + RETURN_OP_NAME(C_POS); + RETURN_OP_NAME(C_INT); + RETURN_OP_NAME(C_PARAM); + RETURN_OP_NAME(C_FUNC); + RETURN_OP_NAME(C_STR); + RETURN_OP_NAME(C_CONSTSTR); + RETURN_OP_NAME(C_ARG); + RETURN_OP_NAME(C_NAME); + RETURN_OP_NAME(C_EOL); + RETURN_OP_NAME(C_RETINFO); + RETURN_OP_NAME(C_USERFUNC); + RETURN_OP_NAME(C_USERFUNC_POS); + + // operators + RETURN_OP_NAME(C_OP3); + RETURN_OP_NAME(C_LOR); + RETURN_OP_NAME(C_LAND); + RETURN_OP_NAME(C_LE); + RETURN_OP_NAME(C_LT); + RETURN_OP_NAME(C_GE); + RETURN_OP_NAME(C_GT); + RETURN_OP_NAME(C_EQ); + RETURN_OP_NAME(C_NE); + RETURN_OP_NAME(C_XOR); + RETURN_OP_NAME(C_OR); + RETURN_OP_NAME(C_AND); + RETURN_OP_NAME(C_ADD); + RETURN_OP_NAME(C_SUB); + RETURN_OP_NAME(C_MUL); + RETURN_OP_NAME(C_DIV); + RETURN_OP_NAME(C_MOD); + RETURN_OP_NAME(C_NEG); + RETURN_OP_NAME(C_LNOT); + RETURN_OP_NAME(C_NOT); + RETURN_OP_NAME(C_R_SHIFT); + RETURN_OP_NAME(C_L_SHIFT); + + default: + ShowDebug("script_op2name: unexpected op=%d\n", op); + return "???"; + } +#undef RETURN_OP_NAME +} + +#ifdef DEBUG_DUMP_STACK +static void script_dump_stack(struct script_state* st) +{ + int i; + ShowMessage("\tstart = %d\n", st->start); + ShowMessage("\tend = %d\n", st->end); + ShowMessage("\tdefsp = %d\n", st->stack->defsp); + ShowMessage("\tsp = %d\n", st->stack->sp); + for( i = 0; i < st->stack->sp; ++i ) + { + struct script_data* data = &st->stack->stack_data[i]; + ShowMessage("\t[%d] %s", i, script_op2name(data->type)); + switch( data->type ) + { + case C_INT: + case C_POS: + ShowMessage(" %d\n", data->u.num); + break; + + case C_STR: + case C_CONSTSTR: + ShowMessage(" \"%s\"\n", data->u.str); + break; + + case C_NAME: + ShowMessage(" \"%s\" (id=%d ref=%p subtype=%s)\n", reference_getname(data), data->u.num, data->ref, script_op2name(str_data[data->u.num].type)); + break; + + case C_RETINFO: + { + struct script_retinfo* ri = data->u.ri; + ShowMessage(" %p {var_function=%p, script=%p, pos=%d, nargs=%d, defsp=%d}\n", ri, ri->var_function, ri->script, ri->pos, ri->nargs, ri->defsp); + } + break; + default: + ShowMessage("\n"); + break; + } + } +} +#endif + +/// Reports on the console the src of a script error. +static void script_reportsrc(struct script_state *st) +{ + struct block_list* bl; + + if( st->oid == 0 ) + return; //Can't report source. + + bl = map_id2bl(st->oid); + if( bl == NULL ) + return; + + switch( bl->type ) + { + case BL_NPC: + if( bl->m >= 0 ) + ShowDebug("Source (NPC): %s at %s (%d,%d)\n", ((struct npc_data *)bl)->name, map[bl->m].name, bl->x, bl->y); + else + ShowDebug("Source (NPC): %s (invisible/not on a map)\n", ((struct npc_data *)bl)->name); + break; + default: + if( bl->m >= 0 ) + ShowDebug("Source (Non-NPC type %d): name %s at %s (%d,%d)\n", bl->type, status_get_name(bl), map[bl->m].name, bl->x, bl->y); + else + ShowDebug("Source (Non-NPC type %d): name %s (invisible/not on a map)\n", bl->type, status_get_name(bl)); + break; + } +} + +/// Reports on the console information about the script data. +static void script_reportdata(struct script_data* data) +{ + if( data == NULL ) + return; + switch( data->type ) + { + case C_NOP:// no value + ShowDebug("Data: nothing (nil)\n"); + break; + case C_INT:// number + ShowDebug("Data: number value=%d\n", data->u.num); + break; + case C_STR: + case C_CONSTSTR:// string + if( data->u.str ) + { + ShowDebug("Data: string value=\"%s\"\n", data->u.str); + } + else + { + ShowDebug("Data: string value=NULL\n"); + } + break; + case C_NAME:// reference + if( reference_tovariable(data) ) + {// variable + const char* name = reference_getname(data); + if( not_array_variable(*name) ) + ShowDebug("Data: variable name='%s'\n", name); + else + ShowDebug("Data: variable name='%s' index=%d\n", name, reference_getindex(data)); + } + else if( reference_toconstant(data) ) + {// constant + ShowDebug("Data: constant name='%s' value=%d\n", reference_getname(data), reference_getconstant(data)); + } + else if( reference_toparam(data) ) + {// param + ShowDebug("Data: param name='%s' type=%d\n", reference_getname(data), reference_getparamtype(data)); + } + else + {// ??? + ShowDebug("Data: reference name='%s' type=%s\n", reference_getname(data), script_op2name(data->type)); + ShowDebug("Please report this!!! - str_data.type=%s\n", script_op2name(str_data[reference_getid(data)].type)); + } + break; + case C_POS:// label + ShowDebug("Data: label pos=%d\n", data->u.num); + break; + default: + ShowDebug("Data: %s\n", script_op2name(data->type)); + break; + } +} + + +/// Reports on the console information about the current built-in function. +static void script_reportfunc(struct script_state* st) +{ + int i, params, id; + struct script_data* data; + + if( !script_hasdata(st,0) ) + {// no stack + return; + } + + data = script_getdata(st,0); + + if( !data_isreference(data) || str_data[reference_getid(data)].type != C_FUNC ) + {// script currently not executing a built-in function or corrupt stack + return; + } + + id = reference_getid(data); + params = script_lastdata(st)-1; + + if( params > 0 ) + { + ShowDebug("Function: %s (%d parameter%s):\n", get_str(id), params, ( params == 1 ) ? "" : "s"); + + for( i = 2; i <= script_lastdata(st); i++ ) + { + script_reportdata(script_getdata(st,i)); + } + } + else + { + ShowDebug("Function: %s (no parameters)\n", get_str(id)); + } +} + + +/*========================================== + * Output error message + *------------------------------------------*/ +static void disp_error_message2(const char *mes,const char *pos,int report) +{ + error_msg = aStrdup(mes); + error_pos = pos; + error_report = report; + longjmp( error_jump, 1 ); +} +#define disp_error_message(mes,pos) disp_error_message2(mes,pos,1) + +/// Checks event parameter validity +static void check_event(struct script_state *st, const char *evt) +{ + if( evt && evt[0] && !stristr(evt, "::On") ) + { + ShowWarning("NPC event parameter deprecated! Please use 'NPCNAME::OnEVENT' instead of '%s'.\n", evt); + script_reportsrc(st); + } +} + +/*========================================== + * Hashes the input string + *------------------------------------------*/ +static unsigned int calc_hash(const char* p) +{ + unsigned int h; + +#if defined(SCRIPT_HASH_DJB2) + h = 5381; + while( *p ) // hash*33 + c + h = ( h << 5 ) + h + ((unsigned char)TOLOWER(*p++)); +#elif defined(SCRIPT_HASH_SDBM) + h = 0; + while( *p ) // hash*65599 + c + h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)TOLOWER(*p++)); +#elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash + h = 0; + while( *p ){ + unsigned int g; + h = ( h << 4 ) + ((unsigned char)TOLOWER(*p++)); + g = h & 0xF0000000; + if( g ) + { + h ^= g >> 24; + h &= ~g; + } + } +#else // athena hash + h = 0; + while( *p ) + h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)TOLOWER(*p++); +#endif + + return h % SCRIPT_HASH_SIZE; +} + + +/*========================================== + * str_data manipulation functions + *------------------------------------------*/ + +/// Looks up string using the provided id. +const char* get_str(int id) +{ + Assert( id >= LABEL_START && id < str_size ); + return str_buf+str_data[id].str; +} + +/// Returns the uid of the string, or -1. +static int search_str(const char* p) +{ + int i; + + for( i = str_hash[calc_hash(p)]; i != 0; i = str_data[i].next ) + if( strcasecmp(get_str(i),p) == 0 ) + return i; + + return -1; +} + +/// Stores a copy of the string and returns its id. +/// If an identical string is already present, returns its id instead. +int add_str(const char* p) +{ + int i, h; + int len; + + h = calc_hash(p); + + if( str_hash[h] == 0 ) + {// empty bucket, add new node here + str_hash[h] = str_num; + } + else + {// scan for end of list, or occurence of identical string + for( i = str_hash[h]; ; i = str_data[i].next ) + { + if( strcasecmp(get_str(i),p) == 0 ) + return i; // string already in list + if( str_data[i].next == 0 ) + break; // reached the end + } + + // append node to end of list + str_data[i].next = str_num; + } + + // grow list if neccessary + if( str_num >= str_data_size ) + { + str_data_size += 128; + RECREATE(str_data,struct str_data_struct,str_data_size); + memset(str_data + (str_data_size - 128), '\0', 128); + } + + len=(int)strlen(p); + + // grow string buffer if neccessary + while( str_pos+len+1 >= str_size ) + { + str_size += 256; + RECREATE(str_buf,char,str_size); + memset(str_buf + (str_size - 256), '\0', 256); + } + + safestrncpy(str_buf+str_pos, p, len+1); + str_data[str_num].type = C_NOP; + str_data[str_num].str = str_pos; + str_data[str_num].next = 0; + str_data[str_num].func = NULL; + str_data[str_num].backpatch = -1; + str_data[str_num].label = -1; + str_pos += len+1; + + return str_num++; +} + + +/// Appends 1 byte to the script buffer. +static void add_scriptb(int a) +{ + if( script_pos+1 >= script_size ) + { + script_size += SCRIPT_BLOCK_SIZE; + RECREATE(script_buf,unsigned char,script_size); + } + script_buf[script_pos++] = (uint8)(a); +} + +/// Appends a c_op value to the script buffer. +/// The value is variable-length encoded into 8-bit blocks. +/// The encoding scheme is ( 01?????? )* 00??????, LSB first. +/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). +static void add_scriptc(int a) +{ + while( a >= 0x40 ) + { + add_scriptb((a&0x3f)|0x40); + a = (a - 0x40) >> 6; + } + + add_scriptb(a); +} + +/// Appends an integer value to the script buffer. +/// The value is variable-length encoded into 8-bit blocks. +/// The encoding scheme is ( 11?????? )* 10??????, LSB first. +/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). +static void add_scripti(int a) +{ + while( a >= 0x40 ) + { + add_scriptb((a&0x3f)|0xc0); + a = (a - 0x40) >> 6; + } + add_scriptb(a|0x80); +} + +/// Appends a str_data object (label/function/variable/integer) to the script buffer. + +/// +/// @param l The id of the str_data entry +// Maximum up to 16M +static void add_scriptl(int l) +{ + int backpatch = str_data[l].backpatch; + + switch(str_data[l].type){ + case C_POS: + case C_USERFUNC_POS: + add_scriptc(C_POS); + add_scriptb(str_data[l].label); + add_scriptb(str_data[l].label>>8); + add_scriptb(str_data[l].label>>16); + break; + case C_NOP: + case C_USERFUNC: + // Embedded data backpatch there is a possibility of label + add_scriptc(C_NAME); + str_data[l].backpatch = script_pos; + add_scriptb(backpatch); + add_scriptb(backpatch>>8); + add_scriptb(backpatch>>16); + break; + case C_INT: + add_scripti(abs(str_data[l].val)); + if( str_data[l].val < 0 ) //Notice that this is negative, from jA (Rayce) + add_scriptc(C_NEG); + break; + default: // assume C_NAME + add_scriptc(C_NAME); + add_scriptb(l); + add_scriptb(l>>8); + add_scriptb(l>>16); + break; + } +} + +/*========================================== + * Resolve the label + *------------------------------------------*/ +void set_label(int l,int pos, const char* script_pos) +{ + int i,next; + + if(str_data[l].type==C_INT || str_data[l].type==C_PARAM || str_data[l].type==C_FUNC) + { //Prevent overwriting constants values, parameters and built-in functions [Skotlex] + disp_error_message("set_label: invalid label name",script_pos); + return; + } + if(str_data[l].label!=-1){ + disp_error_message("set_label: dup label ",script_pos); + return; + } + str_data[l].type=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); + str_data[l].label=pos; + for(i=str_data[l].backpatch;i>=0 && i!=0x00ffffff;){ + next=GETVALUE(script_buf,i); + script_buf[i-1]=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); + SETVALUE(script_buf,i,pos); + i=next; + } +} + +/// Skips spaces and/or comments. +const char* skip_space(const char* p) +{ + if( p == NULL ) + return NULL; + for(;;) + { + while( ISSPACE(*p) ) + ++p; + if( *p == '/' && p[1] == '/' ) + {// line comment + while(*p && *p!='\n') + ++p; + } + else if( *p == '/' && p[1] == '*' ) + {// block comment + p += 2; + for(;;) + { + if( *p == '\0' ) + return p;//disp_error_message("script:skip_space: end of file while parsing block comment. expected "CL_BOLD"*/"CL_NORM, p); + if( *p == '*' && p[1] == '/' ) + {// end of block comment + p += 2; + break; + } + ++p; + } + } + else + break; + } + return p; +} + +/// Skips a word. +/// A word consists of undercores and/or alfanumeric characters, +/// and valid variable prefixes/postfixes. +static +const char* skip_word(const char* p) +{ + // prefix + switch( *p ) + { + case '@':// temporary char variable + ++p; break; + case '#':// account variable + p += ( p[1] == '#' ? 2 : 1 ); break; + case '\'':// instance variable + ++p; break; + case '.':// npc variable + p += ( p[1] == '@' ? 2 : 1 ); break; + case '$':// global variable + p += ( p[1] == '@' ? 2 : 1 ); break; + } + + while( ISALNUM(*p) || *p == '_' ) + ++p; + + // postfix + if( *p == '$' )// string + p++; + + return p; +} + +/// Adds a word to str_data. +/// @see skip_word +/// @see add_str +static +int add_word(const char* p) +{ + char* word; + int len; + int i; + + // Check for a word + len = skip_word(p) - p; + if( len == 0 ) + disp_error_message("script:add_word: invalid word. A word consists of undercores and/or alfanumeric characters, and valid variable prefixes/postfixes.", p); + + // Duplicate the word + word = (char*)aMalloc(len+1); + memcpy(word, p, len); + word[len] = 0; + + // add the word + i = add_str(word); + aFree(word); + return i; +} + +/// Parses a function call. +/// The argument list can have parenthesis or not. +/// The number of arguments is checked. +static +const char* parse_callfunc(const char* p, int require_paren, int is_custom) +{ + const char* p2; + const char* arg=NULL; + int func; + + func = add_word(p); + if( str_data[func].type == C_FUNC ){ + // buildin function + add_scriptl(func); + add_scriptc(C_ARG); + arg = buildin_func[str_data[func].val].arg; + } else if( str_data[func].type == C_USERFUNC || str_data[func].type == C_USERFUNC_POS ){ + // script defined function + add_scriptl(buildin_callsub_ref); + add_scriptc(C_ARG); + add_scriptl(func); + arg = buildin_func[str_data[buildin_callsub_ref].val].arg; + if( *arg == 0 ) + disp_error_message("parse_callfunc: callsub has no arguments, please review it's definition",p); + if( *arg != '*' ) + ++arg; // count func as argument + } else { +#ifdef SCRIPT_CALLFUNC_CHECK + const char* name = get_str(func); + if( !is_custom && strdb_get(userfunc_db, name) == NULL ) { +#endif + disp_error_message("parse_line: expect command, missing function name or calling undeclared function",p); +#ifdef SCRIPT_CALLFUNC_CHECK + } else {; + add_scriptl(buildin_callfunc_ref); + add_scriptc(C_ARG); + add_scriptc(C_STR); + while( *name ) add_scriptb(*name ++); + add_scriptb(0); + arg = buildin_func[str_data[buildin_callfunc_ref].val].arg; + if( *arg != '*' ) ++ arg; + } +#endif + } + + p = skip_word(p); + p = skip_space(p); + syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; + syntax.curly[syntax.curly_count].count = 0; + if( *p == ';' ) + {// <func name> ';' + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + } else if( *p == '(' && *(p2=skip_space(p+1)) == ')' ) + {// <func name> '(' ')' + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + p = p2; + /* + } else if( 0 && require_paren && *p != '(' ) + {// <func name> + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + */ + } else + {// <func name> <arg list> + if( require_paren ){ + if( *p != '(' ) + disp_error_message("need '('",p); + ++p; // skip '(' + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + } else if( *p == '(' ){ + syntax.curly[syntax.curly_count].flag = ARGLIST_UNDEFINED; + } else { + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + } + ++syntax.curly_count; + while( *arg ) { + p2=parse_subexpr(p,-1); + if( p == p2 ) + break; // not an argument + if( *arg != '*' ) + ++arg; // next argument + + p=skip_space(p2); + if( *arg == 0 || *p != ',' ) + break; // no more arguments + ++p; // skip comma + } + --syntax.curly_count; + } + if( *arg && *arg != '?' && *arg != '*' ) + disp_error_message2("parse_callfunc: not enough arguments, expected ','", p, script_config.warn_func_mismatch_paramnum); + if( syntax.curly[syntax.curly_count].type != TYPE_ARGLIST ) + disp_error_message("parse_callfunc: DEBUG last curly is not an argument list",p); + if( syntax.curly[syntax.curly_count].flag == ARGLIST_PAREN ){ + if( *p != ')' ) + disp_error_message("parse_callfunc: expected ')' to close argument list",p); + ++p; + } + add_scriptc(C_FUNC); + return p; +} + +/// Processes end of logical script line. +/// @param first When true, only fix up scheduling data is initialized +/// @param p Script position for error reporting in set_label +static void parse_nextline(bool first, const char* p) +{ + if( !first ) + { + add_scriptc(C_EOL); // mark end of line for stack cleanup + set_label(LABEL_NEXTLINE, script_pos, p); // fix up '-' labels + } + + // initialize data for new '-' label fix up scheduling + str_data[LABEL_NEXTLINE].type = C_NOP; + str_data[LABEL_NEXTLINE].backpatch = -1; + str_data[LABEL_NEXTLINE].label = -1; +} + +/// Parse a variable assignment using the direct equals operator +/// @param p script position where the function should run from +/// @return NULL if not a variable assignment, the new position otherwise +const char* parse_variable(const char* p) { + int i, j, word; + c_op type = C_NOP; + const char *p2 = NULL; + const char *var = p; + + // skip the variable where applicable + p = skip_word(p); + p = skip_space(p); + + if( p == NULL ) {// end of the line or invalid buffer + return NULL; + } + + if( *p == '[' ) {// array variable so process the array as appropriate + for( p2 = p, i = 0, j = 1; p; ++ i ) { + if( *p ++ == ']' && --(j) == 0 ) break; + if( *p == '[' ) ++ j; + } + + if( !(p = skip_space(p)) ) {// end of line or invalid characters remaining + disp_error_message("Missing right expression or closing bracket for variable.", p); + } + } + + if( type == C_NOP && + !( ( p[0] == '=' && p[1] != '=' && (type = C_EQ) ) // = + || ( p[0] == '+' && p[1] == '=' && (type = C_ADD) ) // += + || ( p[0] == '-' && p[1] == '=' && (type = C_SUB) ) // -= + || ( p[0] == '^' && p[1] == '=' && (type = C_XOR) ) // ^= + || ( p[0] == '|' && p[1] == '=' && (type = C_OR ) ) // |= + || ( p[0] == '&' && p[1] == '=' && (type = C_AND) ) // &= + || ( p[0] == '*' && p[1] == '=' && (type = C_MUL) ) // *= + || ( p[0] == '/' && p[1] == '=' && (type = C_DIV) ) // /= + || ( p[0] == '%' && p[1] == '=' && (type = C_MOD) ) // %= + || ( p[0] == '~' && p[1] == '=' && (type = C_NOT) ) // ~= + || ( p[0] == '+' && p[1] == '+' && (type = C_ADD_PP) ) // ++ + || ( p[0] == '-' && p[1] == '-' && (type = C_SUB_PP) ) // -- + || ( p[0] == '<' && p[1] == '<' && p[2] == '=' && (type = C_L_SHIFT) ) // <<= + || ( p[0] == '>' && p[1] == '>' && p[2] == '=' && (type = C_R_SHIFT) ) // >>= + ) ) + {// failed to find a matching operator combination so invalid + return NULL; + } + + switch( type ) { + case C_EQ: {// incremental modifier + p = skip_space( &p[1] ); + } + break; + + case C_L_SHIFT: + case C_R_SHIFT: {// left or right shift modifier + p = skip_space( &p[3] ); + } + break; + + default: {// normal incremental command + p = skip_space( &p[2] ); + } + } + + if( p == NULL ) {// end of line or invalid buffer + return NULL; + } + + // push the set function onto the stack + add_scriptl(buildin_set_ref); + add_scriptc(C_ARG); + + // always append parenthesis to avoid errors + syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; + syntax.curly[syntax.curly_count].count = 0; + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + + // increment the total curly count for the position in the script + ++ syntax.curly_count; + + // parse the variable currently being modified + word = add_word(var); + + if( str_data[word].type == C_FUNC || str_data[word].type == C_USERFUNC || str_data[word].type == C_USERFUNC_POS ) + {// cannot assign a variable which exists as a function or label + disp_error_message("Cannot modify a variable which has the same name as a function or label.", p); + } + + if( p2 ) {// process the variable index + const char* p3 = NULL; + + // push the getelementofarray method into the stack + add_scriptl(buildin_getelementofarray_ref); + add_scriptc(C_ARG); + add_scriptl(word); + + // process the sub-expression for this assignment + p3 = parse_subexpr(p2 + 1, 1); + p3 = skip_space(p3); + + if( *p3 != ']' ) {// closing parenthesis is required for this script + disp_error_message("Missing closing ']' parenthesis for the variable assignment.", p3); + } + + // push the closing function stack operator onto the stack + add_scriptc(C_FUNC); + p3 ++; + } else {// simply push the variable or value onto the stack + add_scriptl(word); + } + + if( type != C_EQ ) + add_scriptc(C_REF); + + if( type == C_ADD_PP || type == C_SUB_PP ) {// incremental operator for the method + add_scripti(1); + add_scriptc(type == C_ADD_PP ? C_ADD : C_SUB); + } else {// process the value as an expression + p = parse_subexpr(p, -1); + + if( type != C_EQ ) + {// push the type of modifier onto the stack + add_scriptc(type); + } + } + + // decrement the curly count for the position within the script + -- syntax.curly_count; + + // close the script by appending the function operator + add_scriptc(C_FUNC); + + // push the buffer from the method + return p; +} + +/*========================================== + * Analysis section + *------------------------------------------*/ +const char* parse_simpleexpr(const char *p) +{ + int i; + p=skip_space(p); + + if(*p==';' || *p==',') + disp_error_message("parse_simpleexpr: unexpected expr end",p); + if(*p=='('){ + if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST ) + ++syntax.curly[i].count; + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST && + syntax.curly[i].flag == ARGLIST_UNDEFINED && --syntax.curly[i].count == 0 + ){ + if( *p == ',' ){ + syntax.curly[i].flag = ARGLIST_PAREN; + return p; + } else + syntax.curly[i].flag = ARGLIST_NO_PAREN; + } + if( *p != ')' ) + disp_error_message("parse_simpleexpr: unmatch ')'",p); + ++p; + } else if(ISDIGIT(*p) || ((*p=='-' || *p=='+') && ISDIGIT(p[1]))){ + char *np; + while(*p == '0' && ISDIGIT(p[1])) p++; + i=strtoul(p,&np,0); + add_scripti(i); + p=np; + } else if(*p=='"'){ + add_scriptc(C_STR); + p++; + while( *p && *p != '"' ){ + if( (unsigned char)p[-1] <= 0x7e && *p == '\\' ) + { + char buf[8]; + size_t len = skip_escaped_c(p) - p; + size_t n = sv_unescape_c(buf, p, len); + if( n != 1 ) + ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf); + p += len; + add_scriptb(*buf); + continue; + } + else if( *p == '\n' ) + disp_error_message("parse_simpleexpr: unexpected newline @ string",p); + add_scriptb(*p++); + } + if(!*p) + disp_error_message("parse_simpleexpr: unexpected eof @ string",p); + add_scriptb(0); + p++; //'"' + } else { + int l; + const char* pv; + + // label , register , function etc + if(skip_word(p)==p) + disp_error_message("parse_simpleexpr: unexpected character",p); + + l=add_word(p); + if( str_data[l].type == C_FUNC || str_data[l].type == C_USERFUNC || str_data[l].type == C_USERFUNC_POS) + return parse_callfunc(p,1,0); +#ifdef SCRIPT_CALLFUNC_CHECK + else { + const char* name = get_str(l); + if( strdb_get(userfunc_db,name) != NULL ) { + return parse_callfunc(p,1,1); + } + } +#endif + + if( (pv = parse_variable(p)) ) + {// successfully processed a variable assignment + return pv; + } + + p=skip_word(p); + if( *p == '[' ){ + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(buildin_getelementofarray_ref); + add_scriptc(C_ARG); + add_scriptl(l); + + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if( *p != ']' ) + disp_error_message("parse_simpleexpr: unmatch ']'",p); + ++p; + add_scriptc(C_FUNC); + }else + add_scriptl(l); + + } + + return p; +} + +/*========================================== + * Analysis of the expression + *------------------------------------------*/ +const char* parse_subexpr(const char* p,int limit) +{ + int op,opl,len; + const char* tmpp; + + p=skip_space(p); + + if( *p == '-' ){ + tmpp = skip_space(p+1); + if( *tmpp == ';' || *tmpp == ',' ){ + add_scriptl(LABEL_NEXTLINE); + p++; + return p; + } + } + + if((op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~')){ + p=parse_subexpr(p+1,10); + add_scriptc(op); + } else + p=parse_simpleexpr(p); + p=skip_space(p); + while(( + (op=C_OP3,opl=0,len=1,*p=='?') || + (op=C_ADD,opl=8,len=1,*p=='+') || + (op=C_SUB,opl=8,len=1,*p=='-') || + (op=C_MUL,opl=9,len=1,*p=='*') || + (op=C_DIV,opl=9,len=1,*p=='/') || + (op=C_MOD,opl=9,len=1,*p=='%') || + (op=C_LAND,opl=2,len=2,*p=='&' && p[1]=='&') || + (op=C_AND,opl=6,len=1,*p=='&') || + (op=C_LOR,opl=1,len=2,*p=='|' && p[1]=='|') || + (op=C_OR,opl=5,len=1,*p=='|') || + (op=C_XOR,opl=4,len=1,*p=='^') || + (op=C_EQ,opl=3,len=2,*p=='=' && p[1]=='=') || + (op=C_NE,opl=3,len=2,*p=='!' && p[1]=='=') || + (op=C_R_SHIFT,opl=7,len=2,*p=='>' && p[1]=='>') || + (op=C_GE,opl=3,len=2,*p=='>' && p[1]=='=') || + (op=C_GT,opl=3,len=1,*p=='>') || + (op=C_L_SHIFT,opl=7,len=2,*p=='<' && p[1]=='<') || + (op=C_LE,opl=3,len=2,*p=='<' && p[1]=='=') || + (op=C_LT,opl=3,len=1,*p=='<')) && opl>limit){ + p+=len; + if(op == C_OP3) { + p=parse_subexpr(p,-1); + p=skip_space(p); + if( *(p++) != ':') + disp_error_message("parse_subexpr: need ':'", p-1); + p=parse_subexpr(p,-1); + } else { + p=parse_subexpr(p,opl); + } + add_scriptc(op); + p=skip_space(p); + } + + return p; /* return first untreated operator */ +} + +/*========================================== + * Evaluation of the expression + *------------------------------------------*/ +const char* parse_expr(const char *p) +{ + switch(*p){ + case ')': case ';': case ':': case '[': case ']': + case '}': + disp_error_message("parse_expr: unexpected char",p); + } + p=parse_subexpr(p,-1); + return p; +} + +/*========================================== + * Analysis of the line + *------------------------------------------*/ +const char* parse_line(const char* p) +{ + const char* p2; + + p=skip_space(p); + if(*p==';') { + //Close decision for if(); for(); while(); + p = parse_syntax_close(p + 1); + return p; + } + if(*p==')' && parse_syntax_for_flag) + return p+1; + + p = skip_space(p); + if(p[0] == '{') { + syntax.curly[syntax.curly_count].type = TYPE_NULL; + syntax.curly[syntax.curly_count].count = -1; + syntax.curly[syntax.curly_count].index = -1; + syntax.curly_count++; + return p + 1; + } else if(p[0] == '}') { + return parse_curly_close(p); + } + + // Syntax-related processing + p2 = parse_syntax(p); + if(p2 != NULL) + return p2; + + // attempt to process a variable assignment + p2 = parse_variable(p); + + if( p2 != NULL ) + {// variable assignment processed so leave the method + return parse_syntax_close(p2 + 1); + } + + p = parse_callfunc(p,0,0); + p = skip_space(p); + + if(parse_syntax_for_flag) { + if( *p != ')' ) + disp_error_message("parse_line: need ')'",p); + } else { + if( *p != ';' ) + disp_error_message("parse_line: need ';'",p); + } + + //Binding decision for if(), for(), while() + p = parse_syntax_close(p+1); + + return p; +} + +// { ... } Closing process +const char* parse_curly_close(const char* p) +{ + if(syntax.curly_count <= 0) { + disp_error_message("parse_curly_close: unexpected string",p); + return p + 1; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_NULL) { + syntax.curly_count--; + //Close decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_SWITCH) { + //Closing switch() + int pos = syntax.curly_count-1; + char label[256]; + int l; + // Remove temporary variables + sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Go to the end pointer unconditionally + sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // You are here labeled + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos, p); + + if(syntax.curly[pos].flag) { + //Exists default + sprintf(label,"goto __SW%x_DEF;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + + // Label end + sprintf(label,"__SW%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos, p); + linkdb_final(&syntax.curly[pos].case_label); // free the list of case label + syntax.curly_count--; + //Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } else { + disp_error_message("parse_curly_close: unexpected string",p); + return p + 1; + } +} + +// Syntax-related processing +// break, case, continue, default, do, for, function, +// if, switch, while ? will handle this internally. +const char* parse_syntax(const char* p) +{ + const char *p2 = skip_word(p); + + switch(*p) { + case 'B': + case 'b': + if(p2 - p == 5 && !strncasecmp(p,"break",5)) { + // break Processing + char label[256]; + int pos = syntax.curly_count - 1; + while(pos >= 0) { + if(syntax.curly[pos].type == TYPE_DO) { + sprintf(label,"goto __DO%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_FOR) { + sprintf(label,"goto __FR%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + sprintf(label,"goto __WL%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_SWITCH) { + sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); + break; + } + pos--; + } + if(pos < 0) { + disp_error_message("parse_syntax: unexpected 'break'",p); + } else { + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + p = skip_space(p2); + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + // Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } + break; + case 'c': + case 'C': + if(p2 - p == 4 && !strncasecmp(p,"case",4)) { + //Processing case + int pos = syntax.curly_count-1; + if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { + disp_error_message("parse_syntax: unexpected 'case' ",p); + return p+1; + } else { + char label[256]; + int l,v; + char *np; + if(syntax.curly[pos].count != 1) { + //Jump for FALLTHRU + sprintf(label,"goto __SW%x_%xJ;",syntax.curly[pos].index,syntax.curly[pos].count); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // You are here labeled + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos, p); + } + //Decision statement switch + p = skip_space(p2); + if(p == p2) { + disp_error_message("parse_syntax: expect space ' '",p); + } + // check whether case label is integer or not + v = strtol(p,&np,0); + if(np == p) { //Check for constants + p2 = skip_word(p); + v = p2-p; // length of word at p2 + memcpy(label,p,v); + label[v]='\0'; + if( !script_get_constant(label, &v) ) + disp_error_message("parse_syntax: 'case' label not integer",p); + p = skip_word(p); + } else { //Numeric value + if((*p == '-' || *p == '+') && ISDIGIT(p[1])) // pre-skip because '-' can not skip_word + p++; + p = skip_word(p); + if(np != p) + disp_error_message("parse_syntax: 'case' label not integer",np); + } + p = skip_space(p); + if(*p != ':') + disp_error_message("parse_syntax: expect ':'",p); + sprintf(label,"if(%d != $@__SW%x_VAL) goto __SW%x_%x;", + v,syntax.curly[pos].index,syntax.curly[pos].index,syntax.curly[pos].count+1); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + // Bad I do not parse twice + p2 = parse_line(label); + parse_line(p2); + syntax.curly_count--; + if(syntax.curly[pos].count != 1) { + // Label after the completion of FALLTHRU + sprintf(label,"__SW%x_%xJ",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + } + // check duplication of case label [Rayce] + if(linkdb_search(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v)) != NULL) + disp_error_message("parse_syntax: dup 'case'",p); + linkdb_insert(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v), (void*)1); + + sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + + parse_line(label); + syntax.curly_count--; + syntax.curly[pos].count++; + } + return p + 1; + } else if(p2 - p == 8 && !strncasecmp(p,"continue",8)) { + // Processing continue + char label[256]; + int pos = syntax.curly_count - 1; + while(pos >= 0) { + if(syntax.curly[pos].type == TYPE_DO) { + sprintf(label,"goto __DO%x_NXT;",syntax.curly[pos].index); + syntax.curly[pos].flag = 1; //Flag put the link for continue + break; + } else if(syntax.curly[pos].type == TYPE_FOR) { + sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); + break; + } + pos--; + } + if(pos < 0) { + disp_error_message("parse_syntax: unexpected 'continue'",p); + } else { + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + p = skip_space(p2); + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + //Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } + break; + case 'd': + case 'D': + if(p2 - p == 7 && !strncasecmp(p,"default",7)) { + // Switch - default processing + int pos = syntax.curly_count-1; + if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { + disp_error_message("parse_syntax: unexpected 'default'",p); + } else if(syntax.curly[pos].flag) { + disp_error_message("parse_syntax: dup 'default'",p); + } else { + char label[256]; + int l; + // Put the label location + p = skip_space(p2); + if(*p != ':') { + disp_error_message("parse_syntax: need ':'",p); + } + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + + // Skip to the next link w/o condition + sprintf(label,"goto __SW%x_%x;",syntax.curly[pos].index,syntax.curly[pos].count+1); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // The default label + sprintf(label,"__SW%x_DEF",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + syntax.curly[syntax.curly_count - 1].flag = 1; + syntax.curly[pos].count++; + } + return p + 1; + } else if(p2 - p == 2 && !strncasecmp(p,"do",2)) { + int l; + char label[256]; + p=skip_space(p2); + + syntax.curly[syntax.curly_count].type = TYPE_DO; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + // Label of the (do) form here + sprintf(label,"__DO%x_BGN",syntax.curly[syntax.curly_count].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count++; + return p; + } + break; + case 'f': + case 'F': + if(p2 - p == 3 && !strncasecmp(p,"for",3)) { + int l; + char label[256]; + int pos = syntax.curly_count; + syntax.curly[syntax.curly_count].type = TYPE_FOR; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + syntax.curly_count++; + + p=skip_space(p2); + + if(*p != '(') + disp_error_message("parse_syntax: need '('",p); + p++; + + // Execute the initialization statement + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + p=parse_line(p); + syntax.curly_count--; + + // Form the start of label decision + sprintf(label,"__FR%x_J",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + p=skip_space(p); + if(*p == ';') { + // For (; Because the pattern of always true ;) + ; + } else { + // Skip to the end point if the condition is false + sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + } + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + p++; + + // Skip to the beginning of the loop + sprintf(label,"goto __FR%x_BGN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Labels to form the next loop + sprintf(label,"__FR%x_NXT",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + // Process the next time you enter the loop + // A ')' last for; flag to be treated as' + parse_syntax_for_flag = 1; + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + p=parse_line(p); + syntax.curly_count--; + parse_syntax_for_flag = 0; + + // Skip to the determination process conditions + sprintf(label,"goto __FR%x_J;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Loop start labeling + sprintf(label,"__FR%x_BGN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + return p; + } + else if( p2 - p == 8 && strncasecmp(p,"function",8) == 0 ) + {// internal script function + const char *func_name; + + func_name = skip_space(p2); + p = skip_word(func_name); + if( p == func_name ) + disp_error_message("parse_syntax:function: function name is missing or invalid", p); + p2 = skip_space(p); + if( *p2 == ';' ) + {// function <name> ; + // function declaration - just register the name + int l; + l = add_word(func_name); + if( str_data[l].type == C_NOP )// register only, if the name was not used by something else + str_data[l].type = C_USERFUNC; + else if( str_data[l].type == C_USERFUNC ) + ; // already registered + else + disp_error_message("parse_syntax:function: function name is invalid", func_name); + + // Close condition of if, for, while + p = parse_syntax_close(p2 + 1); + return p; + } + else if(*p2 == '{') + {// function <name> <line/block of code> + char label[256]; + int l; + + syntax.curly[syntax.curly_count].type = TYPE_USERFUNC; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + ++syntax.curly_count; + + // Jump over the function code + sprintf(label, "goto __FN%x_FIN;", syntax.curly[syntax.curly_count-1].index); + syntax.curly[syntax.curly_count].type = TYPE_NULL; + ++syntax.curly_count; + parse_line(label); + --syntax.curly_count; + + // Set the position of the function (label) + l=add_word(func_name); + if( str_data[l].type == C_NOP || str_data[l].type == C_USERFUNC )// register only, if the name was not used by something else + { + str_data[l].type = C_USERFUNC; + set_label(l, script_pos, p); + if( parse_options&SCRIPT_USE_LABEL_DB ) + strdb_iput(scriptlabel_db, get_str(l), script_pos); + } + else + disp_error_message("parse_syntax:function: function name is invalid", func_name); + + return skip_space(p); + } + else + { + disp_error_message("expect ';' or '{' at function syntax",p); + } + } + break; + case 'i': + case 'I': + if(p2 - p == 2 && !strncasecmp(p,"if",2)) { + // If process + char label[256]; + p=skip_space(p2); + if(*p != '(') { //Prevent if this {} non-c syntax. from Rayce (jA) + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_IF; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + sprintf(label,"__IF%x_%x",syntax.curly[syntax.curly_count].index,syntax.curly[syntax.curly_count].count); + syntax.curly_count++; + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + return p; + } + break; + case 's': + case 'S': + if(p2 - p == 6 && !strncasecmp(p,"switch",6)) { + // Processing of switch () + char label[256]; + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_SWITCH; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + sprintf(label,"$@__SW%x_VAL",syntax.curly[syntax.curly_count].index); + syntax.curly_count++; + add_scriptl(add_str("set")); + add_scriptc(C_ARG); + add_scriptl(add_str(label)); + p=parse_expr(p); + p=skip_space(p); + if(*p != '{') { + disp_error_message("parse_syntax: need '{'",p); + } + add_scriptc(C_FUNC); + return p + 1; + } + break; + case 'w': + case 'W': + if(p2 - p == 5 && !strncasecmp(p,"while",5)) { + int l; + char label[256]; + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_WHILE; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + // Form the start of label decision + sprintf(label,"__WL%x_NXT",syntax.curly[syntax.curly_count].index); + l=add_str(label); + set_label(l,script_pos,p); + + // Skip to the end point if the condition is false + sprintf(label,"__WL%x_FIN",syntax.curly[syntax.curly_count].index); + syntax.curly_count++; + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + return p; + } + break; + } + return NULL; +} + +const char* parse_syntax_close(const char *p) { + // If (...) for (...) hoge (); as to make sure closed closed once again + int flag; + + do { + p = parse_syntax_close_sub(p,&flag); + } while(flag); + return p; +} + +// Close judgment if, for, while, of do +// flag == 1 : closed +// flag == 0 : not closed +const char* parse_syntax_close_sub(const char* p,int* flag) +{ + char label[256]; + int pos = syntax.curly_count - 1; + int l; + *flag = 1; + + if(syntax.curly_count <= 0) { + *flag = 0; + return p; + } else if(syntax.curly[pos].type == TYPE_IF) { + const char *bp = p; + const char *p2; + + // if-block and else-block end is a new line + parse_nextline(false, p); + + // Skip to the last location if + sprintf(label,"goto __IF%x_FIN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Put the label of the location + sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + + syntax.curly[pos].count++; + p = skip_space(p); + p2 = skip_word(p); + if(!syntax.curly[pos].flag && p2 - p == 4 && !strncasecmp(p,"else",4)) { + // else or else - if + p = skip_space(p2); + p2 = skip_word(p); + if(p2 - p == 2 && !strncasecmp(p,"if",2)) { + // else - if + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + *flag = 0; + return p; + } else { + // else + if(!syntax.curly[pos].flag) { + syntax.curly[pos].flag = 1; + *flag = 0; + return p; + } + } + } + // Close if + syntax.curly_count--; + // Put the label of the final location + sprintf(label,"__IF%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + if(syntax.curly[pos].flag == 1) { + // Because the position of the pointer is the same if not else for this + return bp; + } + return p; + } else if(syntax.curly[pos].type == TYPE_DO) { + int l; + char label[256]; + const char *p2; + + if(syntax.curly[pos].flag) { + // (Come here continue) to form the label here + sprintf(label,"__DO%x_NXT",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + } + + // Skip to the end point if the condition is false + p = skip_space(p); + p2 = skip_word(p); + if(p2 - p != 5 || strncasecmp(p,"while",5)) + disp_error_message("parse_syntax: need 'while'",p); + + p = skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + + // do-block end is a new line + parse_nextline(false, p); + + sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + + // Skip to the starting point + sprintf(label,"goto __DO%x_BGN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Form label of the end point conditions + sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + p = skip_space(p); + if(*p != ';') { + disp_error_message("parse_syntax: need ';'",p); + return p+1; + } + p++; + syntax.curly_count--; + return p; + } else if(syntax.curly[pos].type == TYPE_FOR) { + // for-block end is a new line + parse_nextline(false, p); + + // Skip to the next loop + sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // End for labeling + sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + // while-block end is a new line + parse_nextline(false, p); + + // Skip to the decision while + sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // End while labeling + sprintf(label,"__WL%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_USERFUNC) { + int pos = syntax.curly_count-1; + char label[256]; + int l; + // Back + sprintf(label,"return;"); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Put the label of the location + sprintf(label,"__FN%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else { + *flag = 0; + return p; + } +} + +/*========================================== + * Added built-in functions + *------------------------------------------*/ +static void add_buildin_func(void) +{ + int i,n; + const char* p; + for( i = 0; buildin_func[i].func; i++ ) + { + // arg must follow the pattern: (v|s|i|r|l)*\?*\*? + // 'v' - value (either string or int or reference) + // 's' - string + // 'i' - int + // 'r' - reference (of a variable) + // 'l' - label + // '?' - one optional parameter + // '*' - unknown number of optional parameters + p = buildin_func[i].arg; + while( *p == 'v' || *p == 's' || *p == 'i' || *p == 'r' || *p == 'l' ) ++p; + while( *p == '?' ) ++p; + if( *p == '*' ) ++p; + if( *p != 0){ + ShowWarning("add_buildin_func: ignoring function \"%s\" with invalid arg \"%s\".\n", buildin_func[i].name, buildin_func[i].arg); + } else if( *skip_word(buildin_func[i].name) != 0 ){ + ShowWarning("add_buildin_func: ignoring function with invalid name \"%s\" (must be a word).\n", buildin_func[i].name); + } else { + n = add_str(buildin_func[i].name); + str_data[n].type = C_FUNC; + str_data[n].val = i; + str_data[n].func = buildin_func[i].func; + + if (!strcmp(buildin_func[i].name, "set")) buildin_set_ref = n; + else if (!strcmp(buildin_func[i].name, "callsub")) buildin_callsub_ref = n; + else if (!strcmp(buildin_func[i].name, "callfunc")) buildin_callfunc_ref = n; + else if( !strcmp(buildin_func[i].name, "getelementofarray") ) buildin_getelementofarray_ref = n; + } + } +} + +/// Retrieves the value of a constant. +bool script_get_constant(const char* name, int* value) +{ + int n = search_str(name); + + if( n == -1 || str_data[n].type != C_INT ) + {// not found or not a constant + return false; + } + value[0] = str_data[n].val; + + return true; +} + +/// Creates new constant or parameter with given value. +void script_set_constant(const char* name, int value, bool isparameter) +{ + int n = add_str(name); + + if( str_data[n].type == C_NOP ) + {// new + str_data[n].type = isparameter ? C_PARAM : C_INT; + str_data[n].val = value; + } + else if( str_data[n].type == C_PARAM || str_data[n].type == C_INT ) + {// existing parameter or constant + ShowError("script_set_constant: Attempted to overwrite existing %s '%s' (old value=%d, new value=%d).\n", ( str_data[n].type == C_PARAM ) ? "parameter" : "constant", name, str_data[n].val, value); + } + else + {// existing name + ShowError("script_set_constant: Invalid name for %s '%s' (already defined as %s).\n", isparameter ? "parameter" : "constant", name, script_op2name(str_data[n].type)); + } +} + +/*========================================== + * Reading constant databases + * const.txt + *------------------------------------------*/ +static void read_constdb(void) +{ + FILE *fp; + char line[1024],name[1024],val[1024]; + int type; + + sprintf(line, "%s/const.txt", db_path); + fp=fopen(line, "r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return ; + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0]=='/' && line[1]=='/') + continue; + type=0; + if(sscanf(line,"%[A-Za-z0-9_],%[-0-9xXA-Fa-f],%d",name,val,&type)>=2 || + sscanf(line,"%[A-Za-z0-9_] %[-0-9xXA-Fa-f] %d",name,val,&type)>=2){ + script_set_constant(name, (int)strtol(val, NULL, 0), (bool)type); + } + } + fclose(fp); +} + +/*========================================== + * Display emplacement line of script + *------------------------------------------*/ +static const char* script_print_line(StringBuf* buf, const char* p, const char* mark, int line) +{ + int i; + if( p == NULL || !p[0] ) return NULL; + if( line < 0 ) + StringBuf_Printf(buf, "*% 5d : ", -line); + else + StringBuf_Printf(buf, " % 5d : ", line); + for(i=0;p[i] && p[i] != '\n';i++){ + if(p + i != mark) + StringBuf_Printf(buf, "%c", p[i]); + else + StringBuf_Printf(buf, "\'%c\'", p[i]); + } + StringBuf_AppendStr(buf, "\n"); + return p+i+(p[i] == '\n' ? 1 : 0); +} + +void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos) +{ + // Find the line where the error occurred + int j; + int line = start_line; + const char *p; + const char *linestart[5] = { NULL, NULL, NULL, NULL, NULL }; + StringBuf buf; + + for(p=src;p && *p;line++){ + const char *lineend=strchr(p,'\n'); + if(lineend==NULL || error_pos<lineend){ + break; + } + for( j = 0; j < 4; j++ ) { + linestart[j] = linestart[j+1]; + } + linestart[4] = p; + p=lineend+1; + } + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "\a\n"); + StringBuf_Printf(&buf, "script error on %s line %d\n", file, line); + StringBuf_Printf(&buf, " %s\n", error_msg); + for(j = 0; j < 5; j++ ) { + script_print_line(&buf, linestart[j], NULL, line + j - 5); + } + p = script_print_line(&buf, p, error_pos, -line); + for(j = 0; j < 5; j++) { + p = script_print_line(&buf, p, NULL, line + j + 1 ); + } + ShowError("%s", StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); +} + +/*========================================== + * Analysis of the script + *------------------------------------------*/ +struct script_code* parse_script(const char *src,const char *file,int line,int options) +{ + const char *p,*tmpp; + int i; + struct script_code* code = NULL; + static int first=1; + char end; + bool unresolved_names = false; + + if( src == NULL ) + return NULL;// empty script + + memset(&syntax,0,sizeof(syntax)); + if(first){ + add_buildin_func(); + read_constdb(); + first=0; + } + + script_buf=(unsigned char *)aMalloc(SCRIPT_BLOCK_SIZE*sizeof(unsigned char)); + script_pos=0; + script_size=SCRIPT_BLOCK_SIZE; + parse_nextline(true, NULL); + + // who called parse_script is responsible for clearing the database after using it, but just in case... lets clear it here + if( options&SCRIPT_USE_LABEL_DB ) + db_clear(scriptlabel_db); + parse_options = options; + + if( setjmp( error_jump ) != 0 ) { + //Restore program state when script has problems. [from jA] + int i; + const int size = ARRAYLENGTH(syntax.curly); + if( error_report ) + script_error(src,file,line,error_msg,error_pos); + aFree( error_msg ); + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + for(i=LABEL_START;i<str_num;i++) + if(str_data[i].type == C_NOP) str_data[i].type = C_NAME; + for(i=0; i<size; i++) + linkdb_final(&syntax.curly[i].case_label); + return NULL; + } + + parse_syntax_for_flag=0; + p=src; + p=skip_space(p); + if( options&SCRIPT_IGNORE_EXTERNAL_BRACKETS ) + {// does not require brackets around the script + if( *p == '\0' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) + {// empty script and can return NULL + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + return NULL; + } + end = '\0'; + } + else + {// requires brackets around the script + if( *p != '{' ) + disp_error_message("not found '{'",p); + p = skip_space(p+1); + if( *p == '}' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) + {// empty script and can return NULL + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + return NULL; + } + end = '}'; + } + + // clear references of labels, variables and internal functions + for(i=LABEL_START;i<str_num;i++){ + if( + str_data[i].type==C_POS || str_data[i].type==C_NAME || + str_data[i].type==C_USERFUNC || str_data[i].type == C_USERFUNC_POS + ){ + str_data[i].type=C_NOP; + str_data[i].backpatch=-1; + str_data[i].label=-1; + } + } + + while( syntax.curly_count != 0 || *p != end ) + { + if( *p == '\0' ) + disp_error_message("unexpected end of script",p); + // Special handling only label + tmpp=skip_space(skip_word(p)); + if(*tmpp==':' && !(!strncasecmp(p,"default:",8) && p + 7 == tmpp)){ + i=add_word(p); + set_label(i,script_pos,p); + if( parse_options&SCRIPT_USE_LABEL_DB ) + strdb_iput(scriptlabel_db, get_str(i), script_pos); + p=tmpp+1; + p=skip_space(p); + continue; + } + + // All other lumped + p=parse_line(p); + p=skip_space(p); + + parse_nextline(false, p); + } + + add_scriptc(C_NOP); + + // trim code to size + script_size = script_pos; + RECREATE(script_buf,unsigned char,script_pos); + + // default unknown references to variables + for(i=LABEL_START;i<str_num;i++){ + if(str_data[i].type==C_NOP){ + int j,next; + str_data[i].type=C_NAME; + str_data[i].label=i; + for(j=str_data[i].backpatch;j>=0 && j!=0x00ffffff;){ + next=GETVALUE(script_buf,j); + SETVALUE(script_buf,j,i); + j=next; + } + } + else if( str_data[i].type == C_USERFUNC ) + {// 'function name;' without follow-up code + ShowError("parse_script: function '%s' declared but not defined.\n", str_buf+str_data[i].str); + unresolved_names = true; + } + } + + if( unresolved_names ) + { + disp_error_message("parse_script: unresolved function references", p); + } + +#ifdef DEBUG_DISP + for(i=0;i<script_pos;i++){ + if((i&15)==0) ShowMessage("%04x : ",i); + ShowMessage("%02x ",script_buf[i]); + if((i&15)==15) ShowMessage("\n"); + } + ShowMessage("\n"); +#endif +#ifdef DEBUG_DISASM + { + int i = 0,j; + while(i < script_pos) { + c_op op = get_com(script_buf,&i); + + ShowMessage("%06x %s", i, script_op2name(op)); + j = i; + switch(op) { + case C_INT: + ShowMessage(" %d", get_num(script_buf,&i)); + break; + case C_POS: + ShowMessage(" 0x%06x", *(int*)(script_buf+i)&0xffffff); + i += 3; + break; + case C_NAME: + j = (*(int*)(script_buf+i)&0xffffff); + ShowMessage(" %s", ( j == 0xffffff ) ? "?? unknown ??" : get_str(j)); + i += 3; + break; + case C_STR: + j = strlen(script_buf + i); + ShowMessage(" %s", script_buf + i); + i += j+1; + break; + } + ShowMessage(CL_CLL"\n"); + } + } +#endif + + CREATE(code,struct script_code,1); + code->script_buf = script_buf; + code->script_size = script_size; + code->script_vars = idb_alloc(DB_OPT_RELEASE_DATA); + return code; +} + +/// Returns the player attached to this script, identified by the rid. +/// If there is no player attached, the script is terminated. +TBL_PC *script_rid2sd(struct script_state *st) +{ + TBL_PC *sd=map_id2sd(st->rid); + if(!sd){ + ShowError("script_rid2sd: fatal error ! player not attached!\n"); + script_reportfunc(st); + script_reportsrc(st); + st->state = END; + } + return sd; +} + +/// Dereferences a variable/constant, replacing it with a copy of the value. +/// +/// @param st Script state +/// @param data Variable/constant +void get_val(struct script_state* st, struct script_data* data) +{ + const char* name; + char prefix; + char postfix; + TBL_PC* sd = NULL; + + if( !data_isreference(data) ) + return;// not a variable/constant + + name = reference_getname(data); + prefix = name[0]; + postfix = name[strlen(name) - 1]; + + //##TODO use reference_tovariable(data) when it's confirmed that it works [FlavioJS] + if( !reference_toconstant(data) && not_server_variable(prefix) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + {// needs player attached + if( postfix == '$' ) + {// string variable + ShowWarning("script:get_val: cannot access player variable '%s', defaulting to \"\"\n", name); + data->type = C_CONSTSTR; + data->u.str = ""; + } + else + {// integer variable + ShowWarning("script:get_val: cannot access player variable '%s', defaulting to 0\n", name); + data->type = C_INT; + data->u.num = 0; + } + return; + } + } + + if( postfix == '$' ) + {// string variable + + switch( prefix ) + { + case '@': + data->u.str = pc_readregstr(sd, data->u.num); + break; + case '$': + data->u.str = mapreg_readregstr(data->u.num); + break; + case '#': + if( name[1] == '#' ) + data->u.str = pc_readaccountreg2str(sd, name);// global + else + data->u.str = pc_readaccountregstr(sd, name);// local + break; + case '.': + { + struct DBMap* n = + data->ref ? *data->ref: + name[1] == '@' ? st->stack->var_function:// instance/scope variable + st->script->script_vars;// npc variable + if( n ) + data->u.str = (char*)idb_get(n,reference_getuid(data)); + else + data->u.str = NULL; + } + break; + case '\'': + if (st->instance_id) { + data->u.str = (char*)idb_get(instance[st->instance_id].vars,reference_getuid(data)); + } else { + ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to \"\"\n", name); + data->u.str = NULL; + } + break; + default: + data->u.str = pc_readglobalreg_str(sd, name); + break; + } + + if( data->u.str == NULL || data->u.str[0] == '\0' ) + {// empty string + data->type = C_CONSTSTR; + data->u.str = ""; + } + else + {// duplicate string + data->type = C_STR; + data->u.str = aStrdup(data->u.str); + } + + } + else + {// integer variable + + data->type = C_INT; + + if( reference_toconstant(data) ) + { + data->u.num = reference_getconstant(data); + } + else if( reference_toparam(data) ) + { + data->u.num = pc_readparam(sd, reference_getparamtype(data)); + } + else + switch( prefix ) + { + case '@': + data->u.num = pc_readreg(sd, data->u.num); + break; + case '$': + data->u.num = mapreg_readreg(data->u.num); + break; + case '#': + if( name[1] == '#' ) + data->u.num = pc_readaccountreg2(sd, name);// global + else + data->u.num = pc_readaccountreg(sd, name);// local + break; + case '.': + { + struct DBMap* n = + data->ref ? *data->ref: + name[1] == '@' ? st->stack->var_function:// instance/scope variable + st->script->script_vars;// npc variable + if( n ) + data->u.num = (int)idb_iget(n,reference_getuid(data)); + else + data->u.num = 0; + } + break; + case '\'': + if( st->instance_id ) + data->u.num = (int)idb_iget(instance[st->instance_id].vars,reference_getuid(data)); + else { + ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to 0\n", name); + data->u.num = 0; + } + break; + default: + data->u.num = pc_readglobalreg(sd, name); + break; + } + + } + + return; +} + +struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref); + +/// Retrieves the value of a reference identified by uid (variable, constant, param) +/// The value is left in the top of the stack and needs to be removed manually. +void* get_val2(struct script_state* st, int uid, struct DBMap** ref) +{ + struct script_data* data; + push_val2(st->stack, C_NAME, uid, ref); + data = script_getdatatop(st, -1); + get_val(st, data); + return (data->type == C_INT ? (void*)__64BPRTSIZE(data->u.num) : (void*)__64BPRTSIZE(data->u.str)); +} + +/*========================================== + * Stores the value of a script variable + * Return value is 0 on fail, 1 on success. + *------------------------------------------*/ +static int set_reg(struct script_state* st, TBL_PC* sd, int num, const char* name, const void* value, struct DBMap** ref) +{ + char prefix = name[0]; + + if( is_string_variable(name) ) + {// string variable + const char* str = (const char*)value; + switch (prefix) { + case '@': + return pc_setregstr(sd, num, str); + case '$': + return mapreg_setregstr(num, str); + case '#': + return (name[1] == '#') ? + pc_setaccountreg2str(sd, name, str) : + pc_setaccountregstr(sd, name, str); + case '.': + { + struct DBMap* n; + n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; + if( n ) { + idb_remove(n, num); + if (str[0]) idb_put(n, num, aStrdup(str)); + } + } + return 1; + case '\'': + if( st->instance_id ) { + idb_remove(instance[st->instance_id].vars, num); + if( str[0] ) idb_put(instance[st->instance_id].vars, num, aStrdup(str)); + } + return 1; + default: + return pc_setglobalreg_str(sd, name, str); + } + } + else + {// integer variable + int val = (int)__64BPRTSIZE(value); + if(str_data[num&0x00ffffff].type == C_PARAM) + { + if( pc_setparam(sd, str_data[num&0x00ffffff].val, val) == 0 ) + { + if( st != NULL ) + { + ShowError("script:set_reg: failed to set param '%s' to %d.\n", name, val); + script_reportsrc(st); + st->state = END; + } + return 0; + } + return 1; + } + + switch (prefix) { + case '@': + return pc_setreg(sd, num, val); + case '$': + return mapreg_setreg(num, val); + case '#': + return (name[1] == '#') ? + pc_setaccountreg2(sd, name, val) : + pc_setaccountreg(sd, name, val); + case '.': + { + struct DBMap* n; + n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; + if( n ) { + idb_remove(n, num); + if( val != 0 ) + idb_iput(n, num, val); + } + } + return 1; + case '\'': + if( st->instance_id ) { + idb_remove(instance[st->instance_id].vars, num); + if( val != 0 ) + idb_iput(instance[st->instance_id].vars, num, val); + } + return 1; + default: + return pc_setglobalreg(sd, name, val); + } + } +} + +int set_var(TBL_PC* sd, char* name, void* val) +{ + return set_reg(NULL, sd, reference_uid(add_str(name),0), name, val, NULL); +} + +void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref) +{ + set_reg(st, sd, reference_uid(add_str(varname),elem), varname, value, ref); +} + +/// Converts the data to a string +const char* conv_str(struct script_state* st, struct script_data* data) +{ + char* p; + + get_val(st, data); + if( data_isstring(data) ) + {// nothing to convert + } + else if( data_isint(data) ) + {// int -> string + CREATE(p, char, ITEM_NAME_LENGTH); + snprintf(p, ITEM_NAME_LENGTH, "%d", data->u.num); + p[ITEM_NAME_LENGTH-1] = '\0'; + data->type = C_STR; + data->u.str = p; + } + else if( data_isreference(data) ) + {// reference -> string + //##TODO when does this happen (check get_val) [FlavioJS] + data->type = C_CONSTSTR; + data->u.str = reference_getname(data); + } + else + {// unsupported data type + ShowError("script:conv_str: cannot convert to string, defaulting to \"\"\n"); + script_reportdata(data); + script_reportsrc(st); + data->type = C_CONSTSTR; + data->u.str = ""; + } + return data->u.str; +} + +/// Converts the data to an int +int conv_num(struct script_state* st, struct script_data* data) +{ + char* p; + long num; + + get_val(st, data); + if( data_isint(data) ) + {// nothing to convert + } + else if( data_isstring(data) ) + {// string -> int + // the result does not overflow or underflow, it is capped instead + // ex: 999999999999 is capped to INT_MAX (2147483647) + p = data->u.str; + errno = 0; + num = strtol(data->u.str, NULL, 10);// change radix to 0 to support octal numbers "o377" and hex numbers "0xFF" + if( errno == ERANGE +#if LONG_MAX > INT_MAX + || num < INT_MIN || num > INT_MAX +#endif + ) + { + if( num <= INT_MIN ) + { + num = INT_MIN; + ShowError("script:conv_num: underflow detected, capping to %ld\n", num); + } + else//if( num >= INT_MAX ) + { + num = INT_MAX; + ShowError("script:conv_num: overflow detected, capping to %ld\n", num); + } + script_reportdata(data); + script_reportsrc(st); + } + if( data->type == C_STR ) + aFree(p); + data->type = C_INT; + data->u.num = (int)num; + } +#if 0 + // FIXME this function is being used to retrieve the position of labels and + // probably other stuff [FlavioJS] + else + {// unsupported data type + ShowError("script:conv_num: cannot convert to number, defaulting to 0\n"); + script_reportdata(data); + script_reportsrc(st); + data->type = C_INT; + data->u.num = 0; + } +#endif + return data->u.num; +} + +// +// Stack operations +// + +/// Increases the size of the stack +void stack_expand(struct script_stack* stack) +{ + stack->sp_max += 64; + stack->stack_data = (struct script_data*)aRealloc(stack->stack_data, + stack->sp_max * sizeof(stack->stack_data[0]) ); + memset(stack->stack_data + (stack->sp_max - 64), 0, + 64 * sizeof(stack->stack_data[0]) ); +} + +/// Pushes a value into the stack +#define push_val(stack,type,val) push_val2(stack, type, val, NULL) + +/// Pushes a value into the stack (with reference) +struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = type; + stack->stack_data[stack->sp].u.num = val; + stack->stack_data[stack->sp].ref = ref; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a string into the stack +struct script_data* push_str(struct script_stack* stack, enum c_op type, char* str) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = type; + stack->stack_data[stack->sp].u.str = str; + stack->stack_data[stack->sp].ref = NULL; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a retinfo into the stack +struct script_data* push_retinfo(struct script_stack* stack, struct script_retinfo* ri, DBMap **ref) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = C_RETINFO; + stack->stack_data[stack->sp].u.ri = ri; + stack->stack_data[stack->sp].ref = ref; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a copy of the target position into the stack +struct script_data* push_copy(struct script_stack* stack, int pos) +{ + switch( stack->stack_data[pos].type ) + { + case C_CONSTSTR: + return push_str(stack, C_CONSTSTR, stack->stack_data[pos].u.str); + break; + case C_STR: + return push_str(stack, C_STR, aStrdup(stack->stack_data[pos].u.str)); + break; + case C_RETINFO: + ShowFatalError("script:push_copy: can't create copies of C_RETINFO. Exiting...\n"); + exit(1); + break; + default: + return push_val2( + stack,stack->stack_data[pos].type, + stack->stack_data[pos].u.num, + stack->stack_data[pos].ref + ); + break; + } +} + +/// Removes the values in indexes [start,end[ from the stack. +/// Adjusts all stack pointers. +void pop_stack(struct script_state* st, int start, int end) +{ + struct script_stack* stack = st->stack; + struct script_data* data; + int i; + + if( start < 0 ) + start = 0; + if( end > stack->sp ) + end = stack->sp; + if( start >= end ) + return;// nothing to pop + + // free stack elements + for( i = start; i < end; i++ ) + { + data = &stack->stack_data[i]; + if( data->type == C_STR ) + aFree(data->u.str); + if( data->type == C_RETINFO ) + { + struct script_retinfo* ri = data->u.ri; + if( ri->var_function ) + script_free_vars(ri->var_function); + if( data->ref ) + aFree(data->ref); + aFree(ri); + } + data->type = C_NOP; + } + // move the rest of the elements + if( stack->sp > end ) + { + memmove(&stack->stack_data[start], &stack->stack_data[end], sizeof(stack->stack_data[0])*(stack->sp - end)); + for( i = start + stack->sp - end; i < stack->sp; ++i ) + stack->stack_data[i].type = C_NOP; + } + // adjust stack pointers + if( st->start > end ) st->start -= end - start; + else if( st->start > start ) st->start = start; + if( st->end > end ) st->end -= end - start; + else if( st->end > start ) st->end = start; + if( stack->defsp > end ) stack->defsp -= end - start; + else if( stack->defsp > start ) stack->defsp = start; + stack->sp -= end - start; +} + +/// +/// +/// + +/*========================================== + * Release script dependent variable, dependent variable of function + *------------------------------------------*/ +void script_free_vars(struct DBMap* storage) +{ + if( storage ) + {// destroy the storage construct containing the variables + db_destroy(storage); + } +} + +void script_free_code(struct script_code* code) +{ + script_free_vars( code->script_vars ); + aFree( code->script_buf ); + aFree( code ); +} + +/// Creates a new script state. +/// +/// @param script Script code +/// @param pos Position in the code +/// @param rid Who is running the script (attached player) +/// @param oid Where the code is being run (npc 'object') +/// @return Script state +struct script_state* script_alloc_state(struct script_code* script, int pos, int rid, int oid) +{ + struct script_state* st; + CREATE(st, struct script_state, 1); + st->stack = (struct script_stack*)aMalloc(sizeof(struct script_stack)); + st->stack->sp = 0; + st->stack->sp_max = 64; + CREATE(st->stack->stack_data, struct script_data, st->stack->sp_max); + st->stack->defsp = st->stack->sp; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + st->state = RUN; + st->script = script; + //st->scriptroot = script; + st->pos = pos; + st->rid = rid; + st->oid = oid; + st->sleep.timer = INVALID_TIMER; + return st; +} + +/// Frees a script state. +/// +/// @param st Script state +void script_free_state(struct script_state* st) +{ + if(st->bk_st) + {// backup was not restored + ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + } + if( st->sleep.timer != INVALID_TIMER ) + delete_timer(st->sleep.timer, run_script_timer); + script_free_vars(st->stack->var_function); + pop_stack(st, 0, st->stack->sp); + aFree(st->stack->stack_data); + aFree(st->stack); + st->stack = NULL; + st->pos = -1; + aFree(st); +} + +// +// Main execution unit +// +/*========================================== + * Read command + *------------------------------------------*/ +c_op get_com(unsigned char *script,int *pos) +{ + int i = 0, j = 0; + + if(script[*pos]>=0x80){ + return C_INT; + } + while(script[*pos]>=0x40){ + i=script[(*pos)++]<<j; + j+=6; + } + return (c_op)(i+(script[(*pos)++]<<j)); +} + +/*========================================== + * Income figures + *------------------------------------------*/ +int get_num(unsigned char *script,int *pos) +{ + int i,j; + i=0; j=0; + while(script[*pos]>=0xc0){ + i+=(script[(*pos)++]&0x7f)<<j; + j+=6; + } + return i+((script[(*pos)++]&0x7f)<<j); +} + +/*========================================== + * Remove the value from the stack + *------------------------------------------*/ +int pop_val(struct script_state* st) +{ + if(st->stack->sp<=0) + return 0; + st->stack->sp--; + get_val(st,&(st->stack->stack_data[st->stack->sp])); + if(st->stack->stack_data[st->stack->sp].type==C_INT) + return st->stack->stack_data[st->stack->sp].u.num; + return 0; +} + +/// Ternary operators +/// test ? if_true : if_false +void op_3(struct script_state* st, int op) +{ + struct script_data* data; + int flag = 0; + + data = script_getdatatop(st, -3); + get_val(st, data); + + if( data_isstring(data) ) + flag = data->u.str[0];// "" -> false + else if( data_isint(data) ) + flag = data->u.num;// 0 -> false + else + { + ShowError("script:op_3: invalid data for the ternary operator test\n"); + script_reportdata(data); + script_reportsrc(st); + script_removetop(st, -3, 0); + script_pushnil(st); + return; + } + if( flag ) + script_pushcopytop(st, -2); + else + script_pushcopytop(st, -1); + script_removetop(st, -4, -1); +} + +/// Binary string operators +/// s1 EQ s2 -> i +/// s1 NE s2 -> i +/// s1 GT s2 -> i +/// s1 GE s2 -> i +/// s1 LT s2 -> i +/// s1 LE s2 -> i +/// s1 ADD s2 -> s +void op_2str(struct script_state* st, int op, const char* s1, const char* s2) +{ + int a = 0; + + switch(op){ + case C_EQ: a = (strcmp(s1,s2) == 0); break; + case C_NE: a = (strcmp(s1,s2) != 0); break; + case C_GT: a = (strcmp(s1,s2) > 0); break; + case C_GE: a = (strcmp(s1,s2) >= 0); break; + case C_LT: a = (strcmp(s1,s2) < 0); break; + case C_LE: a = (strcmp(s1,s2) <= 0); break; + case C_ADD: + { + char* buf = (char *)aMalloc((strlen(s1)+strlen(s2)+1)*sizeof(char)); + strcpy(buf, s1); + strcat(buf, s2); + script_pushstr(st, buf); + return; + } + default: + ShowError("script:op2_str: unexpected string operator %s\n", script_op2name(op)); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + + script_pushint(st,a); +} + +/// Binary number operators +/// i OP i -> i +void op_2num(struct script_state* st, int op, int i1, int i2) +{ + int ret; + double ret_double; + + switch( op ) + { + case C_AND: ret = i1 & i2; break; + case C_OR: ret = i1 | i2; break; + case C_XOR: ret = i1 ^ i2; break; + case C_LAND: ret = (i1 && i2); break; + case C_LOR: ret = (i1 || i2); break; + case C_EQ: ret = (i1 == i2); break; + case C_NE: ret = (i1 != i2); break; + case C_GT: ret = (i1 > i2); break; + case C_GE: ret = (i1 >= i2); break; + case C_LT: ret = (i1 < i2); break; + case C_LE: ret = (i1 <= i2); break; + case C_R_SHIFT: ret = i1>>i2; break; + case C_L_SHIFT: ret = i1<<i2; break; + case C_DIV: + case C_MOD: + if( i2 == 0 ) + { + ShowError("script:op_2num: division by zero detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + else if( op == C_DIV ) + ret = i1 / i2; + else//if( op == C_MOD ) + ret = i1 % i2; + break; + default: + switch( op ) + {// operators that can overflow/underflow + case C_ADD: ret = i1 + i2; ret_double = (double)i1 + (double)i2; break; + case C_SUB: ret = i1 - i2; ret_double = (double)i1 - (double)i2; break; + case C_MUL: ret = i1 * i2; ret_double = (double)i1 * (double)i2; break; + default: + ShowError("script:op_2num: unexpected number operator %s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + script_pushnil(st); + return; + } + if( ret_double < (double)INT_MIN ) + { + ShowWarning("script:op_2num: underflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + ret = INT_MIN; + } + else if( ret_double > (double)INT_MAX ) + { + ShowWarning("script:op_2num: overflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + ret = INT_MAX; + } + } + script_pushint(st, ret); +} + +/// Binary operators +void op_2(struct script_state *st, int op) +{ + struct script_data* left, leftref; + struct script_data* right; + + leftref.type = C_NOP; + + left = script_getdatatop(st, -2); + right = script_getdatatop(st, -1); + + if (st->op2ref) { + if (data_isreference(left)) { + leftref = *left; + } + + st->op2ref = 0; + } + + get_val(st, left); + get_val(st, right); + + // automatic conversions + switch( op ) + { + case C_ADD: + if( data_isint(left) && data_isstring(right) ) + {// convert int-string to string-string + conv_str(st, left); + } + else if( data_isstring(left) && data_isint(right) ) + {// convert string-int to string-string + conv_str(st, right); + } + break; + } + + if( data_isstring(left) && data_isstring(right) ) + {// ss => op_2str + op_2str(st, op, left->u.str, right->u.str); + script_removetop(st, leftref.type == C_NOP ? -3 : -2, -1);// pop the two values before the top one + + if (leftref.type != C_NOP) + { + aFree(left->u.str); + *left = leftref; + } + } + else if( data_isint(left) && data_isint(right) ) + {// ii => op_2num + int i1 = left->u.num; + int i2 = right->u.num; + + script_removetop(st, leftref.type == C_NOP ? -2 : -1, 0); + op_2num(st, op, i1, i2); + + if (leftref.type != C_NOP) + *left = leftref; + } + else + {// invalid argument + ShowError("script:op_2: invalid data for operator %s\n", script_op2name(op)); + script_reportdata(left); + script_reportdata(right); + script_reportsrc(st); + script_removetop(st, -2, 0); + script_pushnil(st); + st->state = END; + } +} + +/// Unary operators +/// NEG i -> i +/// NOT i -> i +/// LNOT i -> i +void op_1(struct script_state* st, int op) +{ + struct script_data* data; + int i1; + + data = script_getdatatop(st, -1); + get_val(st, data); + + if( !data_isint(data) ) + {// not a number + ShowError("script:op_1: argument is not a number (op=%s)\n", script_op2name(op)); + script_reportdata(data); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + + i1 = data->u.num; + script_removetop(st, -1, 0); + switch( op ) + { + case C_NEG: i1 = -i1; break; + case C_NOT: i1 = ~i1; break; + case C_LNOT: i1 = !i1; break; + default: + ShowError("script:op_1: unexpected operator %s i1=%d\n", script_op2name(op), i1); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + script_pushint(st, i1); +} + + +/// Checks the type of all arguments passed to a built-in function. +/// +/// @param st Script state whose stack arguments should be inspected. +/// @param func Built-in function for which the arguments are intended. +static void script_check_buildin_argtype(struct script_state* st, int func) +{ + char type; + int idx, invalid = 0; + script_function* sf = &buildin_func[str_data[func].val]; + + for( idx = 2; script_hasdata(st, idx); idx++ ) + { + struct script_data* data = script_getdata(st, idx); + + type = sf->arg[idx-2]; + + if( type == '?' || type == '*' ) + {// optional argument or unknown number of optional parameters ( no types are after this ) + break; + } + else if( type == 0 ) + {// more arguments than necessary ( should not happen, as it is checked before ) + ShowWarning("Found more arguments than necessary. unexpected arg type %s\n",script_op2name(data->type)); + invalid++; + break; + } + else + { + const char* name = NULL; + + if( data_isreference(data) ) + {// get name for variables to determine the type they refer to + name = reference_getname(data); + } + + switch( type ) + { + case 'v': + if( !data_isstring(data) && !data_isint(data) && !data_isreference(data) ) + {// variant + ShowWarning("Unexpected type for argument %d. Expected string, number or variable.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 's': + if( !data_isstring(data) && !( data_isreference(data) && is_string_variable(name) ) ) + {// string + ShowWarning("Unexpected type for argument %d. Expected string.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 'i': + if( !data_isint(data) && !( data_isreference(data) && ( reference_toparam(data) || reference_toconstant(data) || !is_string_variable(name) ) ) ) + {// int ( params and constants are always int ) + ShowWarning("Unexpected type for argument %d. Expected number.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 'r': + if( !data_isreference(data) ) + {// variables + ShowWarning("Unexpected type for argument %d. Expected variable, got %s.\n", idx-1,script_op2name(data->type)); + script_reportdata(data); + invalid++; + } + break; + case 'l': + if( !data_islabel(data) && !data_isfunclabel(data) ) + {// label + ShowWarning("Unexpected type for argument %d. Expected label, got %s\n", idx-1,script_op2name(data->type)); + script_reportdata(data); + invalid++; + } + break; + } + } + } + + if(invalid) + { + ShowDebug("Function: %s\n", get_str(func)); + script_reportsrc(st); + } +} + + +/// Executes a buildin command. +/// Stack: C_NAME(<command>) C_ARG <arg0> <arg1> ... <argN> +int run_func(struct script_state *st) +{ + struct script_data* data; + int i,start_sp,end_sp,func; + + end_sp = st->stack->sp;// position after the last argument + for( i = end_sp-1; i > 0 ; --i ) + if( st->stack->stack_data[i].type == C_ARG ) + break; + if( i == 0 ) + { + ShowError("script:run_func: C_ARG not found. please report this!!!\n"); + st->state = END; + script_reportsrc(st); + return 1; + } + start_sp = i-1;// C_NAME of the command + st->start = start_sp; + st->end = end_sp; + + data = &st->stack->stack_data[st->start]; + if( data->type == C_NAME && str_data[data->u.num].type == C_FUNC ) + func = data->u.num; + else + { + ShowError("script:run_func: not a buildin command.\n"); + script_reportdata(data); + script_reportsrc(st); + st->state = END; + return 1; + } + + if( script_config.warn_func_mismatch_argtypes ) + { + script_check_buildin_argtype(st, func); + } + + if(str_data[func].func){ + if (str_data[func].func(st)) //Report error + script_reportsrc(st); + } else { + ShowError("script:run_func: '%s' (id=%d type=%s) has no C function. please report this!!!\n", get_str(func), func, script_op2name(str_data[func].type)); + script_reportsrc(st); + st->state = END; + } + + // Stack's datum are used when re-running functions [Eoe] + if( st->state == RERUNLINE ) + return 0; + + pop_stack(st, st->start, st->end); + if( st->state == RETFUNC ) + {// return from a user-defined function + struct script_retinfo* ri; + int olddefsp = st->stack->defsp; + int nargs; + + pop_stack(st, st->stack->defsp, st->start);// pop distractions from the stack + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp-1].type != C_RETINFO ) + { + ShowWarning("script:run_func: return without callfunc or callsub!\n"); + script_reportsrc(st); + st->state = END; + return 1; + } + script_free_vars( st->stack->var_function ); + + ri = st->stack->stack_data[st->stack->defsp-1].u.ri; + nargs = ri->nargs; + st->pos = ri->pos; + st->script = ri->script; + st->stack->var_function = ri->var_function; + st->stack->defsp = ri->defsp; + memset(ri, 0, sizeof(struct script_retinfo)); + + pop_stack(st, olddefsp-nargs-1, olddefsp);// pop arguments and retinfo + + st->state = GOTO; + } + + return 0; +} + +/*========================================== + * script execution + *------------------------------------------*/ +void run_script(struct script_code *rootscript,int pos,int rid,int oid) +{ + struct script_state *st; + + if( rootscript == NULL || pos < 0 ) + return; + + // TODO In jAthena, this function can take over the pending script in the player. [FlavioJS] + // It is unclear how that can be triggered, so it needs the be traced/checked in more detail. + // NOTE At the time of this change, this function wasn't capable of taking over the script state because st->scriptroot was never set. + st = script_alloc_state(rootscript, pos, rid, oid); + run_script_main(st); +} + +void script_stop_sleeptimers(int id) +{ + struct script_state* st; + for(;;) + { + st = (struct script_state*)linkdb_erase(&sleep_db,(void*)__64BPRTSIZE(id)); + if( st == NULL ) + break; // no more sleep timers + script_free_state(st); + } +} + +/*========================================== + * Delete the specified node from sleep_db + *------------------------------------------*/ +struct linkdb_node* script_erase_sleepdb(struct linkdb_node *n) +{ + struct linkdb_node *retnode; + + if( n == NULL) + return NULL; + if( n->prev == NULL ) + sleep_db = n->next; + else + n->prev->next = n->next; + if( n->next ) + n->next->prev = n->prev; + retnode = n->next; + aFree( n ); + return retnode; // The following; return retnode +} + +/*========================================== + * Timer function for sleep + *------------------------------------------*/ +int run_script_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct script_state *st = (struct script_state *)data; + struct linkdb_node *node = (struct linkdb_node *)sleep_db; + TBL_PC *sd = map_id2sd(st->rid); + + if((sd && sd->status.char_id != id) || (st->rid && !sd)) + { //Character mismatch. Cancel execution. + st->rid = 0; + st->state = END; + } + while( node && st->sleep.timer != INVALID_TIMER ) { + if( (int)__64BPRTSIZE(node->key) == st->oid && ((struct script_state *)node->data)->sleep.timer == st->sleep.timer ) { + script_erase_sleepdb(node); + st->sleep.timer = INVALID_TIMER; + break; + } + node = node->next; + } + if(st->state != RERUNLINE) + st->sleep.tick = 0; + run_script_main(st); + return 0; +} + +/// Detaches script state from possibly attached character and restores it's previous script if any. +/// +/// @param st Script state to detach. +/// @param dequeue_event Whether to schedule any queued events, when there was no previous script. +static void script_detach_state(struct script_state* st, bool dequeue_event) +{ + struct map_session_data* sd; + + if(st->rid && (sd = map_id2sd(st->rid))!=NULL) + { + sd->st = st->bk_st; + sd->npc_id = st->bk_npcid; + /** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ + #if SECURE_NPCTIMEOUT + /** + * We're done with this NPC session, so we cancel the timer (if existent) and move on + **/ + if( sd->npc_idle_timer != INVALID_TIMER ) { + delete_timer(sd->npc_idle_timer,npc_rr_secure_timeout_timer); + sd->npc_idle_timer = INVALID_TIMER; + } + #endif + if(st->bk_st) + { + //Remove tag for removal. + st->bk_st = NULL; + st->bk_npcid = 0; + } + else if(dequeue_event) + { + npc_event_dequeue(sd); + } + } + else if(st->bk_st) + {// rid was set to 0, before detaching the script state + ShowError("script_detach_state: Found previous script state without attached player (rid=%d, oid=%d, state=%d, bk_npcid=%d)\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + script_reportsrc(st->bk_st); + + script_free_state(st->bk_st); + st->bk_st = NULL; + } +} + +/// Attaches script state to possibly attached character and backups it's previous script, if any. +/// +/// @param st Script state to attach. +static void script_attach_state(struct script_state* st) +{ + struct map_session_data* sd; + + if(st->rid && (sd = map_id2sd(st->rid))!=NULL) + { + if(st!=sd->st) + { + if(st->bk_st) + {// there is already a backup + ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + } + st->bk_st = sd->st; + st->bk_npcid = sd->npc_id; + } + sd->st = st; + sd->npc_id = st->oid; +/** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + if( sd->npc_idle_timer == INVALID_TIMER ) + sd->npc_idle_timer = add_timer(gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc_rr_secure_timeout_timer,sd->bl.id,0); + sd->npc_idle_tick = gettick(); +#endif + } +} + +/*========================================== + * The main part of the script execution + *------------------------------------------*/ +void run_script_main(struct script_state *st) +{ + int cmdcount = script_config.check_cmdcount; + int gotocount = script_config.check_gotocount; + TBL_PC *sd; + struct script_stack *stack=st->stack; + struct npc_data *nd; + + script_attach_state(st); + + nd = map_id2nd(st->oid); + if( nd && map[nd->bl.m].instance_id > 0 ) + st->instance_id = map[nd->bl.m].instance_id; + + if(st->state == RERUNLINE) { + run_func(st); + if(st->state == GOTO) + st->state = RUN; + } else if(st->state != END) + st->state = RUN; + + while(st->state == RUN) + { + enum c_op c = get_com(st->script->script_buf,&st->pos); + switch(c){ + case C_EOL: + if( stack->defsp > stack->sp ) + ShowError("script:run_script_main: unexpected stack position (defsp=%d sp=%d). please report this!!!\n", stack->defsp, stack->sp); + else + pop_stack(st, stack->defsp, stack->sp);// pop unused stack data. (unused return value) + break; + case C_INT: + push_val(stack,C_INT,get_num(st->script->script_buf,&st->pos)); + break; + case C_POS: + case C_NAME: + push_val(stack,c,GETVALUE(st->script->script_buf,st->pos)); + st->pos+=3; + break; + case C_ARG: + push_val(stack,c,0); + break; + case C_STR: + push_str(stack,C_CONSTSTR,(char*)(st->script->script_buf+st->pos)); + while(st->script->script_buf[st->pos++]); + break; + case C_FUNC: + run_func(st); + if(st->state==GOTO){ + st->state = RUN; + if( !st->freeloop && gotocount>0 && (--gotocount)<=0 ){ + ShowError("run_script: infinity loop !\n"); + script_reportsrc(st); + st->state=END; + } + } + break; + + case C_REF: + st->op2ref = 1; + break; + + case C_NEG: + case C_NOT: + case C_LNOT: + op_1(st ,c); + break; + + case C_ADD: + case C_SUB: + case C_MUL: + case C_DIV: + case C_MOD: + case C_EQ: + case C_NE: + case C_GT: + case C_GE: + case C_LT: + case C_LE: + case C_AND: + case C_OR: + case C_XOR: + case C_LAND: + case C_LOR: + case C_R_SHIFT: + case C_L_SHIFT: + op_2(st, c); + break; + + case C_OP3: + op_3(st, c); + break; + + case C_NOP: + st->state=END; + break; + + default: + ShowError("unknown command : %d @ %d\n",c,st->pos); + st->state=END; + break; + } + if( !st->freeloop && cmdcount>0 && (--cmdcount)<=0 ){ + ShowError("run_script: infinity loop !\n"); + script_reportsrc(st); + st->state=END; + } + } + + if(st->sleep.tick > 0) { + //Restore previous script + script_detach_state(st, false); + //Delay execution + sd = map_id2sd(st->rid); // Get sd since script might have attached someone while running. [Inkfish] + st->sleep.charid = sd?sd->status.char_id:0; + st->sleep.timer = add_timer(gettick()+st->sleep.tick, + run_script_timer, st->sleep.charid, (intptr_t)st); + linkdb_insert(&sleep_db, (void*)__64BPRTSIZE(st->oid), st); + } + else if(st->state != END && st->rid){ + //Resume later (st is already attached to player). + if(st->bk_st) { + ShowWarning("Unable to restore stack! Double continuation!\n"); + //Report BOTH scripts to see if that can help somehow. + ShowDebug("Previous script (lost):\n"); + script_reportsrc(st->bk_st); + ShowDebug("Current script:\n"); + script_reportsrc(st); + + script_free_state(st->bk_st); + st->bk_st = NULL; + } + } else { + //Dispose of script. + if ((sd = map_id2sd(st->rid))!=NULL) + { //Restore previous stack and save char. + if(sd->state.using_fake_npc){ + clif_clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd); + sd->state.using_fake_npc = 0; + } + //Restore previous script if any. + script_detach_state(st, true); + if (sd->state.reg_dirty&2) + intif_saveregistry(sd,2); + if (sd->state.reg_dirty&1) + intif_saveregistry(sd,1); + } + script_free_state(st); + st = NULL; + } +} + +int script_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + ShowError("File not found: %s\n", cfgName); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"warn_func_mismatch_paramnum")==0) { + script_config.warn_func_mismatch_paramnum = config_switch(w2); + } + else if(strcmpi(w1,"check_cmdcount")==0) { + script_config.check_cmdcount = config_switch(w2); + } + else if(strcmpi(w1,"check_gotocount")==0) { + script_config.check_gotocount = config_switch(w2); + } + else if(strcmpi(w1,"input_min_value")==0) { + script_config.input_min_value = config_switch(w2); + } + else if(strcmpi(w1,"input_max_value")==0) { + script_config.input_max_value = config_switch(w2); + } + else if(strcmpi(w1,"warn_func_mismatch_argtypes")==0) { + script_config.warn_func_mismatch_argtypes = config_switch(w2); + } + else if(strcmpi(w1,"import")==0){ + script_config_read(w2); + } + else { + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + } + fclose(fp); + + return 0; +} + +/** + * @see DBApply + */ +static int db_script_free_code_sub(DBKey key, DBData *data, va_list ap) +{ + struct script_code *code = db_data2ptr(data); + if (code) + script_free_code(code); + return 0; +} + +void script_run_autobonus(const char *autobonus, int id, int pos) +{ + struct script_code *script = (struct script_code *)strdb_get(autobonus_db, autobonus); + + if( script ) + { + current_equip_item_index = pos; + run_script(script,0,id,0); + } +} + +void script_add_autobonus(const char *autobonus) +{ + if( strdb_get(autobonus_db, autobonus) == NULL ) + { + struct script_code *script = parse_script(autobonus, "autobonus", 0, 0); + + if( script ) + strdb_put(autobonus_db, autobonus, script); + } +} + + +/// resets a temporary character array variable to given value +void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value) +{ + int key; + uint8 idx; + + if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) + { + ShowError("script_cleararray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); + return; + } + + key = add_str(varname); + + if( is_string_variable(varname) ) + { + for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) + { + pc_setregstr(sd, reference_uid(key, idx), (const char*)value); + } + } + else + { + for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) + { + pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); + } + } +} + + +/// sets a temporary character array variable element idx to given value +/// @param refcache Pointer to an int variable, which keeps a copy of the reference to varname and must be initialized to 0. Can be NULL if only one element is set. +void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache) +{ + int key; + + if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) + { + ShowError("script_setarray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); + return; + } + + if( idx >= SCRIPT_MAX_ARRAYSIZE ) + { + ShowError("script_setarray_pc: Variable '%s' has invalid index '%d' (char_id=%d).\n", varname, (int)idx, sd->status.char_id); + return; + } + + key = ( refcache && refcache[0] ) ? refcache[0] : add_str(varname); + + if( is_string_variable(varname) ) + { + pc_setregstr(sd, reference_uid(key, idx), (const char*)value); + } + else + { + pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); + } + + if( refcache ) + {// save to avoid repeated add_str calls + refcache[0] = key; + } +} +#ifdef BETA_THREAD_TEST +int buildin_query_sql_sub(struct script_state* st, Sql* handle); + +/* used to receive items the queryThread has already processed */ +int queryThread_timer(int tid, unsigned int tick, int id, intptr_t data) { + int i, cursor = 0; + bool allOk = true; + + EnterSpinLock(&queryThreadLock); + + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + + if( !entry->ok ) { + allOk = false; + continue; + } + + run_script_main(entry->st); + + entry->st = NULL;/* empty entries */ + aFree(entry); + queryThreadData.entry[i] = NULL; + } + + + if( allOk ) { + /* cancel the repeating timer -- it'll re-create itself when necessary, dont need to remain looping */ + delete_timer(queryThreadData.timer, queryThread_timer); + queryThreadData.timer = INVALID_TIMER; + } + + /* now lets clear the mess. */ + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + if( entry == NULL ) + continue;/* entry on hold */ + + /* move */ + memmove(&queryThreadData.entry[cursor], &queryThreadData.entry[i], sizeof(struct queryThreadEntry*)); + + cursor++; + } + + queryThreadData.count = cursor; + + LeaveSpinLock(&queryThreadLock); + + return 0; +} + +void queryThread_add(struct script_state *st, bool type) { + int idx = 0; + struct queryThreadEntry* entry = NULL; + + EnterSpinLock(&queryThreadLock); + + if( queryThreadData.count++ != 0 ) + RECREATE(queryThreadData.entry, struct queryThreadEntry* , queryThreadData.count); + + idx = queryThreadData.count-1; + + CREATE(queryThreadData.entry[idx],struct queryThreadEntry,1); + + entry = queryThreadData.entry[idx]; + + entry->st = st; + entry->ok = false; + entry->type = type; + if( queryThreadData.timer == INVALID_TIMER ) { /* start the receiver timer */ + queryThreadData.timer = add_timer_interval(gettick() + 100, queryThread_timer, 0, 0, 100); + } + + LeaveSpinLock(&queryThreadLock); + + /* unlock the queryThread */ + racond_signal(queryThreadCond); +} +/* adds a new log to the queue */ +void queryThread_log(char * entry, int length) { + int idx = logThreadData.count; + + EnterSpinLock(&queryThreadLock); + + if( logThreadData.count++ != 0 ) + RECREATE(logThreadData.entry, char* , logThreadData.count); + + CREATE(logThreadData.entry[idx], char, length + 1 ); + safestrncpy(logThreadData.entry[idx], entry, length + 1 ); + + LeaveSpinLock(&queryThreadLock); + + /* unlock the queryThread */ + racond_signal(queryThreadCond); +} + +/* queryThread_main */ +static void *queryThread_main(void *x) { + Sql *queryThread_handle = Sql_Malloc(); + int i; + + if ( SQL_ERROR == Sql_Connect(queryThread_handle, map_server_id, map_server_pw, map_server_ip, map_server_port, map_server_db) ) + exit(EXIT_FAILURE); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(queryThread_handle, default_codepage) ) + Sql_ShowDebug(queryThread_handle); + + if( log_config.sql_logs ) { + logmysql_handle = Sql_Malloc(); + + if ( SQL_ERROR == Sql_Connect(logmysql_handle, log_db_id, log_db_pw, log_db_ip, log_db_port, log_db_db) ) + exit(EXIT_FAILURE); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(logmysql_handle, default_codepage) ) + Sql_ShowDebug(logmysql_handle); + } + + while( 1 ) { + + if(queryThreadTerminate > 0) + break; + + EnterSpinLock(&queryThreadLock); + + /* mess with queryThreadData within the lock */ + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + + if( entry->ok ) + continue; + else if ( !entry->st || !entry->st->stack ) { + entry->ok = true;/* dispose */ + continue; + } + + buildin_query_sql_sub(entry->st, entry->type ? logmysql_handle : queryThread_handle); + + entry->ok = true;/* we're done with this */ + } + + /* also check for any logs in need to be sent */ + if( log_config.sql_logs ) { + for( i = 0; i < logThreadData.count; i++ ) { + if( SQL_ERROR == Sql_Query(logmysql_handle, logThreadData.entry[i]) ) + Sql_ShowDebug(logmysql_handle); + aFree(logThreadData.entry[i]); + } + logThreadData.count = 0; + } + + LeaveSpinLock(&queryThreadLock); + + ramutex_lock( queryThreadMutex ); + racond_wait( queryThreadCond, queryThreadMutex, -1 ); + ramutex_unlock( queryThreadMutex ); + + } + + Sql_Free(queryThread_handle); + + if( log_config.sql_logs ) { + Sql_Free(logmysql_handle); + } + + return NULL; +} +#endif +/*========================================== + * Destructor + *------------------------------------------*/ +int do_final_script() { + int i; +#ifdef DEBUG_HASH + if (battle_config.etc_log) + { + FILE *fp = fopen("hash_dump.txt","wt"); + if(fp) { + int count[SCRIPT_HASH_SIZE]; + int count2[SCRIPT_HASH_SIZE]; // number of buckets with a certain number of items + int n=0; + int min=INT_MAX,max=0,zero=0; + double mean=0.0f; + double median=0.0f; + + ShowNotice("Dumping script str hash information to hash_dump.txt\n"); + memset(count, 0, sizeof(count)); + fprintf(fp,"num : hash : data_name\n"); + fprintf(fp,"---------------------------------------------------------------\n"); + for(i=LABEL_START; i<str_num; i++) { + unsigned int h = calc_hash(get_str(i)); + fprintf(fp,"%04d : %4u : %s\n",i,h, get_str(i)); + ++count[h]; + } + fprintf(fp,"--------------------\n\n"); + memset(count2, 0, sizeof(count2)); + for(i=0; i<SCRIPT_HASH_SIZE; i++) { + fprintf(fp," hash %3d = %d\n",i,count[i]); + if(min > count[i]) + min = count[i]; // minimun count of collision + if(max < count[i]) + max = count[i]; // maximun count of collision + if(count[i] == 0) + zero++; + ++count2[count[i]]; + } + fprintf(fp,"\n--------------------\n items : buckets\n--------------------\n"); + for( i=min; i <= max; ++i ){ + fprintf(fp," %5d : %7d\n",i,count2[i]); + mean += 1.0f*i*count2[i]/SCRIPT_HASH_SIZE; // Note: this will always result in <nr labels>/<nr buckets> + } + for( i=min; i <= max; ++i ){ + n += count2[i]; + if( n*2 >= SCRIPT_HASH_SIZE ) + { + if( SCRIPT_HASH_SIZE%2 == 0 && SCRIPT_HASH_SIZE/2 == n ) + median = (i+i+1)/2.0f; + else + median = i; + break; + } + } + fprintf(fp,"--------------------\n min = %d, max = %d, zero = %d\n mean = %lf, median = %lf\n",min,max,zero,mean,median); + fclose(fp); + } + } +#endif + + mapreg_final(); + + db_destroy(scriptlabel_db); + userfunc_db->destroy(userfunc_db, db_script_free_code_sub); + autobonus_db->destroy(autobonus_db, db_script_free_code_sub); + if(sleep_db) { + struct linkdb_node *n = (struct linkdb_node *)sleep_db; + while(n) { + struct script_state *st = (struct script_state *)n->data; + script_free_state(st); + n = n->next; + } + linkdb_final(&sleep_db); + } + + if (str_data) + aFree(str_data); + if (str_buf) + aFree(str_buf); + + for( i = 0; i < atcmd_binding_count; i++ ) { + aFree(atcmd_binding[i]); + } + + if( atcmd_binding_count != 0 ) + aFree(atcmd_binding); +#ifdef BETA_THREAD_TEST + /* QueryThread */ + InterlockedIncrement(&queryThreadTerminate); + racond_signal(queryThreadCond); + rathread_wait(queryThread, NULL); + + // Destroy cond var and mutex. + racond_destroy( queryThreadCond ); + ramutex_destroy( queryThreadMutex ); + + /* Clear missing vars */ + for( i = 0; i < queryThreadData.count; i++ ) { + aFree(queryThreadData.entry[i]); + } + + aFree(queryThreadData.entry); + + for( i = 0; i < logThreadData.count; i++ ) { + aFree(logThreadData.entry[i]); + } + + aFree(logThreadData.entry); +#endif + + return 0; +} +/*========================================== + * Initialization + *------------------------------------------*/ +int do_init_script() { + userfunc_db=strdb_alloc(DB_OPT_DUP_KEY,0); + scriptlabel_db=strdb_alloc(DB_OPT_DUP_KEY,50); + autobonus_db = strdb_alloc(DB_OPT_DUP_KEY,0); + + mapreg_init(); +#ifdef BETA_THREAD_TEST + CREATE(queryThreadData.entry, struct queryThreadEntry*, 1); + queryThreadData.count = 0; + CREATE(logThreadData.entry, char *, 1); + logThreadData.count = 0; + /* QueryThread Start */ + + InitializeSpinLock(&queryThreadLock); + + queryThreadData.timer = INVALID_TIMER; + queryThreadTerminate = 0; + queryThreadMutex = ramutex_create(); + queryThreadCond = racond_create(); + + queryThread = rathread_create(queryThread_main, NULL); + + if(queryThread == NULL){ + ShowFatalError("do_init_script: cannot spawn Query Thread.\n"); + exit(EXIT_FAILURE); + } + + add_timer_func_list(queryThread_timer, "queryThread_timer"); +#endif + return 0; +} + +int script_reload() { + int i; + +#ifdef BETA_THREAD_TEST + /* we're reloading so any queries undergoing should be...exterminated. */ + EnterSpinLock(&queryThreadLock); + + for( i = 0; i < queryThreadData.count; i++ ) { + aFree(queryThreadData.entry[i]); + } + queryThreadData.count = 0; + + if( queryThreadData.timer != INVALID_TIMER ) { + delete_timer(queryThreadData.timer, queryThread_timer); + queryThreadData.timer = INVALID_TIMER; + } + + LeaveSpinLock(&queryThreadLock); +#endif + + + userfunc_db->clear(userfunc_db, db_script_free_code_sub); + db_clear(scriptlabel_db); + + // @commands (script based) + // Clear bindings + for( i = 0; i < atcmd_binding_count; i++ ) { + aFree(atcmd_binding[i]); + } + + if( atcmd_binding_count != 0 ) + aFree(atcmd_binding); + + atcmd_binding_count = 0; + + if(sleep_db) { + struct linkdb_node *n = (struct linkdb_node *)sleep_db; + while(n) { + struct script_state *st = (struct script_state *)n->data; + script_free_state(st); + n = n->next; + } + linkdb_final(&sleep_db); + } + mapreg_reload(); + return 0; +} + +//----------------------------------------------------------------------------- +// buildin functions +// + +#define BUILDIN_DEF(x,args) { buildin_ ## x , #x , args } +#define BUILDIN_DEF2(x,x2,args) { buildin_ ## x , x2 , args } +#define BUILDIN_FUNC(x) int buildin_ ## x (struct script_state* st) + +///////////////////////////////////////////////////////////////////// +// NPC interaction +// + +/// Appends a message to the npc dialog. +/// If a dialog doesn't exist yet, one is created. +/// +/// mes "<message>"; +BUILDIN_FUNC(mes) +{ + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( !script_hasdata(st, 3) ) + {// only a single line detected in the script + clif_scriptmes(sd, st->oid, script_getstr(st, 2)); + } + else + {// parse multiple lines as they exist + int i; + + for( i = 2; script_hasdata(st, i); i++ ) + { + // send the message to the client + clif_scriptmes(sd, st->oid, script_getstr(st, i)); + } + } + + return 0; +} + +/// Displays the button 'next' in the npc dialog. +/// The dialog text is cleared and the script continues when the button is pressed. +/// +/// next; +BUILDIN_FUNC(next) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = STOP; + clif_scriptnext(sd, st->oid); + return 0; +} + +/// Ends the script and displays the button 'close' on the npc dialog. +/// The dialog is closed when the button is pressed. +/// +/// close; +BUILDIN_FUNC(close) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = END; + clif_scriptclose(sd, st->oid); + return 0; +} + +/// Displays the button 'close' on the npc dialog. +/// The dialog is closed and the script continues when the button is pressed. +/// +/// close2; +BUILDIN_FUNC(close2) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = STOP; + clif_scriptclose(sd, st->oid); + return 0; +} + +/// Counts the number of valid and total number of options in 'str' +/// If max_count > 0 the counting stops when that valid option is reached +/// total is incremented for each option (NULL is supported) +static int menu_countoptions(const char* str, int max_count, int* total) +{ + int count = 0; + int bogus_total; + + if( total == NULL ) + total = &bogus_total; + ++(*total); + + // initial empty options + while( *str == ':' ) + { + ++str; + ++(*total); + } + // count menu options + while( *str != '\0' ) + { + ++count; + --max_count; + if( max_count == 0 ) + break; + while( *str != ':' && *str != '\0' ) + ++str; + while( *str == ':' ) + { + ++str; + ++(*total); + } + } + return count; +} + +/// Displays a menu with options and goes to the target label. +/// The script is stopped if cancel is pressed. +/// Options with no text are not displayed in the client. +/// +/// Options can be grouped together, separated by the character ':' in the text: +/// ex: menu "A:B:C",L_target; +/// All these options go to the specified target label. +/// +/// The index of the selected option is put in the variable @menu. +/// Indexes start with 1 and are consistent with grouped and empty options. +/// ex: menu "A::B",-,"",L_Impossible,"C",-; +/// // displays "A", "B" and "C", corresponding to indexes 1, 3 and 5 +/// +/// NOTE: the client closes the npc dialog when cancel is pressed +/// +/// menu "<option_text>",<target_label>{,"<option_text>",<target_label>,...}; +BUILDIN_FUNC(menu) +{ + int i; + const char* text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + // TODO detect multiple scripts waiting for input at the same time, and what to do when that happens + if( sd->state.menu_or_input == 0 ) + { + struct StringBuf buf; + struct script_data* data; + + if( script_lastdata(st) % 2 == 0 ) + {// argument count is not even (1st argument is at index 2) + ShowError("script:menu: illegal number of arguments (%d).\n", (script_lastdata(st) - 1)); + st->state = END; + return 1; + } + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i < script_lastdata(st); i += 2 ) + { + // menu options + text = script_getstr(st, i); + + // target label + data = script_getdata(st, i+1); + if( !data_islabel(data) ) + {// not a label + StringBuf_Destroy(&buf); + ShowError("script:menu: argument #%d (from 1) is not a label or label not found.\n", i); + script_reportdata(data); + st->state = END; + return 1; + } + + // append option(s) + if( text[0] == '\0' ) + continue;// empty string, ignore + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) + {// client supports only up to 254 entries; 0 is not used and 255 is reserved for cancel; excess entries are displayed but cause 'uint8' overflow + ShowWarning("buildin_menu: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } + else if( sd->npc_menu == 0xff ) + {// Cancel was pressed + sd->state.menu_or_input = 0; + st->state = END; + } + else + {// goto target label + int menu = 0; + + sd->state.menu_or_input = 0; + if( sd->npc_menu <= 0 ) + { + ShowDebug("script:menu: unexpected selection (%d)\n", sd->npc_menu); + st->state = END; + return 1; + } + + // get target label + for( i = 2; i < script_lastdata(st); i += 2 ) + { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + if( sd->npc_menu > 0 ) + {// Invalid selection + ShowDebug("script:menu: selection is out of range (%d pairs are missing?) - please report this\n", sd->npc_menu); + st->state = END; + return 1; + } + if( !data_islabel(script_getdata(st, i + 1)) ) + {// TODO remove this temporary crash-prevention code (fallback for multiple scripts requesting user input) + ShowError("script:menu: unexpected data in label argument\n"); + script_reportdata(script_getdata(st, i + 1)); + st->state = END; + return 1; + } + pc_setreg(sd, add_str("@menu"), menu); + st->pos = script_getnum(st, i + 1); + st->state = GOTO; + } + return 0; +} + +/// Displays a menu with options and returns the selected option. +/// Behaves like 'menu' without the target labels. +/// +/// select(<option_text>{,<option_text>,...}) -> <selected_option> +/// +/// @see menu +BUILDIN_FUNC(select) +{ + int i; + const char* text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->state.menu_or_input == 0 ) { + struct StringBuf buf; + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i <= script_lastdata(st); ++i ) { + text = script_getstr(st, i); + + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) { + ShowWarning("buildin_select: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } else if( sd->npc_menu == 0xff ) {// Cancel was pressed + sd->state.menu_or_input = 0; + st->state = END; + } else {// return selected option + int menu = 0; + + sd->state.menu_or_input = 0; + for( i = 2; i <= script_lastdata(st); ++i ) { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + pc_setreg(sd, add_str("@menu"), menu); + script_pushint(st, menu); + st->state = RUN; + } + return 0; +} + +/// Displays a menu with options and returns the selected option. +/// Behaves like 'menu' without the target labels, except when cancel is +/// pressed. +/// When cancel is pressed, the script continues and 255 is returned. +/// +/// prompt(<option_text>{,<option_text>,...}) -> <selected_option> +/// +/// @see menu +BUILDIN_FUNC(prompt) +{ + int i; + const char *text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->state.menu_or_input == 0 ) + { + struct StringBuf buf; + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i <= script_lastdata(st); ++i ) + { + text = script_getstr(st, i); + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) + { + ShowWarning("buildin_prompt: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } + else if( sd->npc_menu == 0xff ) + {// Cancel was pressed + sd->state.menu_or_input = 0; + pc_setreg(sd, add_str("@menu"), 0xff); + script_pushint(st, 0xff); + st->state = RUN; + } + else + {// return selected option + int menu = 0; + + sd->state.menu_or_input = 0; + for( i = 2; i <= script_lastdata(st); ++i ) + { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + pc_setreg(sd, add_str("@menu"), menu); + script_pushint(st, menu); + st->state = RUN; + } + return 0; +} + +///////////////////////////////////////////////////////////////////// +// ... +// + +/// Jumps to the target script label. +/// +/// goto <label>; +BUILDIN_FUNC(goto) +{ + if( !data_islabel(script_getdata(st,2)) ) + { + ShowError("script:goto: not a label\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + st->pos = script_getnum(st,2); + st->state = GOTO; + return 0; +} + +/*========================================== + * user-defined function call + *------------------------------------------*/ +BUILDIN_FUNC(callfunc) +{ + int i, j; + struct script_retinfo* ri; + struct script_code* scr; + const char* str = script_getstr(st,2); + DBMap **ref = NULL; + + scr = (struct script_code*)strdb_get(userfunc_db, str); + if( !scr ) + { + ShowError("script:callfunc: function not found! [%s]\n", str); + st->state = END; + return 1; + } + + for( i = st->start+3, j = 0; i < st->end; i++, j++ ) + { + struct script_data* data = push_copy(st->stack,i); + if( data_isreference(data) && !data->ref ) + { + const char* name = reference_getname(data); + if( name[0] == '.' ) { + if ( !ref ) { + ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); + ref[0] = (name[1] == '@' ? st->stack->var_function : st->script->script_vars); + } + data->ref = ref; + } + } + } + + CREATE(ri, struct script_retinfo, 1); + ri->script = st->script;// script code + ri->var_function = st->stack->var_function;// scope variables + ri->pos = st->pos;// script location + ri->nargs = j;// argument count + ri->defsp = st->stack->defsp;// default stack pointer + push_retinfo(st->stack, ri, ref); + + st->pos = 0; + st->script = scr; + st->stack->defsp = st->stack->sp; + st->state = GOTO; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + + return 0; +} +/*========================================== + * subroutine call + *------------------------------------------*/ +BUILDIN_FUNC(callsub) +{ + int i,j; + struct script_retinfo* ri; + int pos = script_getnum(st,2); + DBMap **ref = NULL; + + if( !data_islabel(script_getdata(st,2)) && !data_isfunclabel(script_getdata(st,2)) ) + { + ShowError("script:callsub: argument is not a label\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + for( i = st->start+3, j = 0; i < st->end; i++, j++ ) + { + struct script_data* data = push_copy(st->stack,i); + if( data_isreference(data) && !data->ref ) + { + const char* name = reference_getname(data); + if( name[0] == '.' && name[1] == '@' ) { + if ( !ref ) { + ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); + ref[0] = st->stack->var_function; + } + data->ref = ref; + } + } + } + + CREATE(ri, struct script_retinfo, 1); + ri->script = st->script;// script code + ri->var_function = st->stack->var_function;// scope variables + ri->pos = st->pos;// script location + ri->nargs = j;// argument count + ri->defsp = st->stack->defsp;// default stack pointer + push_retinfo(st->stack, ri, ref); + + st->pos = pos; + st->stack->defsp = st->stack->sp; + st->state = GOTO; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + + return 0; +} + +/// Retrieves an argument provided to callfunc/callsub. +/// If the argument doesn't exist +/// +/// getarg(<index>{,<default_value>}) -> <value> +BUILDIN_FUNC(getarg) +{ + struct script_retinfo* ri; + int idx; + + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) + { + ShowError("script:getarg: no callfunc or callsub!\n"); + st->state = END; + return 1; + } + ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; + + idx = script_getnum(st,2); + + if( idx >= 0 && idx < ri->nargs ) + push_copy(st->stack, st->stack->defsp - 1 - ri->nargs + idx); + else if( script_hasdata(st,3) ) + script_pushcopy(st, 3); + else + { + ShowError("script:getarg: index (idx=%d) out of range (nargs=%d) and no default value found\n", idx, ri->nargs); + st->state = END; + return 1; + } + + return 0; +} + +/// Returns from the current function, optionaly returning a value from the functions. +/// Don't use outside script functions. +/// +/// return; +/// return <value>; +BUILDIN_FUNC(return) +{ + if( script_hasdata(st,2) ) + {// return value + struct script_data* data; + script_pushcopy(st, 2); + data = script_getdatatop(st, -1); + if( data_isreference(data) ) + { + const char* name = reference_getname(data); + if( name[0] == '.' && name[1] == '@' ) + {// scope variable + if( !data->ref || data->ref == (DBMap**)&st->stack->var_function ) + get_val(st, data);// current scope, convert to value + } + else if( name[0] == '.' && !data->ref ) + {// script variable, link to current script + data->ref = &st->script->script_vars; + } + } + } + else + {// no return value + script_pushnil(st); + } + st->state = RETFUNC; + return 0; +} + +/// Returns a random number from 0 to <range>-1. +/// Or returns a random number from <min> to <max>. +/// If <min> is greater than <max>, their numbers are switched. +/// rand(<range>) -> <int> +/// rand(<min>,<max>) -> <int> +BUILDIN_FUNC(rand) +{ + int range; + int min; + int max; + + if( script_hasdata(st,3) ) + {// min,max + min = script_getnum(st,2); + max = script_getnum(st,3); + if( max < min ) + swap(min, max); + range = max - min + 1; + } + else + {// range + min = 0; + range = script_getnum(st,2); + } + if( range <= 1 ) + script_pushint(st, min); + else + script_pushint(st, rnd()%range + min); + + return 0; +} + +/*========================================== + * Warp sd to str,x,y or Random or SavePoint/Save + *------------------------------------------*/ +BUILDIN_FUNC(warp) +{ + int ret; + int x,y; + const char* str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + str = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + + if(strcmp(str,"Random")==0) + ret = pc_randomwarp(sd,CLR_TELEPORT); + else if(strcmp(str,"SavePoint")==0 || strcmp(str,"Save")==0) + ret = pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + else + ret = pc_setpos(sd,mapindex_name2id(str),x,y,CLR_OUTSIGHT); + + if( ret ) { + ShowError("buildin_warp: moving player '%s' to \"%s\",%d,%d failed.\n", sd->status.name, str, x, y); + script_reportsrc(st); + } + + return 0; +} +/*========================================== + * Warp a specified area + *------------------------------------------*/ +static int buildin_areawarp_sub(struct block_list *bl,va_list ap) +{ + int x2,y2,x3,y3; + unsigned int index; + + index = va_arg(ap,unsigned int); + x2 = va_arg(ap,int); + y2 = va_arg(ap,int); + x3 = va_arg(ap,int); + y3 = va_arg(ap,int); + + if(index == 0) + pc_randomwarp((TBL_PC *)bl,CLR_TELEPORT); + else if(x3 && y3) { + int max, tx, ty, j = 0; + + // choose a suitable max number of attempts + if( (max = (y3-y2+1)*(x3-x2+1)*3) > 1000 ) + max = 1000; + + // find a suitable map cell + do { + tx = rnd()%(x3-x2+1)+x2; + ty = rnd()%(y3-y2+1)+y2; + j++; + } while( map_getcell(index,tx,ty,CELL_CHKNOPASS) && j < max ); + + pc_setpos((TBL_PC *)bl,index,tx,ty,CLR_OUTSIGHT); + } + else + pc_setpos((TBL_PC *)bl,index,x2,y2,CLR_OUTSIGHT); + return 0; +} +BUILDIN_FUNC(areawarp) +{ + int16 m, x0,y0,x1,y1, x2,y2,x3=0,y3=0; + unsigned int index; + const char *str; + const char *mapname; + + mapname = script_getstr(st,2); + x0 = script_getnum(st,3); + y0 = script_getnum(st,4); + x1 = script_getnum(st,5); + y1 = script_getnum(st,6); + str = script_getstr(st,7); + x2 = script_getnum(st,8); + y2 = script_getnum(st,9); + + if( script_hasdata(st,10) && script_hasdata(st,11) ) { // Warp area to area + if( (x3 = script_getnum(st,10)) < 0 || (y3 = script_getnum(st,11)) < 0 ){ + x3 = 0; + y3 = 0; + } else if( x3 && y3 ) { + // normalize x3/y3 coordinates + if( x3 < x2 ) swap(x3,x2); + if( y3 < y2 ) swap(y3,y2); + } + } + + if( (m = map_mapname2mapid(mapname)) < 0 ) + return 0; + + if( strcmp(str,"Random") == 0 ) + index = 0; + else if( !(index=mapindex_name2id(str)) ) + return 0; + + map_foreachinarea(buildin_areawarp_sub, m,x0,y0,x1,y1, BL_PC, index,x2,y2,x3,y3); + return 0; +} + +/*========================================== + * areapercentheal <map>,<x1>,<y1>,<x2>,<y2>,<hp>,<sp> + *------------------------------------------*/ +static int buildin_areapercentheal_sub(struct block_list *bl,va_list ap) +{ + int hp, sp; + hp = va_arg(ap, int); + sp = va_arg(ap, int); + pc_percentheal((TBL_PC *)bl,hp,sp); + return 0; +} +BUILDIN_FUNC(areapercentheal) +{ + int hp,sp,m; + const char *mapname; + int x0,y0,x1,y1; + + mapname=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + hp=script_getnum(st,7); + sp=script_getnum(st,8); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areapercentheal_sub,m,x0,y0,x1,y1,BL_PC,hp,sp); + return 0; +} + +/*========================================== + * warpchar [LuzZza] + * Useful for warp one player from + * another player npc-session. + * Using: warpchar "mapname",x,y,Char_ID; + *------------------------------------------*/ +BUILDIN_FUNC(warpchar) +{ + int x,y,a; + const char *str; + TBL_PC *sd; + + str=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + a=script_getnum(st,5); + + sd = map_charid2sd(a); + if( sd == NULL ) + return 0; + + if(strcmp(str, "Random") == 0) + pc_randomwarp(sd, CLR_TELEPORT); + else + if(strcmp(str, "SavePoint") == 0) + pc_setpos(sd, sd->status.save_point.map,sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); + else + pc_setpos(sd, mapindex_name2id(str), x, y, CLR_TELEPORT); + + return 0; +} +/*========================================== + * Warpparty - [Fredzilla] [Paradox924X] + * Syntax: warpparty "to_mapname",x,y,Party_ID,{"from_mapname"}; + * If 'from_mapname' is specified, only the party members on that map will be warped + *------------------------------------------*/ +BUILDIN_FUNC(warpparty) +{ + TBL_PC *sd = NULL; + TBL_PC *pl_sd; + struct party_data* p; + int type; + int mapindex; + int i; + + const char* str = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + int p_id = script_getnum(st,5); + const char* str2 = NULL; + if ( script_hasdata(st,6) ) + str2 = script_getstr(st,6); + + p = party_search(p_id); + if(!p) + return 0; + + type = ( strcmp(str,"Random")==0 ) ? 0 + : ( strcmp(str,"SavePointAll")==0 ) ? 1 + : ( strcmp(str,"SavePoint")==0 ) ? 2 + : ( strcmp(str,"Leader")==0 ) ? 3 + : 4; + + switch (type) + { + case 3: + for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); + if (i == MAX_PARTY || !p->data[i].sd) //Leader not found / not online + return 0; + pl_sd = p->data[i].sd; + mapindex = pl_sd->mapindex; + x = pl_sd->bl.x; + y = pl_sd->bl.y; + break; + case 4: + mapindex = mapindex_name2id(str); + break; + case 2: + //"SavePoint" uses save point of the currently attached player + if (( sd = script_rid2sd(st) ) == NULL ) + return 0; + default: + mapindex = 0; + break; + } + + for (i = 0; i < MAX_PARTY; i++) + { + if( !(pl_sd = p->data[i].sd) || pl_sd->status.party_id != p_id ) + continue; + + if( str2 && strcmp(str2, map[pl_sd->bl.m].name) != 0 ) + continue; + + if( pc_isdead(pl_sd) ) + continue; + + switch( type ) + { + case 0: // Random + if(!map[pl_sd->bl.m].flag.nowarp) + pc_randomwarp(pl_sd,CLR_TELEPORT); + break; + case 1: // SavePointAll + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); + break; + case 2: // SavePoint + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + case 3: // Leader + case 4: // m,x,y + if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) + pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); + break; + } + } + + return 0; +} +/*========================================== + * Warpguild - [Fredzilla] + * Syntax: warpguild "mapname",x,y,Guild_ID; + *------------------------------------------*/ +BUILDIN_FUNC(warpguild) +{ + TBL_PC *sd = NULL; + TBL_PC *pl_sd; + struct guild* g; + struct s_mapiterator* iter; + int type; + + const char* str = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + int gid = script_getnum(st,5); + + g = guild_search(gid); + if( g == NULL ) + return 0; + + type = ( strcmp(str,"Random")==0 ) ? 0 + : ( strcmp(str,"SavePointAll")==0 ) ? 1 + : ( strcmp(str,"SavePoint")==0 ) ? 2 + : 3; + + if( type == 2 && ( sd = script_rid2sd(st) ) == NULL ) + {// "SavePoint" uses save point of the currently attached player + return 0; + } + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if( pl_sd->status.guild_id != gid ) + continue; + + switch( type ) + { + case 0: // Random + if(!map[pl_sd->bl.m].flag.nowarp) + pc_randomwarp(pl_sd,CLR_TELEPORT); + break; + case 1: // SavePointAll + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); + break; + case 2: // SavePoint + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + case 3: // m,x,y + if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) + pc_setpos(pl_sd,mapindex_name2id(str),x,y,CLR_TELEPORT); + break; + } + } + mapit_free(iter); + + return 0; +} +/*========================================== + * Force Heal a player (hp and sp) + *------------------------------------------*/ +BUILDIN_FUNC(heal) +{ + TBL_PC *sd; + int hp,sp; + + sd = script_rid2sd(st); + if (!sd) return 0; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + status_heal(&sd->bl, hp, sp, 1); + return 0; +} +/*========================================== + * Heal a player by item (get vit bonus etc) + *------------------------------------------*/ +BUILDIN_FUNC(itemheal) +{ + TBL_PC *sd; + int hp,sp; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + + if(potion_flag==1) { + potion_hp = hp; + potion_sp = sp; + return 0; + } + + sd = script_rid2sd(st); + if (!sd) return 0; + pc_itemheal(sd,sd->itemid,hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(percentheal) +{ + int hp,sp; + TBL_PC* sd; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + + if(potion_flag==1) { + potion_per_hp = hp; + potion_per_sp = sp; + return 0; + } + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; +#ifdef RENEWAL + if( sd->sc.data[SC_EXTREMITYFIST2] ) + sp = 0; +#endif + pc_percentheal(sd,hp,sp); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(jobchange) +{ + int job, upper=-1; + + job=script_getnum(st,2); + if( script_hasdata(st,3) ) + upper=script_getnum(st,3); + + if (pcdb_checkid(job)) + { + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_jobchange(sd, job, upper); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(jobname) +{ + int class_=script_getnum(st,2); + script_pushconststr(st, (char*)job_name(class_)); + return 0; +} + +/// Get input from the player. +/// For numeric inputs the value is capped to the range [min,max]. Returns 1 if +/// the value was higher than 'max', -1 if lower than 'min' and 0 otherwise. +/// For string inputs it returns 1 if the string was longer than 'max', -1 is +/// shorter than 'min' and 0 otherwise. +/// +/// input(<var>{,<min>{,<max>}}) -> <int> +BUILDIN_FUNC(input) +{ + TBL_PC* sd; + struct script_data* data; + int uid; + const char* name; + int min; + int max; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + data = script_getdata(st,2); + if( !data_isreference(data) ){ + ShowError("script:input: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1; + } + uid = reference_getuid(data); + name = reference_getname(data); + min = (script_hasdata(st,3) ? script_getnum(st,3) : script_config.input_min_value); + max = (script_hasdata(st,4) ? script_getnum(st,4) : script_config.input_max_value); + + if( !sd->state.menu_or_input ) + { // first invocation, display npc input box + sd->state.menu_or_input = 1; + st->state = RERUNLINE; + if( is_string_variable(name) ) + clif_scriptinputstr(sd,st->oid); + else + clif_scriptinput(sd,st->oid); + } + else + { // take received text/value and store it in the designated variable + sd->state.menu_or_input = 0; + if( is_string_variable(name) ) + { + int len = (int)strlen(sd->npc_str); + set_reg(st, sd, uid, name, (void*)sd->npc_str, script_getref(st,2)); + script_pushint(st, (len > max ? 1 : len < min ? -1 : 0)); + } + else + { + int amount = sd->npc_amount; + set_reg(st, sd, uid, name, (void*)__64BPRTSIZE(cap_value(amount,min,max)), script_getref(st,2)); + script_pushint(st, (amount > max ? 1 : amount < min ? -1 : 0)); + } + st->state = RUN; + } + return 0; +} + +// declare the copyarray method here for future reference +BUILDIN_FUNC(copyarray); + +/// Sets the value of a variable. +/// The value is converted to the type of the variable. +/// +/// set(<variable>,<value>) -> <variable> +BUILDIN_FUNC(set) +{ + TBL_PC* sd = NULL; + struct script_data* data; + //struct script_data* datavalue; + int num; + const char* name; + char prefix; + + data = script_getdata(st,2); + //datavalue = script_getdata(st,3); + if( !data_isreference(data) ) + { + ShowError("script:set: not a variable\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + num = reference_getuid(data); + name = reference_getname(data); + prefix = *name; + + if( not_server_variable(prefix) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + { + ShowError("script:set: no player attached for player variable '%s'\n", name); + return 0; + } + } + +#if 0 + if( data_isreference(datavalue) ) + {// the value being referenced is a variable + const char* namevalue = reference_getname(datavalue); + + if( !not_array_variable(*namevalue) ) + {// array variable being copied into another array variable + if( sd == NULL && not_server_variable(*namevalue) && !(sd = script_rid2sd(st)) ) + {// player must be attached in order to copy a player variable + ShowError("script:set: no player attached for player variable '%s'\n", namevalue); + return 0; + } + + if( is_string_variable(namevalue) != is_string_variable(name) ) + {// non-matching array value types + ShowWarning("script:set: two array variables do not match in type.\n"); + return 0; + } + + // push the maximum number of array values to the stack + push_val(st->stack, C_INT, SCRIPT_MAX_ARRAYSIZE); + + // call the copy array method directly + return buildin_copyarray(st); + } + } +#endif + + if( is_string_variable(name) ) + set_reg(st,sd,num,name,(void*)script_getstr(st,3),script_getref(st,2)); + else + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(script_getnum(st,3)),script_getref(st,2)); + + // return a copy of the variable reference + script_pushcopy(st,2); + + return 0; +} + +///////////////////////////////////////////////////////////////////// +/// Array variables +/// + +/// Returns the size of the specified array +static int32 getarraysize(struct script_state* st, int32 id, int32 idx, int isstring, struct DBMap** ref) +{ + int32 ret = idx; + + if( isstring ) + { + for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) + { + char* str = (char*)get_val2(st, reference_uid(id, idx), ref); + if( str && *str ) + ret = idx + 1; + script_removetop(st, -1, 0); + } + } + else + { + for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) + { + int32 num = (int32)__64BPRTSIZE(get_val2(st, reference_uid(id, idx), ref)); + if( num ) + ret = idx + 1; + script_removetop(st, -1, 0); + } + } + return ret; +} + +/// Sets values of an array, from the starting index. +/// ex: setarray arr[1],1,2,3; +/// +/// setarray <array variable>,<value1>{,<value2>...}; +BUILDIN_FUNC(setarray) +{ + struct script_data* data; + const char* name; + int32 start; + int32 end; + int32 id; + int32 i; + TBL_PC* sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:setarray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:setarray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + end = start + script_lastdata(st) - 2; + if( end > SCRIPT_MAX_ARRAYSIZE ) + end = SCRIPT_MAX_ARRAYSIZE; + + if( is_string_variable(name) ) + {// string array + for( i = 3; start < end; ++start, ++i ) + set_reg(st, sd, reference_uid(id, start), name, (void*)script_getstr(st,i), reference_getref(data)); + } + else + {// int array + for( i = 3; start < end; ++start, ++i ) + set_reg(st, sd, reference_uid(id, start), name, (void*)__64BPRTSIZE(script_getnum(st,i)), reference_getref(data)); + } + return 0; +} + +/// Sets count values of an array, from the starting index. +/// ex: cleararray arr[0],0,1; +/// +/// cleararray <array variable>,<value>,<count>; +BUILDIN_FUNC(cleararray) +{ + struct script_data* data; + const char* name; + int32 start; + int32 end; + int32 id; + void* v; + TBL_PC* sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:cleararray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:cleararray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + if( is_string_variable(name) ) + v = (void*)script_getstr(st, 3); + else + v = (void*)__64BPRTSIZE(script_getnum(st, 3)); + + end = start + script_getnum(st, 4); + if( end > SCRIPT_MAX_ARRAYSIZE ) + end = SCRIPT_MAX_ARRAYSIZE; + + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, v, script_getref(st,2)); + return 0; +} + +/// Copies data from one array to another. +/// ex: copyarray arr[0],arr[2],2; +/// +/// copyarray <destination array variable>,<source array variable>,<count>; +BUILDIN_FUNC(copyarray) +{ + struct script_data* data1; + struct script_data* data2; + const char* name1; + const char* name2; + int32 idx1; + int32 idx2; + int32 id1; + int32 id2; + void* v; + int32 i; + int32 count; + TBL_PC* sd = NULL; + + data1 = script_getdata(st, 2); + data2 = script_getdata(st, 3); + if( !data_isreference(data1) || !data_isreference(data2) ) + { + ShowError("script:copyarray: not a variable\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// not a variable + } + + id1 = reference_getid(data1); + id2 = reference_getid(data2); + idx1 = reference_getindex(data1); + idx2 = reference_getindex(data2); + name1 = reference_getname(data1); + name2 = reference_getname(data2); + if( not_array_variable(*name1) || not_array_variable(*name2) ) + { + ShowError("script:copyarray: illegal scope\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// not supported + } + + if( is_string_variable(name1) != is_string_variable(name2) ) + { + ShowError("script:copyarray: type mismatch\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name1) || not_server_variable(*name2) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + count = script_getnum(st, 4); + if( count > SCRIPT_MAX_ARRAYSIZE - idx1 ) + count = SCRIPT_MAX_ARRAYSIZE - idx1; + if( count <= 0 || (id1 == id2 && idx1 == idx2) ) + return 0;// nothing to copy + + if( id1 == id2 && idx1 > idx2 ) + {// destination might be overlapping the source - copy in reverse order + for( i = count - 1; i >= 0; --i ) + { + v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); + script_removetop(st, -1, 0); + } + } + else + {// normal copy + for( i = 0; i < count; ++i ) + { + if( idx2 + i < SCRIPT_MAX_ARRAYSIZE ) + { + v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); + script_removetop(st, -1, 0); + } + else// out of range - assume ""/0 + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, (is_string_variable(name1)?(void*)"":(void*)0), reference_getref(data1)); + } + } + return 0; +} + +/// Returns the size of the array. +/// Assumes that everything before the starting index exists. +/// ex: getarraysize(arr[3]) +/// +/// getarraysize(<array variable>) -> <int> +BUILDIN_FUNC(getarraysize) +{ + struct script_data* data; + const char* name; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:getarraysize: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not a variable + } + + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:getarraysize: illegal scope\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not supported + } + + script_pushint(st, getarraysize(st, reference_getid(data), reference_getindex(data), is_string_variable(name), reference_getref(data))); + return 0; +} + +/// Deletes count or all the elements in an array, from the starting index. +/// ex: deletearray arr[4],2; +/// +/// deletearray <array variable>; +/// deletearray <array variable>,<count>; +BUILDIN_FUNC(deletearray) +{ + struct script_data* data; + const char* name; + int start; + int end; + int id; + TBL_PC *sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:deletearray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:deletearray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + end = SCRIPT_MAX_ARRAYSIZE; + + if( start >= end ) + return 0;// nothing to free + + if( script_hasdata(st,3) ) + { + int count = script_getnum(st, 3); + if( count > end - start ) + count = end - start; + if( count <= 0 ) + return 0;// nothing to free + + // move rest of the elements backward + for( ; start + count < end; ++start ) + { + void* v = get_val2(st, reference_uid(id, start + count), reference_getref(data)); + set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); + script_removetop(st, -1, 0); + } + } + + // clear the rest of the array + if( is_string_variable(name) ) + { + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, (void *)"", reference_getref(data)); + } + else + { + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, (void*)0, reference_getref(data)); + } + return 0; +} + +/// Returns a reference to the target index of the array variable. +/// Equivalent to var[index]. +/// +/// getelementofarray(<array variable>,<index>) -> <variable reference> +BUILDIN_FUNC(getelementofarray) +{ + struct script_data* data; + const char* name; + int32 id; + int i; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:getelementofarray: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:getelementofarray: illegal scope\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not supported + } + + i = script_getnum(st, 3); + if( i < 0 || i >= SCRIPT_MAX_ARRAYSIZE ) + { + ShowWarning("script:getelementofarray: index out of range (%d)\n", i); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// out of range + } + + push_val2(st->stack, C_NAME, reference_uid(id, i), reference_getref(data)); + return 0; +} + +///////////////////////////////////////////////////////////////////// +/// ... +/// + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(setlook) +{ + int type,val; + TBL_PC* sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_changelook(sd,type,val); + + return 0; +} + +BUILDIN_FUNC(changelook) +{ // As setlook but only client side + int type,val; + TBL_PC* sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_changelook(&sd->bl,type,val); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(cutin) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_cutin(sd,script_getstr(st,2),script_getnum(st,3)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(viewpoint) +{ + int type,x,y,id,color; + TBL_PC* sd; + + type=script_getnum(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + id=script_getnum(st,5); + color=script_getnum(st,6); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_viewpoint(sd,st->oid,type,x,y,id,color); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(countitem) +{ + int nameid, i; + int count = 0; + struct item_data* id = NULL; + struct script_data* data; + + TBL_PC* sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + + data = script_getdata(st,2); + get_val(st, data); // convert into value in case of a variable + + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + + nameid = id->nameid; + + for(i = 0; i < MAX_INVENTORY; i++) + if(sd->status.inventory[i].nameid == nameid) + count += sd->status.inventory[i].amount; + + script_pushint(st,count); + return 0; +} + +/*========================================== + * countitem2(nameID,Identified,Refine,Attribute,Card0,Card1,Card2,Card3) [Lupus] + * returns number of items that meet the conditions + *------------------------------------------*/ +BUILDIN_FUNC(countitem2) +{ + int nameid, iden, ref, attr, c1, c2, c3, c4; + int count = 0; + int i; + struct item_data* id = NULL; + struct script_data* data; + + TBL_PC* sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + + data = script_getdata(st,2); + get_val(st, data); // convert into value in case of a variable + + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem2: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + + nameid = id->nameid; + iden = script_getnum(st,3); + ref = script_getnum(st,4); + attr = script_getnum(st,5); + c1 = (short)script_getnum(st,6); + c2 = (short)script_getnum(st,7); + c3 = (short)script_getnum(st,8); + c4 = (short)script_getnum(st,9); + + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] != NULL && + sd->status.inventory[i].amount > 0 && sd->status.inventory[i].nameid == nameid && + sd->status.inventory[i].identify == iden && sd->status.inventory[i].refine == ref && + sd->status.inventory[i].attribute == attr && sd->status.inventory[i].card[0] == c1 && + sd->status.inventory[i].card[1] == c2 && sd->status.inventory[i].card[2] == c3 && + sd->status.inventory[i].card[3] == c4 + ) + count += sd->status.inventory[i].amount; + + script_pushint(st,count); + return 0; +} + +/*========================================== + * Check if item with this amount can fit in inventory + * Checking : weight, stack amount >32k, slots amount >(MAX_INVENTORY) + * Return + * 0 : fail + * 1 : success (npc side only) + *------------------------------------------*/ +BUILDIN_FUNC(checkweight) +{ + int nameid, amount, slots, amount2=0; + unsigned int weight=0, i, nbargs; + struct item_data* id = NULL; + struct map_session_data* sd; + struct script_data* data; + + if( ( sd = script_rid2sd(st) ) == NULL ){ + return 0; + } + nbargs = script_lastdata(st)+1; + if(nbargs%2){ + ShowError("buildin_checkweight: Invalid nb of args should be a multiple of 2.\n"); + script_pushint(st,0); + return 1; + } + slots = pc_inventoryblank(sd); //nb of empty slot + + for(i=2; i<nbargs; i=i+2){ + data = script_getdata(st,i); + get_val(st, data); // convert into value in case of a variable + if( data_isstring(data) ){// item name + id = itemdb_searchname(conv_str(st, data)); + } else {// item id + id = itemdb_exists(conv_num(st, data)); + } + if( id == NULL ) { + ShowError("buildin_checkweight: Invalid item '%s'.\n", script_getstr(st,i)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + nameid = id->nameid; + + amount = script_getnum(st,i+1); + if( amount < 1 ) { + ShowError("buildin_checkweight: Invalid amount '%d'.\n", amount); + script_pushint(st,0); + return 1; + } + + weight += itemdb_weight(nameid)*amount; //total weight for all chk + if( weight + sd->weight > sd->max_weight ) + {// too heavy + script_pushint(st,0); + return 0; + } + + switch( pc_checkadditem(sd, nameid, amount) ) + { + case ADDITEM_EXIST: + // item is already in inventory, but there is still space for the requested amount + break; + case ADDITEM_NEW: + if( itemdb_isstackable(nameid) ) {// stackable + amount2++; + if( slots < amount2 ) { + script_pushint(st,0); + return 0; + } + } + else {// non-stackable + amount2 += amount; + if( slots < amount2){ + script_pushint(st,0); + return 0; + } + } + break; + case ADDITEM_OVERAMOUNT: + script_pushint(st,0); + return 0; + } + } + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(checkweight2) +{ + //variable sub checkweight + int32 nameid=-1, amount=-1; + int i=0, amount2=0, slots=0, weight=0; + short fail=0; + + //variable for array parsing + struct script_data* data_it; + struct script_data* data_nb; + const char* name_it; + const char* name_nb; + int32 id_it, id_nb; + int32 idx_it, idx_nb; + int nb_it, nb_nb; //array size + + TBL_PC *sd = script_rid2sd(st); + nullpo_retr(1,sd); + + data_it = script_getdata(st, 2); + data_nb = script_getdata(st, 3); + + if( !data_isreference(data_it) || !data_isreference(data_nb)) + { + ShowError("script:checkweight2: parameter not a variable\n"); + script_pushint(st,0); + return 1;// not a variable + } + id_it = reference_getid(data_it); + id_nb = reference_getid(data_nb); + idx_it = reference_getindex(data_it); + idx_nb = reference_getindex(data_nb); + name_it = reference_getname(data_it); + name_nb = reference_getname(data_nb); + + if( not_array_variable(*name_it) || not_array_variable(*name_nb)) + { + ShowError("script:checkweight2: illegal scope\n"); + script_pushint(st,0); + return 1;// not supported + } + if(is_string_variable(name_it) || is_string_variable(name_nb)){ + ShowError("script:checkweight2: illegal type, need int\n"); + script_pushint(st,0); + return 1;// not supported + } + nb_it = getarraysize(st, id_it, idx_it, 0, reference_getref(data_it)); + nb_nb = getarraysize(st, id_nb, idx_nb, 0, reference_getref(data_nb)); + if(nb_it != nb_nb){ + ShowError("Size mistmatch: nb_it=%d, nb_nb=%d\n",nb_it,nb_nb); + fail = 1; + } + + slots = pc_inventoryblank(sd); + for(i=0; i<nb_it; i++){ + nameid = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_it,idx_it+i),reference_getref(data_it))); + script_removetop(st, -1, 0); + amount = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_nb,idx_nb+i),reference_getref(data_nb))); + script_removetop(st, -1, 0); + if(fail) continue; //cpntonie to depop rest + + if(itemdb_exists(nameid) == NULL ){ + ShowError("buildin_checkweight2: Invalid item '%d'.\n", nameid); + fail=1; + continue; + } + if(amount < 0 ){ + ShowError("buildin_checkweight2: Invalid amount '%d'.\n", amount); + fail = 1; + continue; + } + weight += itemdb_weight(nameid)*amount; + if( weight + sd->weight > sd->max_weight ){ + fail = 1; + continue; + } + switch( pc_checkadditem(sd, nameid, amount) ) { + case ADDITEM_EXIST: + // item is already in inventory, but there is still space for the requested amount + break; + case ADDITEM_NEW: + if( itemdb_isstackable(nameid) ){// stackable + amount2++; + if( slots < amount2 ) + fail = 1; + } + else {// non-stackable + amount2 += amount; + if( slots < amount2 ){ + fail = 1; + } + } + break; + case ADDITEM_OVERAMOUNT: + fail = 1; + } //end switch + } //end loop DO NOT break it prematurly we need to depop all stack + + fail?script_pushint(st,0):script_pushint(st,1); + return 0; +} + +/*========================================== + * getitem <item id>,<amount>{,<account ID>}; + * getitem "<item name>",<amount>{,<account ID>}; + *------------------------------------------*/ +BUILDIN_FUNC(getitem) +{ + int nameid,amount,get_count,i,flag = 0; + struct item it; + TBL_PC *sd; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + {// "<item name>" + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data == NULL ){ + ShowError("buildin_getitem: Nonexistant item %s requested.\n", name); + return 1; //No item created. + } + nameid=item_data->nameid; + } else if( data_isint(data) ) + {// <item id> + nameid=conv_num(st,data); + //Violet Box, Blue Box, etc - random item pick + if( nameid < 0 ) { + nameid = -nameid; + flag = 1; + } + if( nameid <= 0 || !itemdb_exists(nameid) ){ + ShowError("buildin_getitem: Nonexistant item %d requested.\n", nameid); + return 1; //No item created. + } + } else { + ShowError("buildin_getitem: invalid data type for argument #1 (%d).", data->type); + return 1; + } + + // <amount> + if( (amount=script_getnum(st,3)) <= 0) + return 0; //return if amount <=0, skip the useles iteration + + memset(&it,0,sizeof(it)); + it.nameid=nameid; + if(!flag) + it.identify=1; + else + it.identify=itemdb_isidentified(nameid); + + if( script_hasdata(st,4) ) + sd=map_id2sd(script_getnum(st,4)); // <Account ID> + else + sd=script_rid2sd(st); // Attached player + + if( sd == NULL ) // no target + return 0; + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = amount; + + for (i = 0; i < amount; i += get_count) + { + // if not pet egg + if (!pet_create_egg(sd, nameid)) + { + if ((flag = pc_additem(sd, &it, get_count, LOG_TYPE_SCRIPT))) + { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&it) ) + map_addflooritem(&it,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(getitem2) +{ + int nameid,amount,get_count,i,flag = 0; + int iden,ref,attr,c1,c2,c3,c4; + struct item_data *item_data; + struct item item_tmp; + TBL_PC *sd; + struct script_data *data; + + if( script_hasdata(st,11) ) + sd=map_id2sd(script_getnum(st,11)); // <Account ID> + else + sd=script_rid2sd(st); // Attached player + + if( sd == NULL ) // no target + return 0; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + else + nameid=UNKNOWN_ITEM_ID; + }else + nameid=conv_num(st,data); + + amount=script_getnum(st,3); + iden=script_getnum(st,4); + ref=script_getnum(st,5); + attr=script_getnum(st,6); + c1=(short)script_getnum(st,7); + c2=(short)script_getnum(st,8); + c3=(short)script_getnum(st,9); + c4=(short)script_getnum(st,10); + + if(nameid<0) { // Invalide nameid + nameid = -nameid; + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_data=itemdb_exists(nameid); + if (item_data == NULL) + return -1; + if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR){ + if(ref > MAX_REFINE) ref = MAX_REFINE; + } + else if(item_data->type==IT_PETEGG) { + iden = 1; + ref = 0; + } + else { + iden = 1; + ref = attr = 0; + } + + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=iden; + else if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR) + item_tmp.identify=0; + item_tmp.refine=ref; + item_tmp.attribute=attr; + item_tmp.card[0]=(short)c1; + item_tmp.card[1]=(short)c2; + item_tmp.card[2]=(short)c3; + item_tmp.card[3]=(short)c4; + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = amount; + + for (i = 0; i < amount; i += get_count) + { + // if not pet egg + if (!pet_create_egg(sd, nameid)) + { + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) + { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&item_tmp) ) + map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + } + + return 0; +} + +/*========================================== + * rentitem <item id>,<seconds> + * rentitem "<item name>",<seconds> + *------------------------------------------*/ +BUILDIN_FUNC(rentitem) +{ + struct map_session_data *sd; + struct script_data *data; + struct item it; + int seconds; + int nameid = 0, flag; + + data = script_getdata(st,2); + get_val(st,data); + + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + + if( data_isstring(data) ) + { + const char *name = conv_str(st,data); + struct item_data *itd = itemdb_searchname(name); + if( itd == NULL ) + { + ShowError("buildin_rentitem: Nonexistant item %s requested.\n", name); + return 1; + } + nameid = itd->nameid; + } + else if( data_isint(data) ) + { + nameid = conv_num(st,data); + if( nameid <= 0 || !itemdb_exists(nameid) ) + { + ShowError("buildin_rentitem: Nonexistant item %d requested.\n", nameid); + return 1; + } + } + else + { + ShowError("buildin_rentitem: invalid data type for argument #1 (%d).\n", data->type); + return 1; + } + + seconds = script_getnum(st,3); + memset(&it, 0, sizeof(it)); + it.nameid = nameid; + it.identify = 1; + it.expire_time = (unsigned int)(time(NULL) + seconds); + + if( (flag = pc_additem(sd, &it, 1, LOG_TYPE_SCRIPT)) ) + { + clif_additem(sd, 0, 0, flag); + return 1; + } + + return 0; +} + +/*========================================== + * gets an item with someone's name inscribed [Skotlex] + * getinscribeditem item_num, character_name + * Returned Qty is always 1, only works on equip-able + * equipment + *------------------------------------------*/ +BUILDIN_FUNC(getnameditem) +{ + int nameid; + struct item item_tmp; + TBL_PC *sd, *tsd; + struct script_data *data; + + sd = script_rid2sd(st); + if (sd == NULL) + { //Player not attached! + script_pushint(st,0); + return 0; + } + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data == NULL) + { //Failed + script_pushint(st,0); + return 0; + } + nameid = item_data->nameid; + }else + nameid = conv_num(st,data); + + if(!itemdb_exists(nameid)/* || itemdb_isstackable(nameid)*/) + { //Even though named stackable items "could" be risky, they are required for certain quests. + script_pushint(st,0); + return 0; + } + + data=script_getdata(st,3); + get_val(st,data); + if( data_isstring(data) ) //Char Name + tsd=map_nick2sd(conv_str(st,data)); + else //Char Id was given + tsd=map_charid2sd(conv_num(st,data)); + + if( tsd == NULL ) + { //Failed + script_pushint(st,0); + return 0; + } + + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + item_tmp.amount=1; + item_tmp.identify=1; + item_tmp.card[0]=CARD0_CREATE; //we don't use 255! because for example SIGNED WEAPON shouldn't get TOP10 BS Fame bonus [Lupus] + item_tmp.card[2]=tsd->status.char_id; + item_tmp.card[3]=tsd->status.char_id >> 16; + if(pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT)) { + script_pushint(st,0); + return 0; //Failed to add item, we will not drop if they don't fit + } + + script_pushint(st,1); + return 0; +} + +/*========================================== + * gets a random item ID from an item group [Skotlex] + * groupranditem group_num + *------------------------------------------*/ +BUILDIN_FUNC(grouprandomitem) +{ + int group; + + group = script_getnum(st,2); + script_pushint(st,itemdb_searchrandomid(group)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(makeitem) +{ + int nameid,amount,flag = 0; + int x,y,m; + const char *mapname; + struct item item_tmp; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + else + nameid=UNKNOWN_ITEM_ID; + }else + nameid=conv_num(st,data); + + amount=script_getnum(st,3); + mapname =script_getstr(st,4); + x =script_getnum(st,5); + y =script_getnum(st,6); + + if(strcmp(mapname,"this")==0) + { + TBL_PC *sd; + sd = script_rid2sd(st); + if (!sd) return 0; //Failed... + m=sd->bl.m; + } else + m=map_mapname2mapid(mapname); + + if(nameid<0) { + nameid = -nameid; + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=1; + else + item_tmp.identify=itemdb_isidentified(nameid); + + map_addflooritem(&item_tmp,amount,m,x,y,0,0,0,0); + } + + return 0; +} + + +/// Counts / deletes the current item given by idx. +/// Used by buildin_delitem_search +/// Relies on all input data being already fully valid. +static void buildin_delitem_delete(struct map_session_data* sd, int idx, int* amount, bool delete_items) +{ + int delamount; + struct item* inv = &sd->status.inventory[idx]; + + delamount = ( amount[0] < inv->amount ) ? amount[0] : inv->amount; + + if( delete_items ) + { + if( sd->inventory_data[idx]->type == IT_PETEGG && inv->card[0] == CARD0_PET ) + {// delete associated pet + intif_delete_petdata(MakeDWord(inv->card[1], inv->card[2])); + } + pc_delitem(sd, idx, delamount, 0, 0, LOG_TYPE_SCRIPT); + } + + amount[0]-= delamount; +} + + +/// Searches for item(s) and checks, if there is enough of them. +/// Used by delitem and delitem2 +/// Relies on all input data being already fully valid. +/// @param exact_match will also match item attributes and cards, not just name id +/// @return true when all items could be deleted, false when there were not enough items to delete +static bool buildin_delitem_search(struct map_session_data* sd, struct item* it, bool exact_match) +{ + bool delete_items = false; + int i, amount, important; + struct item* inv; + + // prefer always non-equipped items + it->equip = 0; + + // when searching for nameid only, prefer additionally + if( !exact_match ) + { + // non-refined items + it->refine = 0; + // card-less items + memset(it->card, 0, sizeof(it->card)); + } + + for(;;) + { + amount = it->amount; + important = 0; + + // 1st pass -- less important items / exact match + for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) + { + inv = &sd->status.inventory[i]; + + if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) + {// wrong/invalid item + continue; + } + + if( inv->equip != it->equip || inv->refine != it->refine ) + {// not matching attributes + important++; + continue; + } + + if( exact_match ) + { + if( inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// not matching exact attributes + continue; + } + } + else + { + if( sd->inventory_data[i]->type == IT_PETEGG ) + { + if( inv->card[0] == CARD0_PET && CheckForCharServer() ) + {// pet which cannot be deleted + continue; + } + } + else if( memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// named/carded item + important++; + continue; + } + } + + // count / delete item + buildin_delitem_delete(sd, i, &amount, delete_items); + } + + // 2nd pass -- any matching item + if( amount == 0 || important == 0 ) + {// either everything was already consumed or no items were skipped + ; + } + else for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) + { + inv = &sd->status.inventory[i]; + + if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) + {// wrong/invalid item + continue; + } + + if( sd->inventory_data[i]->type == IT_PETEGG && inv->card[0] == CARD0_PET && CheckForCharServer() ) + {// pet which cannot be deleted + continue; + } + + if( exact_match ) + { + if( inv->refine != it->refine || inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// not matching attributes + continue; + } + } + + // count / delete item + buildin_delitem_delete(sd, i, &amount, delete_items); + } + + if( amount ) + {// not enough items + return false; + } + else if( delete_items ) + {// we are done with the work + return true; + } + else + {// get rid of the items now + delete_items = true; + } + } +} + + +/// Deletes items from the target/attached player. +/// Prioritizes ordinary items. +/// +/// delitem <item id>,<amount>{,<account id>} +/// delitem "<item name>",<amount>{,<account id>} +BUILDIN_FUNC(delitem) +{ + TBL_PC *sd; + struct item it; + struct script_data *data; + + if( script_hasdata(st,4) ) + { + int account_id = script_getnum(st,4); + sd = map_id2sd(account_id); // <account id> + if( sd == NULL ) + { + ShowError("script:delitem: player not found (AID=%d).\n", account_id); + st->state = END; + return 1; + } + } + else + { + sd = script_rid2sd(st);// attached player + if( sd == NULL ) + return 0; + } + + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + { + const char* item_name = conv_str(st,data); + struct item_data* id = itemdb_searchname(item_name); + if( id == NULL ) + { + ShowError("script:delitem: unknown item \"%s\".\n", item_name); + st->state = END; + return 1; + } + it.nameid = id->nameid;// "<item name>" + } + else + { + it.nameid = conv_num(st,data);// <item id> + if( !itemdb_exists( it.nameid ) ) + { + ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); + st->state = END; + return 1; + } + } + + it.amount=script_getnum(st,3); + + if( it.amount <= 0 ) + return 0;// nothing to do + + if( buildin_delitem_search(sd, &it, false) ) + {// success + return 0; + } + + ShowError("script:delitem: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); + st->state = END; + clif_scriptclose(sd, st->oid); + return 1; +} + +/// Deletes items from the target/attached player. +/// +/// delitem2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} +/// delitem2 "<Item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} +BUILDIN_FUNC(delitem2) +{ + TBL_PC *sd; + struct item it; + struct script_data *data; + + if( script_hasdata(st,11) ) + { + int account_id = script_getnum(st,11); + sd = map_id2sd(account_id); // <account id> + if( sd == NULL ) + { + ShowError("script:delitem2: player not found (AID=%d).\n", account_id); + st->state = END; + return 1; + } + } + else + { + sd = script_rid2sd(st);// attached player + if( sd == NULL ) + return 0; + } + + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + { + const char* item_name = conv_str(st,data); + struct item_data* id = itemdb_searchname(item_name); + if( id == NULL ) + { + ShowError("script:delitem2: unknown item \"%s\".\n", item_name); + st->state = END; + return 1; + } + it.nameid = id->nameid;// "<item name>" + } + else + { + it.nameid = conv_num(st,data);// <item id> + if( !itemdb_exists( it.nameid ) ) + { + ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); + st->state = END; + return 1; + } + } + + it.amount=script_getnum(st,3); + it.identify=script_getnum(st,4); + it.refine=script_getnum(st,5); + it.attribute=script_getnum(st,6); + it.card[0]=(short)script_getnum(st,7); + it.card[1]=(short)script_getnum(st,8); + it.card[2]=(short)script_getnum(st,9); + it.card[3]=(short)script_getnum(st,10); + + if( it.amount <= 0 ) + return 0;// nothing to do + + if( buildin_delitem_search(sd, &it, true) ) + {// success + return 0; + } + + ShowError("script:delitem2: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); + st->state = END; + clif_scriptclose(sd, st->oid); + return 1; +} + +/*========================================== + * Enables/Disables use of items while in an NPC [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(enableitemuse) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if (sd) + sd->npc_item_flag = st->oid; + return 0; +} + +BUILDIN_FUNC(disableitemuse) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if (sd) + sd->npc_item_flag = 0; + return 0; +} + +/*========================================== + * return the basic stats of sd + * chk pc_readparam for available type + *------------------------------------------*/ +BUILDIN_FUNC(readparam) +{ + int type; + TBL_PC *sd; + + type=script_getnum(st,2); + if( script_hasdata(st,3) ) + sd=map_nick2sd(script_getstr(st,3)); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + script_pushint(st,-1); + return 0; + } + + script_pushint(st,pc_readparam(sd,type)); + + return 0; +} + +/*========================================== + * Return charid identification + * return by @num : + * 0 : char_id + * 1 : party_id + * 2 : guild_id + * 3 : account_id + * 4 : bg_id + *------------------------------------------*/ +BUILDIN_FUNC(getcharid) +{ + int num; + TBL_PC *sd; + + num = script_getnum(st,2); + if( script_hasdata(st,3) ) + sd=map_nick2sd(script_getstr(st,3)); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + script_pushint(st,0); //return 0, according docs + return 0; + } + + switch( num ) { + case 0: script_pushint(st,sd->status.char_id); break; + case 1: script_pushint(st,sd->status.party_id); break; + case 2: script_pushint(st,sd->status.guild_id); break; + case 3: script_pushint(st,sd->status.account_id); break; + case 4: script_pushint(st,sd->bg_id); break; + default: + ShowError("buildin_getcharid: invalid parameter (%d).\n", num); + script_pushint(st,0); + break; + } + + return 0; +} +/*========================================== + * returns the GID of an NPC + *------------------------------------------*/ +BUILDIN_FUNC(getnpcid) +{ + int num = script_getnum(st,2); + struct npc_data* nd = NULL; + + if( script_hasdata(st,3) ) + {// unique npc name + if( ( nd = npc_name2id(script_getstr(st,3)) ) == NULL ) + { + ShowError("buildin_getnpcid: No such NPC '%s'.\n", script_getstr(st,3)); + script_pushint(st,0); + return 1; + } + } + + switch (num) { + case 0: + script_pushint(st,nd ? nd->bl.id : st->oid); + break; + default: + ShowError("buildin_getnpcid: invalid parameter (%d).\n", num); + script_pushint(st,0); + return 1; + } + + return 0; +} + +/*========================================== + * Return the name of the party_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getpartyname) +{ + int party_id; + struct party_data* p; + + party_id = script_getnum(st,2); + + if( ( p = party_search(party_id) ) != NULL ) + { + script_pushstrcopy(st,p->party.name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +/*========================================== + * Get the information of the members of a party by type + * @party_id, @type + * return by @type : + * - : nom des membres + * 1 : char_id des membres + * 2 : account_id des membres + *------------------------------------------*/ +BUILDIN_FUNC(getpartymember) +{ + struct party_data *p; + int i,j=0,type=0; + + p=party_search(script_getnum(st,2)); + + if( script_hasdata(st,3) ) + type=script_getnum(st,3); + + if(p!=NULL){ + for(i=0;i<MAX_PARTY;i++){ + if(p->party.member[i].account_id){ + switch (type) { + case 2: + mapreg_setreg(reference_uid(add_str("$@partymemberaid"), j),p->party.member[i].account_id); + break; + case 1: + mapreg_setreg(reference_uid(add_str("$@partymembercid"), j),p->party.member[i].char_id); + break; + default: + mapreg_setregstr(reference_uid(add_str("$@partymembername$"), j),p->party.member[i].name); + } + j++; + } + } + } + mapreg_setreg(add_str("$@partymembercount"),j); + + return 0; +} + +/*========================================== + * Retrieves party leader. if flag is specified, + * return some of the leader data. Otherwise, return name. + *------------------------------------------*/ +BUILDIN_FUNC(getpartyleader) +{ + int party_id, type = 0, i=0; + struct party_data *p; + + party_id=script_getnum(st,2); + if( script_hasdata(st,3) ) + type=script_getnum(st,3); + + p=party_search(party_id); + + if (p) //Search leader + for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); + + if (!p || i == MAX_PARTY) { //leader not found + if (type) + script_pushint(st,-1); + else + script_pushconststr(st,"null"); + return 0; + } + + switch (type) { + case 1: script_pushint(st,p->party.member[i].account_id); break; + case 2: script_pushint(st,p->party.member[i].char_id); break; + case 3: script_pushint(st,p->party.member[i].class_); break; + case 4: script_pushstrcopy(st,mapindex_id2name(p->party.member[i].map)); break; + case 5: script_pushint(st,p->party.member[i].lv); break; + default: script_pushstrcopy(st,p->party.member[i].name); break; + } + return 0; +} + +/*========================================== + * Return the name of the @guild_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getguildname) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +/*========================================== + * Return the name of the guild master of @guild_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getguildmaster) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->member[0].name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +BUILDIN_FUNC(getguildmasterid) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushint(st,g->member[0].char_id); + } + else + { + script_pushint(st,0); + } + return 0; +} + +/*========================================== + * Get char string information by type : + * Return by @type : + * 0 : char_name + * 1 : party_name or "" + * 2 : guild_name or "" + * 3 : map_name + * - : "" + *------------------------------------------*/ +BUILDIN_FUNC(strcharinfo) +{ + TBL_PC *sd; + int num; + struct guild* g; + struct party_data* p; + + sd=script_rid2sd(st); + if (!sd) { //Avoid crashing.... + script_pushconststr(st,""); + return 0; + } + num=script_getnum(st,2); + switch(num){ + case 0: + script_pushstrcopy(st,sd->status.name); + break; + case 1: + if( ( p = party_search(sd->status.party_id) ) != NULL ) + { + script_pushstrcopy(st,p->party.name); + } + else + { + script_pushconststr(st,""); + } + break; + case 2: + if( ( g = guild_search(sd->status.guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->name); + } + else + { + script_pushconststr(st,""); + } + break; + case 3: + script_pushconststr(st,map[sd->bl.m].name); + break; + default: + ShowWarning("buildin_strcharinfo: unknown parameter.\n"); + script_pushconststr(st,""); + break; + } + + return 0; +} + +/*========================================== + * Get npc string information by type + * return by @type: + * 0 : name + * 1 : str# + * 2 : #str + * 3 : ::str + * 4 : map name + *------------------------------------------*/ +BUILDIN_FUNC(strnpcinfo) +{ + TBL_NPC* nd; + int num; + char *buf,*name=NULL; + + nd = map_id2nd(st->oid); + if (!nd) { + script_pushconststr(st, ""); + return 0; + } + + num = script_getnum(st,2); + switch(num){ + case 0: // display name + name = aStrdup(nd->name); + break; + case 1: // visible part of display name + if((buf = strchr(nd->name,'#')) != NULL) + { + name = aStrdup(nd->name); + name[buf - nd->name] = 0; + } else // Return the name, there is no '#' present + name = aStrdup(nd->name); + break; + case 2: // # fragment + if((buf = strchr(nd->name,'#')) != NULL) + name = aStrdup(buf+1); + break; + case 3: // unique name + name = aStrdup(nd->exname); + break; + case 4: // map name + name = aStrdup(map[nd->bl.m].name); + break; + } + + if(name) + script_pushstr(st, name); + else + script_pushconststr(st, ""); + + return 0; +} + + +// aegis->athena slot position conversion table +static unsigned int equip[] = {EQP_HEAD_TOP,EQP_ARMOR,EQP_HAND_L,EQP_HAND_R,EQP_GARMENT,EQP_SHOES,EQP_ACC_L,EQP_ACC_R,EQP_HEAD_MID,EQP_HEAD_LOW}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------*/ +BUILDIN_FUNC(getequipid) +{ + int i, num; + TBL_PC* sd; + struct item_data* item; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num = script_getnum(st,2) - 1; + if( num < 0 || num >= ARRAYLENGTH(equip) ) + { + script_pushint(st,-1); + return 0; + } + + // get inventory position of item + i = pc_checkequip(sd,equip[num]); + if( i < 0 ) + { + script_pushint(st,-1); + return 0; + } + + item = sd->inventory_data[i]; + if( item != 0 ) + script_pushint(st,item->nameid); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the equipement name at pos + * return item jname or "" + *------------------------------------------*/ +BUILDIN_FUNC(getequipname) +{ + int i, num; + TBL_PC* sd; + struct item_data* item; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num = script_getnum(st,2) - 1; + if( num < 0 || num >= ARRAYLENGTH(equip) ) + { + script_pushconststr(st,""); + return 0; + } + + // get inventory position of item + i = pc_checkequip(sd,equip[num]); + if( i < 0 ) + { + script_pushint(st,-1); + return 0; + } + + item = sd->inventory_data[i]; + if( item != 0 ) + script_pushstrcopy(st,item->jname); + else + script_pushconststr(st,""); + + return 0; +} + +/*========================================== + * getbrokenid [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(getbrokenid) +{ + int i,num,id=0,brokencounter=0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num=script_getnum(st,2); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].attribute){ + brokencounter++; + if(num==brokencounter){ + id=sd->status.inventory[i].nameid; + break; + } + } + } + + script_pushint(st,id); + + return 0; +} + +/*========================================== + * repair [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(repair) +{ + int i,num; + int repaircounter=0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num=script_getnum(st,2); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].attribute){ + repaircounter++; + if(num==repaircounter){ + sd->status.inventory[i].attribute=0; + clif_equiplist(sd); + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + clif_misceffect(&sd->bl, 3); + break; + } + } + } + + return 0; +} + +/*========================================== + * repairall + *------------------------------------------*/ +BUILDIN_FUNC(repairall) +{ + int i, repaircounter = 0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if(sd == NULL) + return 0; + + for(i = 0; i < MAX_INVENTORY; i++) + { + if(sd->status.inventory[i].nameid && sd->status.inventory[i].attribute) + { + sd->status.inventory[i].attribute = 0; + clif_produceeffect(sd,0,sd->status.inventory[i].nameid); + repaircounter++; + } + } + + if(repaircounter) + { + clif_misceffect(&sd->bl, 3); + clif_equiplist(sd); + } + + return 0; +} + +/*========================================== + * Chk if player have something equiped at pos + *------------------------------------------*/ +BUILDIN_FUNC(getequipisequiped) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if(i >= 0) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} + +/*========================================== + * Chk if the player have something equiped at pos + * if so chk if this item ain't marked not refinable or rental + * return (npc) + * 1 : true + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipisenableref) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( num > 0 && num <= ARRAYLENGTH(equip) ) + i = pc_checkequip(sd,equip[num-1]); + if( i >= 0 && sd->inventory_data[i] && !sd->inventory_data[i]->flag.no_refine && !sd->status.inventory[i].expire_time ) + script_pushint(st,1); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Chk if the item equiped at pos is identify (huh ?) + * return (npc) + * 1 : true + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipisidentify) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + script_pushint(st,sd->status.inventory[i].identify); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the item refined value at pos + * return (npc) + * x : refine amount + * 0 : false (not refined) + *------------------------------------------*/ +BUILDIN_FUNC(getequiprefinerycnt) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + script_pushint(st,sd->status.inventory[i].refine); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the weapon level value at pos + * (pos should normally only be EQI_HAND_L or EQI_HAND_R) + * return (npc) + * x : weapon level + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipweaponlv) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->inventory_data[i]) + script_pushint(st,sd->inventory_data[i]->wlv); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the item refine chance (from refine.txt) for item at pos + * return (npc) + * x : refine chance + * 0 : false (max refine level or unequip..) + *------------------------------------------*/ +BUILDIN_FUNC(getequippercentrefinery) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->status.inventory[i].nameid && sd->status.inventory[i].refine < MAX_REFINE) + script_pushint(st,status_get_refine_chance(itemdb_wlv(sd->status.inventory[i].nameid), (int)sd->status.inventory[i].refine)); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Refine +1 item at pos and log and display refine + *------------------------------------------*/ +BUILDIN_FUNC(successrefitem) +{ + int i=-1,num,ep; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep=sd->status.inventory[i].equip; + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below + + clif_refine(sd->fd,0,i,sd->status.inventory[i].refine); + clif_delitem(sd,i,1,3); + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); + + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,3); + if(sd->status.inventory[i].refine == MAX_REFINE && + sd->status.inventory[i].card[0] == CARD0_FORGE && + sd->status.char_id == (int)MakeDWord(sd->status.inventory[i].card[2],sd->status.inventory[i].card[3]) + ){ // Fame point system [DracoRPG] + switch (sd->inventory_data[i]->wlv){ + case 1: + pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point + break; + case 2: + pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point + break; + case 3: + pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point + break; + } + } + } + + return 0; +} + +/*========================================== + * Show a failed Refine +1 attempt + *------------------------------------------*/ +BUILDIN_FUNC(failedrefitem) +{ + int i=-1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + sd->status.inventory[i].refine = 0; + pc_unequipitem(sd,i,3); //recalculate bonus + clif_refine(sd->fd,1,i,sd->status.inventory[i].refine); //notify client of failure + + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + + clif_misceffect(&sd->bl,2); // display failure effect + } + + return 0; +} + +/*========================================== + * Downgrades an Equipment Part by -1 . [Masao] + *------------------------------------------*/ +BUILDIN_FUNC(downrefitem) +{ + int i = -1,num,ep; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i = pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep = sd->status.inventory[i].equip; + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below + + clif_refine(sd->fd,2,i,sd->status.inventory[i].refine = sd->status.inventory[i].refine - 2); + clif_delitem(sd,i,1,3); + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); + + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(statusup) +{ + int type; + TBL_PC *sd; + + type=script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_statusup(sd,type); + + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(statusup2) +{ + int type,val; + TBL_PC *sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_statusup2(sd,type,val); + + return 0; +} + +/// See 'doc/item_bonus.txt' +/// +/// bonus <bonus type>,<val1>; +/// bonus2 <bonus type>,<val1>,<val2>; +/// bonus3 <bonus type>,<val1>,<val2>,<val3>; +/// bonus4 <bonus type>,<val1>,<val2>,<val3>,<val4>; +/// bonus5 <bonus type>,<val1>,<val2>,<val3>,<val4>,<val5>; +BUILDIN_FUNC(bonus) +{ + int type; + int val1; + int val2 = 0; + int val3 = 0; + int val4 = 0; + int val5 = 0; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + type = script_getnum(st,2); + switch( type ) { + case SP_AUTOSPELL: + case SP_AUTOSPELL_WHENHIT: + case SP_AUTOSPELL_ONSKILL: + case SP_SKILL_ATK: + case SP_SKILL_HEAL: + case SP_SKILL_HEAL2: + case SP_ADD_SKILL_BLOW: + case SP_CASTRATE: + case SP_ADDEFF_ONSKILL: + case SP_SKILL_USE_SP_RATE: + case SP_SKILL_COOLDOWN: + case SP_SKILL_FIXEDCAST: + case SP_SKILL_VARIABLECAST: + case SP_VARCASTRATE: + case SP_SKILL_USE_SP: + // these bonuses support skill names + val1 = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + break; + default: + val1 = script_getnum(st,3); + break; + } + + switch( script_lastdata(st)-2 ) { + case 1: + pc_bonus(sd, type, val1); + break; + case 2: + val2 = script_getnum(st,4); + pc_bonus2(sd, type, val1, val2); + break; + case 3: + val2 = script_getnum(st,4); + val3 = script_getnum(st,5); + pc_bonus3(sd, type, val1, val2, val3); + break; + case 4: + if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) + val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name + else + val2 = script_getnum(st,4); + + val3 = script_getnum(st,5); + val4 = script_getnum(st,6); + pc_bonus4(sd, type, val1, val2, val3, val4); + break; + case 5: + if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) + val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name + else + val2 = script_getnum(st,4); + + val3 = script_getnum(st,5); + val4 = script_getnum(st,6); + val5 = script_getnum(st,7); + pc_bonus5(sd, type, val1, val2, val3, val4, val5); + break; + default: + ShowDebug("buildin_bonus: unexpected number of arguments (%d)\n", (script_lastdata(st) - 1)); + break; + } + + return 0; +} + +BUILDIN_FUNC(autobonus) +{ + unsigned int dur; + short rate; + short atk_type = 0; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !bonus_script ) + return 0; + + if( script_hasdata(st,5) ) + atk_type = script_getnum(st,5); + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus,ARRAYLENGTH(sd->autobonus), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +BUILDIN_FUNC(autobonus2) +{ + unsigned int dur; + short rate; + short atk_type = 0; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !bonus_script ) + return 0; + + if( script_hasdata(st,5) ) + atk_type = script_getnum(st,5); + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus2,ARRAYLENGTH(sd->autobonus2), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +BUILDIN_FUNC(autobonus3) +{ + unsigned int dur; + short rate,atk_type; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + atk_type = ( script_isstring(st,5) ? skill_name2id(script_getstr(st,5)) : script_getnum(st,5) ); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !atk_type || !bonus_script ) + return 0; + + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus3,ARRAYLENGTH(sd->autobonus3), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,true) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +/// Changes the level of a player skill. +/// <flag> defaults to 1 +/// <flag>=0 : set the level of the skill +/// <flag>=1 : set the temporary level of the skill +/// <flag>=2 : add to the level of the skill +/// +/// skill <skill id>,<level>,<flag> +/// skill <skill id>,<level> +/// skill "<skill name>",<level>,<flag> +/// skill "<skill name>",<level> +BUILDIN_FUNC(skill) +{ + int id; + int level; + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + if( script_hasdata(st,4) ) + flag = script_getnum(st,4); + pc_skill(sd, id, level, flag); + + return 0; +} + +/// Changes the level of a player skill. +/// like skill, but <flag> defaults to 2 +/// +/// addtoskill <skill id>,<amount>,<flag> +/// addtoskill <skill id>,<amount> +/// addtoskill "<skill name>",<amount>,<flag> +/// addtoskill "<skill name>",<amount> +/// +/// @see skill +BUILDIN_FUNC(addtoskill) +{ + int id; + int level; + int flag = 2; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + if( script_hasdata(st,4) ) + flag = script_getnum(st,4); + pc_skill(sd, id, level, flag); + + return 0; +} + +/// Increases the level of a guild skill. +/// +/// guildskill <skill id>,<amount>; +/// guildskill "<skill name>",<amount>; +BUILDIN_FUNC(guildskill) +{ + int id; + int level; + TBL_PC* sd; + int i; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + for( i=0; i < level; i++ ) + guild_skillup(sd, id); + + return 0; +} + +/// Returns the level of the player skill. +/// +/// getskilllv(<skill id>) -> <level> +/// getskilllv("<skill name>") -> <level> +BUILDIN_FUNC(getskilllv) +{ + int id; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + script_pushint(st, pc_checkskill(sd,id)); + + return 0; +} + +/// Returns the level of the guild skill. +/// +/// getgdskilllv(<guild id>,<skill id>) -> <level> +/// getgdskilllv(<guild id>,"<skill name>") -> <level> +BUILDIN_FUNC(getgdskilllv) +{ + int guild_id; + uint16 skill_id; + struct guild* g; + + guild_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + g = guild_search(guild_id); + if( g == NULL ) + script_pushint(st, -1); + else + script_pushint(st, guild_checkskill(g,skill_id)); + + return 0; +} + +/// Returns the 'basic_skill_check' setting. +/// This config determines if the server checks the skill level of NV_BASIC +/// before allowing the basic actions. +/// +/// basicskillcheck() -> <bool> +BUILDIN_FUNC(basicskillcheck) +{ + script_pushint(st, battle_config.basic_skill_check); + return 0; +} + +/// Returns the GM level of the player. +/// +/// getgmlevel() -> <level> +BUILDIN_FUNC(getgmlevel) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + script_pushint(st, pc_get_group_level(sd)); + + return 0; +} + +/// Returns the group ID of the player. +/// +/// getgroupid() -> <int> +BUILDIN_FUNC(getgroupid) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if (sd == NULL) + return 1; // no player attached, report source + script_pushint(st, pc_get_group_id(sd)); + + return 0; +} + +/// Terminates the execution of this script instance. +/// +/// end +BUILDIN_FUNC(end) +{ + st->state = END; + return 0; +} + +/// Checks if the player has that effect state (option). +/// +/// checkoption(<option>) -> <bool> +BUILDIN_FUNC(checkoption) +{ + int option; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + option = script_getnum(st,2); + if( sd->sc.option&option ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Checks if the player is in that body state (opt1). +/// +/// checkoption1(<opt1>) -> <bool> +BUILDIN_FUNC(checkoption1) +{ + int opt1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + opt1 = script_getnum(st,2); + if( sd->sc.opt1 == opt1 ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Checks if the player has that health state (opt2). +/// +/// checkoption2(<opt2>) -> <bool> +BUILDIN_FUNC(checkoption2) +{ + int opt2; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + opt2 = script_getnum(st,2); + if( sd->sc.opt2&opt2 ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Changes the effect state (option) of the player. +/// <flag> defaults to 1 +/// <flag>=0 : removes the option +/// <flag>=other : adds the option +/// +/// setoption <option>,<flag>; +/// setoption <option>; +BUILDIN_FUNC(setoption) +{ + int option; + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + option = script_getnum(st,2); + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + else if( !option ){// Request to remove everything. + flag = 0; + option = OPTION_FALCON|OPTION_RIDING; +#ifndef NEW_CARTS + option |= OPTION_CART; +#endif + } + if( flag ){// Add option + if( option&OPTION_WEDDING && !battle_config.wedding_modifydisplay ) + option &= ~OPTION_WEDDING;// Do not show the wedding sprites + pc_setoption(sd, sd->sc.option|option); + } else// Remove option + pc_setoption(sd, sd->sc.option&~option); + + return 0; +} + +/// Returns if the player has a cart. +/// +/// checkcart() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkcart) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_iscarton(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets the cart of the player. +/// <type> defaults to 1 +/// <type>=0 : removes the cart +/// <type>=1 : Normal cart +/// <type>=2 : Wooden cart +/// <type>=3 : Covered cart with flowers and ferns +/// <type>=4 : Wooden cart with a Panda doll on the back +/// <type>=5 : Normal cart with bigger wheels, a roof and a banner on the back +/// +/// setcart <type>; +/// setcart; +BUILDIN_FUNC(setcart) +{ + int type = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + type = script_getnum(st,2); + pc_setcart(sd, type); + + return 0; +} + +/// Returns if the player has a falcon. +/// +/// checkfalcon() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkfalcon) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_isfalcon(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player has a falcon or not. +/// <flag> defaults to 1 +/// +/// setfalcon <flag>; +/// setfalcon; +BUILDIN_FUNC(setfalcon) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + + pc_setfalcon(sd, flag); + + return 0; +} + +/// Returns if the player is riding. +/// +/// checkriding() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkriding) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_isriding(sd) || pc_isridingwug(sd) || pc_isridingdragon(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player is riding. +/// <flag> defaults to 1 +/// +/// setriding <flag>; +/// setriding; +BUILDIN_FUNC(setriding) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + pc_setriding(sd, flag); + + return 0; +} + +/// Returns if the player has a warg. +/// +/// checkwug() -> <bool> +/// +BUILDIN_FUNC(checkwug) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_iswug(sd) || pc_isridingwug(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Returns if the player is wearing MADO Gear. +/// +/// checkmadogear() -> <bool> +/// +BUILDIN_FUNC(checkmadogear) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_ismadogear(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player is riding MADO Gear. +/// <flag> defaults to 1 +/// +/// setmadogear <flag>; +/// setmadogear; +BUILDIN_FUNC(setmadogear) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + pc_setmadogear(sd, flag); + + return 0; +} + +/// Sets the save point of the player. +/// +/// save "<map name>",<x>,<y> +/// savepoint "<map name>",<x>,<y> +BUILDIN_FUNC(savepoint) +{ + int x; + int y; + short map; + const char* str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + str = script_getstr(st, 2); + x = script_getnum(st,3); + y = script_getnum(st,4); + map = mapindex_name2id(str); + if( map ) + pc_setsavepoint(sd, map, x, y); + + return 0; +} + +/*========================================== + * GetTimeTick(0: System Tick, 1: Time Second Tick) + *------------------------------------------*/ +BUILDIN_FUNC(gettimetick) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=script_getnum(st,2); + + switch(type){ + case 2: + //type 2:(Get the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC + // from the system clock.) + script_pushint(st,(int)time(NULL)); + break; + case 1: + //type 1:(Second Ticks: 0-86399, 00:00:00-23:59:59) + time(&timer); + t=localtime(&timer); + script_pushint(st,((t->tm_hour)*3600+(t->tm_min)*60+t->tm_sec)); + break; + case 0: + default: + //type 0:(System Ticks) + script_pushint(st,gettick()); + break; + } + return 0; +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------*/ +BUILDIN_FUNC(gettime) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=script_getnum(st,2); + + time(&timer); + t=localtime(&timer); + + switch(type){ + case 1://Sec(0~59) + script_pushint(st,t->tm_sec); + break; + case 2://Min(0~59) + script_pushint(st,t->tm_min); + break; + case 3://Hour(0~23) + script_pushint(st,t->tm_hour); + break; + case 4://WeekDay(0~6) + script_pushint(st,t->tm_wday); + break; + case 5://MonthDay(01~31) + script_pushint(st,t->tm_mday); + break; + case 6://Month(01~12) + script_pushint(st,t->tm_mon+1); + break; + case 7://Year(20xx) + script_pushint(st,t->tm_year+1900); + break; + case 8://Year Day(01~366) + script_pushint(st,t->tm_yday+1); + break; + default://(format error) + script_pushint(st,-1); + break; + } + return 0; +} + +/*========================================== + * GetTimeStr("TimeFMT", Length); + *------------------------------------------*/ +BUILDIN_FUNC(gettimestr) +{ + char *tmpstr; + const char *fmtstr; + int maxlen; + time_t now = time(NULL); + + fmtstr=script_getstr(st,2); + maxlen=script_getnum(st,3); + + tmpstr=(char *)aMalloc((maxlen+1)*sizeof(char)); + strftime(tmpstr,maxlen,fmtstr,localtime(&now)); + tmpstr[maxlen]='\0'; + + script_pushstr(st,tmpstr); + return 0; +} + +/*========================================== + * Open player storage + *------------------------------------------*/ +BUILDIN_FUNC(openstorage) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + storage_storageopen(sd); + return 0; +} + +BUILDIN_FUNC(guildopenstorage) +{ + TBL_PC* sd; + int ret; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + ret = storage_guild_storageopen(sd); + script_pushint(st,ret); + return 0; +} + +/*========================================== + * Make player use a skill trought item usage + *------------------------------------------*/ +/// itemskill <skill id>,<level> +/// itemskill "<skill name>",<level> +BUILDIN_FUNC(itemskill) +{ + int id; + int lv; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL || sd->ud.skilltimer != INVALID_TIMER ) + return 0; + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + lv = script_getnum(st,3); + + sd->skillitem=id; + sd->skillitemlv=lv; + clif_item_skill(sd,id,lv); + return 0; +} +/*========================================== + * Attempt to create an item + *------------------------------------------*/ +BUILDIN_FUNC(produce) +{ + int trigger; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + trigger=script_getnum(st,2); + clif_skill_produce_mix_list(sd, -1, trigger); + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(cooking) +{ + int trigger; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + trigger=script_getnum(st,2); + clif_cooking_list(sd, trigger, AM_PHARMACY, 1, 1); + return 0; +} +/*========================================== + * Create a pet + *------------------------------------------*/ +BUILDIN_FUNC(makepet) +{ + TBL_PC* sd; + int id,pet_id; + + id=script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pet_id = search_petDB_index(id, PET_CLASS); + + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0 && sd) { + sd->catch_target_class = pet_db[pet_id].class_; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } + + return 0; +} +/*========================================== + * Give player exp base,job * quest_exp_rate/100 + *------------------------------------------*/ +BUILDIN_FUNC(getexp) +{ + TBL_PC* sd; + int base=0,job=0; + double bonus; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + base=script_getnum(st,2); + job =script_getnum(st,3); + if(base<0 || job<0) + return 0; + + // bonus for npc-given exp + bonus = battle_config.quest_exp_rate / 100.; + base = (int) cap_value(base * bonus, 0, INT_MAX); + job = (int) cap_value(job * bonus, 0, INT_MAX); + + pc_gainexp(sd, NULL, base, job, true); + + return 0; +} + +/*========================================== + * Gain guild exp [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(guildgetexp) +{ + TBL_PC* sd; + int exp; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + exp = script_getnum(st,2); + if(exp < 0) + return 0; + if(sd && sd->status.guild_id > 0) + guild_getexp (sd, exp); + + return 0; +} + +/*========================================== + * Changes the guild master of a guild [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(guildchangegm) +{ + TBL_PC *sd; + int guild_id; + const char *name; + + guild_id = script_getnum(st,2); + name = script_getstr(st,3); + sd=map_nick2sd(name); + + if (!sd) + script_pushint(st,0); + else + script_pushint(st,guild_gm_change(guild_id, sd)); + + return 0; +} + +/*========================================== + * Spawn a monster : + @mapn,x,y : location + @str : monster name + @class_ : mob_id + @amount : nb to spawn + @event : event to attach to mob + *------------------------------------------*/ +BUILDIN_FUNC(monster) +{ + const char* mapn = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + const char* str = script_getstr(st,5); + int class_ = script_getnum(st,6); + int amount = script_getnum(st,7); + const char* event = ""; + unsigned int size = SZ_SMALL; + unsigned int ai = AI_NONE; + + struct map_session_data* sd; + int16 m; + + if (script_hasdata(st, 8)) + { + event = script_getstr(st, 8); + check_event(st, event); + } + + if (script_hasdata(st, 9)) + { + size = script_getnum(st, 9); + if (size > 3) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); + return 1; + } + } + + if (script_hasdata(st, 10)) + { + ai = script_getnum(st, 10); + if (ai > 4) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); + return 1; + } + } + + if (class_ >= 0 && !mobdb_checkid(class_)) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing monster class %d\n", class_); + return 1; + } + + sd = map_id2sd(st->rid); + + if (sd && strcmp(mapn, "this") == 0) + m = sd->bl.m; + else + { + m = map_mapname2mapid(mapn); + if (map[m].flag.src4instance && st->instance_id) + { // Try to redirect to the instance map, not the src map + if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) + { + ShowError("buildin_monster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); + return 1; + } + } + } + + mob_once_spawn(sd, m, x, y, str, class_, amount, event, size, ai); + return 0; +} +/*========================================== + * Request List of Monster Drops + *------------------------------------------*/ +BUILDIN_FUNC(getmobdrops) +{ + int class_ = script_getnum(st,2); + int i, j = 0; + struct mob_db *mob; + + if( !mobdb_checkid(class_) ) + { + script_pushint(st, 0); + return 0; + } + + mob = mob_db(class_); + + for( i = 0; i < MAX_MOB_DROP; i++ ) + { + if( mob->dropitem[i].nameid < 1 ) + continue; + if( itemdb_exists(mob->dropitem[i].nameid) == NULL ) + continue; + + mapreg_setreg(reference_uid(add_str("$@MobDrop_item"), j), mob->dropitem[i].nameid); + mapreg_setreg(reference_uid(add_str("$@MobDrop_rate"), j), mob->dropitem[i].p); + + j++; + } + + mapreg_setreg(add_str("$@MobDrop_count"), j); + script_pushint(st, 1); + + return 0; +} +/*========================================== + * Same as monster but randomize location in x0,x1,y0,y1 area + *------------------------------------------*/ +BUILDIN_FUNC(areamonster) +{ + const char* mapn = script_getstr(st,2); + int x0 = script_getnum(st,3); + int y0 = script_getnum(st,4); + int x1 = script_getnum(st,5); + int y1 = script_getnum(st,6); + const char* str = script_getstr(st,7); + int class_ = script_getnum(st,8); + int amount = script_getnum(st,9); + const char* event = ""; + unsigned int size = SZ_SMALL; + unsigned int ai = AI_NONE; + + struct map_session_data* sd; + int16 m; + + if (script_hasdata(st,10)) + { + event = script_getstr(st, 10); + check_event(st, event); + } + + if (script_hasdata(st, 11)) + { + size = script_getnum(st, 11); + if (size > 3) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); + return 1; + } + } + + if (script_hasdata(st, 12)) + { + ai = script_getnum(st, 12); + if (ai > 4) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); + return 1; + } + } + + sd = map_id2sd(st->rid); + + if (sd && strcmp(mapn, "this") == 0) + m = sd->bl.m; + else + { + m = map_mapname2mapid(mapn); + if (map[m].flag.src4instance && st->instance_id) + { // Try to redirect to the instance map, not the src map + if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) + { + ShowError("buildin_areamonster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); + return 1; + } + } + } + + mob_once_spawn_area(sd, m, x0, y0, x1, y1, str, class_, amount, event, size, ai); + return 0; +} +/*========================================== + * KillMonster subcheck, verify if mob to kill ain't got an even to handle, could be force kill by allflag + *------------------------------------------*/ + static int buildin_killmonster_sub_strip(struct block_list *bl,va_list ap) +{ //same fix but with killmonster instead - stripping events from mobs. + TBL_MOB* md = (TBL_MOB*)bl; + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + md->state.npc_killmonster = 1; + + if(!allflag){ + if(strcmp(event,md->npc_event)==0) + status_kill(bl); + }else{ + if(!md->spawn) + status_kill(bl); + } + md->state.npc_killmonster = 0; + return 0; +} +static int buildin_killmonster_sub(struct block_list *bl,va_list ap) +{ + TBL_MOB* md = (TBL_MOB*)bl; + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + if(!allflag){ + if(strcmp(event,md->npc_event)==0) + status_kill(bl); + }else{ + if(!md->spawn) + status_kill(bl); + } + return 0; +} +BUILDIN_FUNC(killmonster) +{ + const char *mapname,*event; + int16 m,allflag=0; + mapname=script_getstr(st,2); + event=script_getstr(st,3); + if(strcmp(event,"All")==0) + allflag = 1; + else + check_event(st, event); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + if( script_hasdata(st,4) ) { + if ( script_getnum(st,4) == 1 ) { + map_foreachinmap(buildin_killmonster_sub, m, BL_MOB, event ,allflag); + return 0; + } + } + + map_freeblock_lock(); + map_foreachinmap(buildin_killmonster_sub_strip, m, BL_MOB, event ,allflag); + map_freeblock_unlock(); + return 0; +} + +static int buildin_killmonsterall_sub_strip(struct block_list *bl,va_list ap) +{ //Strips the event from the mob if it's killed the old method. + struct mob_data *md; + + md = BL_CAST(BL_MOB, bl); + if (md->npc_event[0]) + md->npc_event[0] = 0; + + status_kill(bl); + return 0; +} +static int buildin_killmonsterall_sub(struct block_list *bl,va_list ap) +{ + status_kill(bl); + return 0; +} +BUILDIN_FUNC(killmonsterall) +{ + const char *mapname; + int16 m; + mapname=script_getstr(st,2); + + if( (m = map_mapname2mapid(mapname))<0 ) + return 0; + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + if( script_hasdata(st,3) ) { + if ( script_getnum(st,3) == 1 ) { + map_foreachinmap(buildin_killmonsterall_sub,m,BL_MOB); + return 0; + } + } + + map_foreachinmap(buildin_killmonsterall_sub_strip,m,BL_MOB); + return 0; +} + +/*========================================== + * Creates a clone of a player. + * clone map, x, y, event, char_id, master_id, mode, flag, duration + *------------------------------------------*/ +BUILDIN_FUNC(clone) +{ + TBL_PC *sd, *msd=NULL; + int char_id,master_id=0,x,y, mode = 0, flag = 0, m; + unsigned int duration = 0; + const char *map,*event=""; + + map=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + event=script_getstr(st,5); + char_id=script_getnum(st,6); + + if( script_hasdata(st,7) ) + master_id=script_getnum(st,7); + + if( script_hasdata(st,8) ) + mode=script_getnum(st,8); + + if( script_hasdata(st,9) ) + flag=script_getnum(st,9); + + if( script_hasdata(st,10) ) + duration=script_getnum(st,10); + + check_event(st, event); + + m = map_mapname2mapid(map); + if (m < 0) return 0; + + sd = map_charid2sd(char_id); + + if (master_id) { + msd = map_charid2sd(master_id); + if (msd) + master_id = msd->bl.id; + else + master_id = 0; + } + if (sd) //Return ID of newly crafted clone. + script_pushint(st,mob_clone_spawn(sd, m, x, y, event, master_id, mode, flag, 1000*duration)); + else //Failed to create clone. + script_pushint(st,0); + + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(doevent) +{ + const char* event = script_getstr(st,2); + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + check_event(st, event); + npc_event(sd, event, 0); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(donpcevent) +{ + const char* event = script_getstr(st,2); + check_event(st, event); + if( !npc_event_do(event) ) { + struct npc_data * nd = map_id2nd(st->oid); + ShowDebug("NPCEvent '%s' not found! (source: %s)\n",event,nd?nd->name:"Unknown"); + script_pushint(st, 0); + } else + script_pushint(st, 1); + return 0; +} + +/// for Aegis compatibility +/// basically a specialized 'donpcevent', with the event specified as two arguments instead of one +BUILDIN_FUNC(cmdothernpc) // Added by RoVeRT +{ + const char* npc = script_getstr(st,2); + const char* command = script_getstr(st,3); + char event[EVENT_NAME_LENGTH]; + snprintf(event, sizeof(event), "%s::OnCommand%s", npc, command); + check_event(st, event); + npc_event_do(event); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(addtimer) +{ + int tick = script_getnum(st,2); + const char* event = script_getstr(st, 3); + TBL_PC* sd; + + check_event(st, event); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_addeventtimer(sd,tick,event); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(deltimer) +{ + const char *event; + TBL_PC* sd; + + event=script_getstr(st, 2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + check_event(st, event); + pc_deleventtimer(sd,event); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(addtimercount) +{ + const char *event; + int tick; + TBL_PC* sd; + + event=script_getstr(st, 2); + tick=script_getnum(st,3); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + check_event(st, event); + pc_addeventtimercount(sd,event,tick); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(initnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Attach + { + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + nd->u.scr.rid = sd->bl.id; + } + + nd->u.scr.timertick = 0; + npc_settimerevent_tick(nd,0); + npc_timerevent_start(nd, st->rid); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(startnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Attach + { + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + nd->u.scr.rid = sd->bl.id; + } + + npc_timerevent_start(nd, st->rid); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(stopnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Detach + nd->u.scr.rid = 0; + + npc_timerevent_stop(nd); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getnpctimer) +{ + struct npc_data *nd; + TBL_PC *sd; + int type = script_getnum(st,2); + int val = 0; + + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st,3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,0); + ShowError("getnpctimer: Invalid NPC.\n"); + return 1; + } + + switch( type ) + { + case 0: val = npc_gettimerevent_tick(nd); break; + case 1: + if( nd->u.scr.rid ) + { + sd = map_id2sd(nd->u.scr.rid); + if( !sd ) + { + ShowError("buildin_getnpctimer: Attached player not found!\n"); + break; + } + val = (sd->npc_timer_id != INVALID_TIMER); + } + else + val = (nd->u.scr.timerid != INVALID_TIMER); + break; + case 2: val = nd->u.scr.timeramount; break; + } + + script_pushint(st,val); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(setnpctimer) +{ + int tick; + struct npc_data *nd; + + tick = script_getnum(st,2); + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st,3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("setnpctimer: Invalid NPC.\n"); + return 1; + } + + npc_settimerevent_tick(nd,tick); + script_pushint(st,0); + return 0; +} + +/*========================================== + * attaches the player rid to the timer [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(attachnpctimer) +{ + TBL_PC *sd; + struct npc_data *nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("setnpctimer: Invalid NPC.\n"); + return 1; + } + + if( script_hasdata(st,2) ) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if( !sd ) + { + script_pushint(st,1); + ShowWarning("attachnpctimer: Invalid player.\n"); + return 1; + } + + nd->u.scr.rid = sd->bl.id; + script_pushint(st,0); + return 0; +} + +/*========================================== + * detaches a player rid from the timer [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(detachnpctimer) +{ + struct npc_data *nd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st,2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("detachnpctimer: Invalid NPC.\n"); + return 1; + } + + nd->u.scr.rid = 0; + script_pushint(st,0); + return 0; +} + +/*========================================== + * To avoid "player not attached" script errors, this function is provided, + * it checks if there is a player attached to the current script. [Skotlex] + * If no, returns 0, if yes, returns the account_id of the attached player. + *------------------------------------------*/ +BUILDIN_FUNC(playerattached) +{ + if(st->rid == 0 || map_id2sd(st->rid) == NULL) + script_pushint(st,0); + else + script_pushint(st,st->rid); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(announce) +{ + const char *mes = script_getstr(st,2); + int flag = script_getnum(st,3); + const char *fontColor = script_hasdata(st,4) ? script_getstr(st,4) : NULL; + int fontType = script_hasdata(st,5) ? script_getnum(st,5) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,6) ? script_getnum(st,6) : 12; // default fontSize + int fontAlign = script_hasdata(st,7) ? script_getnum(st,7) : 0; // default fontAlign + int fontY = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontY + + if (flag&0x0f) // Broadcast source or broadcast region defined + { + send_target target; + struct block_list *bl = (flag&0x08) ? map_id2bl(st->oid) : (struct block_list *)script_rid2sd(st); // If bc_npc flag is set, use NPC as broadcast source + if (bl == NULL) + return 0; + + flag &= 0x07; + target = (flag == 1) ? ALL_SAMEMAP : + (flag == 2) ? AREA : + (flag == 3) ? SELF : + ALL_CLIENT; + if (fontColor) + clif_broadcast2(bl, mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, target); + else + clif_broadcast(bl, mes, (int)strlen(mes)+1, flag&0xf0, target); + } + else + { + if (fontColor) + intif_broadcast2(mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY); + else + intif_broadcast(mes, (int)strlen(mes)+1, flag&0xf0); + } + return 0; +} +/*========================================== + *------------------------------------------*/ +static int buildin_announce_sub(struct block_list *bl, va_list ap) +{ + char *mes = va_arg(ap, char *); + int len = va_arg(ap, int); + int type = va_arg(ap, int); + char *fontColor = va_arg(ap, char *); + short fontType = (short)va_arg(ap, int); + short fontSize = (short)va_arg(ap, int); + short fontAlign = (short)va_arg(ap, int); + short fontY = (short)va_arg(ap, int); + if (fontColor) + clif_broadcast2(bl, mes, len, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, SELF); + else + clif_broadcast(bl, mes, len, type, SELF); + return 0; +} + +BUILDIN_FUNC(mapannounce) +{ + const char *mapname = script_getstr(st,2); + const char *mes = script_getstr(st,3); + int flag = script_getnum(st,4); + const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; + int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize + int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign + int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY + int16 m; + + if ((m = map_mapname2mapid(mapname)) < 0) + return 0; + + map_foreachinmap(buildin_announce_sub, m, BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(areaannounce) +{ + const char *mapname = script_getstr(st,2); + int x0 = script_getnum(st,3); + int y0 = script_getnum(st,4); + int x1 = script_getnum(st,5); + int y1 = script_getnum(st,6); + const char *mes = script_getstr(st,7); + int flag = script_getnum(st,8); + const char *fontColor = script_hasdata(st,9) ? script_getstr(st,9) : NULL; + int fontType = script_hasdata(st,10) ? script_getnum(st,10) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,11) ? script_getnum(st,11) : 12; // default fontSize + int fontAlign = script_hasdata(st,12) ? script_getnum(st,12) : 0; // default fontAlign + int fontY = script_hasdata(st,13) ? script_getnum(st,13) : 0; // default fontY + int16 m; + + if ((m = map_mapname2mapid(mapname)) < 0) + return 0; + + map_foreachinarea(buildin_announce_sub, m, x0, y0, x1, y1, BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getusers) +{ + int flag, val = 0; + struct map_session_data* sd; + struct block_list* bl = NULL; + + flag = script_getnum(st,2); + + switch(flag&0x07) + { + case 0: + if(flag&0x8) + {// npc + bl = map_id2bl(st->oid); + } + else if((sd = script_rid2sd(st))!=NULL) + {// pc + bl = &sd->bl; + } + + if(bl) + { + val = map[bl->m].users; + } + break; + case 1: + val = map_getusers(); + break; + default: + ShowWarning("buildin_getusers: Unknown type %d.\n", flag); + script_pushint(st,0); + return 1; + } + + script_pushint(st,val); + return 0; +} +/*========================================== + * Works like @WHO - displays all online users names in window + *------------------------------------------*/ +BUILDIN_FUNC(getusersname) +{ + TBL_PC *sd, *pl_sd; + int /*disp_num=1,*/ group_level = 0; + struct s_mapiterator* iter; + + sd = script_rid2sd(st); + if (!sd) return 0; + + group_level = pc_get_group_level(sd); + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) && pc_get_group_level(pl_sd) > group_level) + continue; // skip hidden sessions + + /* Temporary fix for bugreport:1023. + * Do not uncomment unless you want thousands of 'next' buttons. + if((disp_num++)%10==0) + clif_scriptnext(sd,st->oid);*/ + clif_scriptmes(sd,st->oid,pl_sd->status.name); + } + mapit_free(iter); + + return 0; +} +/*========================================== + * getmapguildusers("mapname",guild ID) Returns the number guild members present on a map [Reddozen] + *------------------------------------------*/ +BUILDIN_FUNC(getmapguildusers) +{ + const char *str; + int16 m; + int gid; + int i=0,c=0; + struct guild *g = NULL; + str=script_getstr(st,2); + gid=script_getnum(st,3); + if ((m = map_mapname2mapid(str)) < 0) { // map id on this server (m == -1 if not in actual map-server) + script_pushint(st,-1); + return 0; + } + g = guild_search(gid); + + if (g){ + for(i = 0; i < g->max_member; i++) + { + if (g->member[i].sd && g->member[i].sd->bl.m == m) + c++; + } + } + + script_pushint(st,c); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getmapusers) +{ + const char *str; + int16 m; + str=script_getstr(st,2); + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + script_pushint(st,map[m].users); + return 0; +} +/*========================================== + *------------------------------------------*/ +static int buildin_getareausers_sub(struct block_list *bl,va_list ap) +{ + int *users=va_arg(ap,int *); + (*users)++; + return 0; +} +BUILDIN_FUNC(getareausers) +{ + const char *str; + int16 m,x0,y0,x1,y1,users=0; //doubt we can have more then 32k users on + str=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + map_foreachinarea(buildin_getareausers_sub, + m,x0,y0,x1,y1,BL_PC,&users); + script_pushint(st,users); + return 0; +} + +/*========================================== + *------------------------------------------*/ +static int buildin_getareadropitem_sub(struct block_list *bl,va_list ap) +{ + int item=va_arg(ap,int); + int *amount=va_arg(ap,int *); + struct flooritem_data *drop=(struct flooritem_data *)bl; + + if(drop->item_data.nameid==item) + (*amount)+=drop->item_data.amount; + + return 0; +} +BUILDIN_FUNC(getareadropitem) +{ + const char *str; + int16 m,x0,y0,x1,y1; + int item,amount=0; + struct script_data *data; + + str=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + + data=script_getdata(st,7); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + item=UNKNOWN_ITEM_ID; + if( item_data ) + item=item_data->nameid; + }else + item=conv_num(st,data); + + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + map_foreachinarea(buildin_getareadropitem_sub, + m,x0,y0,x1,y1,BL_ITEM,item,&amount); + script_pushint(st,amount); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(enablenpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,1); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(disablenpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,0); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(hideoffnpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,2); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(hideonnpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,4); + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start <effect_id>,<duration>,<val1>{,<unit_id>}; +BUILDIN_FUNC(sc_start) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val4 = 0; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + if( script_hasdata(st,5) ) + bl = map_id2bl(script_getnum(st,5)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + val4 = 1;// Mark that this was a thrown sc_effect + } + + if( bl ) + status_change_start(bl, type, 10000, val1, 0, 0, val4, tick, 2); + + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start2 <effect_id>,<duration>,<val1>,<percent chance>{,<unit_id>}; +BUILDIN_FUNC(sc_start2) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val4 = 0; + int rate; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + rate = script_getnum(st,5); + if( script_hasdata(st,6) ) + bl = map_id2bl(script_getnum(st,6)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + val4 = 1;// Mark that this was a thrown sc_effect + } + + if( bl ) + status_change_start(bl, type, rate, val1, 0, 0, val4, tick, 2); + + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start4 <effect_id>,<duration>,<val1>,<val2>,<val3>,<val4>{,<unit_id>}; +BUILDIN_FUNC(sc_start4) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val2; + int val3; + int val4; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + val2 = script_getnum(st,5); + val3 = script_getnum(st,6); + val4 = script_getnum(st,7); + if( script_hasdata(st,8) ) + bl = map_id2bl(script_getnum(st,8)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + } + + if( bl ) + status_change_start(bl, type, 10000, val1, val2, val3, val4, tick, 2); + + return 0; +} + +/// Ends one or all status effects on the target unit or on the attached player. +/// +/// sc_end <effect_id>{,<unit_id>}; +BUILDIN_FUNC(sc_end) +{ + struct block_list* bl; + int type; + + type = script_getnum(st, 2); + if (script_hasdata(st, 3)) + bl = map_id2bl(script_getnum(st, 3)); + else + bl = map_id2bl(st->rid); + + if (potion_flag == 1 && potion_target) //##TODO how does this work [FlavioJS] + bl = map_id2bl(potion_target); + + if (!bl) + return 0; + + if (type >= 0 && type < SC_MAX) + { + struct status_change *sc = status_get_sc(bl); + struct status_change_entry *sce = sc ? sc->data[type] : NULL; + + if (!sce) + return 0; + + + switch (type) + { + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_NOCHAT: + case SC_PUSH_CART: + return 0; + + default: + break; + } + + //This should help status_change_end force disabling the SC in case it has no limit. + sce->val1 = sce->val2 = sce->val3 = sce->val4 = 0; + status_change_end(bl, (sc_type)type, INVALID_TIMER); + } + else + status_change_clear(bl, 3); // remove all effects + + return 0; +} + +/*========================================== + * @FIXME atm will return reduced tick, 0 immune, 1 no tick + *------------------------------------------*/ +BUILDIN_FUNC(getscrate) +{ + struct block_list *bl; + int type,rate; + + type=script_getnum(st,2); + rate=script_getnum(st,3); + if( script_hasdata(st,4) ) //get for the bl assigned + bl = map_id2bl(script_getnum(st,4)); + else + bl = map_id2bl(st->rid); + + if (bl) + rate = status_get_sc_def(bl, (sc_type)type, 10000, 10000, 0); + + script_pushint(st,rate); + return 0; +} + +/*========================================== + * getstatus <type>{, <info>}; + *------------------------------------------*/ +BUILDIN_FUNC(getstatus) +{ + int id, type; + struct map_session_data* sd = script_rid2sd(st); + + if( sd == NULL ) + {// no player attached + return 0; + } + + id = script_getnum(st, 2); + type = script_hasdata(st, 3) ? script_getnum(st, 3) : 0; + + if( id <= SC_NONE || id >= SC_MAX ) + {// invalid status type given + ShowWarning("script.c:getstatus: Invalid status type given (%d).\n", id); + return 0; + } + + if( sd->sc.count == 0 || !sd->sc.data[id] ) + {// no status is active + script_pushint(st, 0); + return 0; + } + + switch( type ) + { + case 1: script_pushint(st, sd->sc.data[id]->val1); break; + case 2: script_pushint(st, sd->sc.data[id]->val2); break; + case 3: script_pushint(st, sd->sc.data[id]->val3); break; + case 4: script_pushint(st, sd->sc.data[id]->val4); break; + case 5: + { + struct TimerData* timer = (struct TimerData*)get_timer(sd->sc.data[id]->timer); + + if( timer ) + {// return the amount of time remaining + script_pushint(st, timer->tick - gettick()); + } + } + break; + default: script_pushint(st, 1); break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(debugmes) +{ + const char *str; + str=script_getstr(st,2); + ShowDebug("script debug : %d %d : %s\n",st->rid,st->oid,str); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(catchpet) +{ + int pet_id; + TBL_PC *sd; + + pet_id= script_getnum(st,2); + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + pet_catch_process1(sd,pet_id); + return 0; +} + +/*========================================== + * [orn] + *------------------------------------------*/ +BUILDIN_FUNC(homunculus_evolution) +{ + TBL_PC *sd; + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(merc_is_hom_active(sd->hd)) + { + if (sd->hd->homunculus.intimacy > 91000) + merc_hom_evolution(sd->hd); + else + clif_emotion(&sd->hd->bl, E_SWT); + } + return 0; +} + +/*========================================== + * [Xantara] + *------------------------------------------*/ +BUILDIN_FUNC(homunculus_mutate) +{ + int homun_id, m_class, m_id; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(script_hasdata(st,2)) + homun_id = script_getnum(st,2); + else + homun_id = 6048 + (rnd() % 4); + + if(merc_is_hom_active(sd->hd)) { + m_class = hom_class2mapid(sd->hd->homunculus.class_); + m_id = hom_class2mapid(homun_id); + + if ( m_class != -1 && m_id != -1 && m_class&HOM_EVO && m_id&HOM_S && sd->hd->homunculus.level >= 99 ) + hom_mutate(sd->hd, homun_id); + else + clif_emotion(&sd->hd->bl, E_SWT); + } + return 0; +} + +// [Zephyrus] +BUILDIN_FUNC(homunculus_shuffle) +{ + TBL_PC *sd; + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(merc_is_hom_active(sd->hd)) + merc_hom_shuffle(sd->hd); + + return 0; +} + +//These two functions bring the eA MAPID_* class functionality to scripts. +BUILDIN_FUNC(eaclass) +{ + int class_; + if( script_hasdata(st,2) ) + class_ = script_getnum(st,2); + else { + TBL_PC *sd; + sd=script_rid2sd(st); + if (!sd) { + script_pushint(st,-1); + return 0; + } + class_ = sd->status.class_; + } + script_pushint(st,pc_jobid2mapid(class_)); + return 0; +} + +BUILDIN_FUNC(roclass) +{ + int class_ =script_getnum(st,2); + int sex; + if( script_hasdata(st,3) ) + sex = script_getnum(st,3); + else { + TBL_PC *sd; + if (st->rid && (sd=script_rid2sd(st))) + sex = sd->status.sex; + else + sex = 1; //Just use male when not found. + } + script_pushint(st,pc_mapid2jobid(class_, sex)); + return 0; +} + +/*========================================== + * Tells client to open a hatching window, used for pet incubator + *------------------------------------------*/ +BUILDIN_FUNC(birthpet) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->status.pet_id ) + {// do not send egg list, when you already have a pet + return 0; + } + + clif_sendegg(sd); + return 0; +} + +/*========================================== + * Added - AppleGirl For Advanced Classes, (Updated for Cleaner Script Purposes) + * @type + * 1 : make like after rebirth + * 2 : blvl,jlvl=1, skillpoint=0 + * 3 : don't reset skill, blvl=1 + * 4 : jlvl=0 + *------------------------------------------*/ +BUILDIN_FUNC(resetlvl) +{ + TBL_PC *sd; + + int type=script_getnum(st,2); + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_resetlvl(sd,type); + return 0; +} +/*========================================== + * Reset a player status point + *------------------------------------------*/ +BUILDIN_FUNC(resetstatus) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + pc_resetstate(sd); + return 0; +} + +/*========================================== + * script command resetskill + *------------------------------------------*/ +BUILDIN_FUNC(resetskill) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + pc_resetskill(sd,1); + return 0; +} + +/*========================================== + * Counts total amount of skill points. + *------------------------------------------*/ +BUILDIN_FUNC(skillpointcount) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + script_pushint(st,sd->status.skill_point + pc_resetskill(sd,2)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(changebase) +{ + TBL_PC *sd=NULL; + int vclass; + + if( script_hasdata(st,3) ) + sd=map_id2sd(script_getnum(st,3)); + else + sd=script_rid2sd(st); + + if(sd == NULL) + return 0; + + vclass = script_getnum(st,2); + if(vclass == JOB_WEDDING) + { + if (!battle_config.wedding_modifydisplay || //Do not show the wedding sprites + sd->class_&JOBL_BABY //Baby classes screw up when showing wedding sprites. [Skotlex] They don't seem to anymore. + ) + return 0; + } + + if(!sd->disguise && vclass != sd->vd.class_) { + status_set_viewdata(&sd->bl, vclass); + //Updated client view. Base, Weapon and Cloth Colors. + clif_changelook(&sd->bl,LOOK_BASE,sd->vd.class_); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + if (sd->vd.cloth_color) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->vd.cloth_color); + clif_skillinfoblock(sd); + } + + return 0; +} + +/*========================================== + * Unequip all item and request for a changesex to char-serv + *------------------------------------------*/ +BUILDIN_FUNC(changesex) +{ + int i; + TBL_PC *sd = NULL; + sd = script_rid2sd(st); + + pc_resetskill(sd,4); + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for( i=0; i<EQI_MAX; i++ ) + if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3); + chrif_changesex(sd); + return 0; +} + +/*========================================== + * Works like 'announce' but outputs in the common chat window + *------------------------------------------*/ +BUILDIN_FUNC(globalmes) +{ + struct block_list *bl = map_id2bl(st->oid); + struct npc_data *nd = (struct npc_data *)bl; + const char *name=NULL,*mes; + + mes=script_getstr(st,2); + if(mes==NULL) return 0; + + if(script_hasdata(st,3)){ // npc name to display + name=script_getstr(st,3); + } else { + name=nd->name; //use current npc name + } + + npc_globalmessage(name,mes); // broadcast to all players connected + + return 0; +} + +///////////////////////////////////////////////////////////////////// +// NPC waiting room (chat room) +// + +/// Creates a waiting room (chat room) for this npc. +/// +/// waitingroom "<title>",<limit>{,"<event>"{,<trigger>{,<zeny>{,<minlvl>{,<maxlvl>}}}}}; +BUILDIN_FUNC(waitingroom) +{ + struct npc_data* nd; + int pub = 1; + const char* title = script_getstr(st, 2); + int limit = script_getnum(st, 3); + const char* ev = script_hasdata(st,4) ? script_getstr(st,4) : ""; + int trigger = script_hasdata(st,5) ? script_getnum(st,5) : limit; + int zeny = script_hasdata(st,6) ? script_getnum(st,6) : 0; + int minLvl = script_hasdata(st,7) ? script_getnum(st,7) : 1; + int maxLvl = script_hasdata(st,8) ? script_getnum(st,8) : MAX_LEVEL; + + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd != NULL ) + chat_createnpcchat(nd, title, limit, pub, trigger, ev, zeny, minLvl, maxLvl); + + return 0; +} + +/// Removes the waiting room of the current or target npc. +/// +/// delwaitingroom "<npc_name>"; +/// delwaitingroom; +BUILDIN_FUNC(delwaitingroom) +{ + struct npc_data* nd; + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd != NULL ) + chat_deletenpcchat(nd); + return 0; +} + +/// Kicks all the players from the waiting room of the current or target npc. +/// +/// kickwaitingroomall "<npc_name>"; +/// kickwaitingroomall; +BUILDIN_FUNC(waitingroomkickall) +{ + struct npc_data* nd; + struct chat_data* cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st,2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_npckickall(cd); + return 0; +} + +/// Enables the waiting room event of the current or target npc. +/// +/// enablewaitingroomevent "<npc_name>"; +/// enablewaitingroomevent; +BUILDIN_FUNC(enablewaitingroomevent) +{ + struct npc_data* nd; + struct chat_data* cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_enableevent(cd); + return 0; +} + +/// Disables the waiting room event of the current or target npc. +/// +/// disablewaitingroomevent "<npc_name>"; +/// disablewaitingroomevent; +BUILDIN_FUNC(disablewaitingroomevent) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_disableevent(cd); + return 0; +} + +/// Returns info on the waiting room of the current or target npc. +/// Returns -1 if the type unknown +/// <type>=0 : current number of users +/// <type>=1 : maximum number of users allowed +/// <type>=2 : the number of users that trigger the event +/// <type>=3 : if the trigger is disabled +/// <type>=4 : the title of the waiting room +/// <type>=5 : the password of the waiting room +/// <type>=16 : the name of the waiting room event +/// <type>=32 : if the waiting room is full +/// <type>=33 : if there are enough users to trigger the event +/// +/// getwaitingroomstate(<type>,"<npc_name>") -> <info> +/// getwaitingroomstate(<type>) -> <info> +BUILDIN_FUNC(getwaitingroomstate) +{ + struct npc_data *nd; + struct chat_data *cd; + int type; + + type = script_getnum(st,2); + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st, 3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + { + script_pushint(st, -1); + return 0; + } + + switch(type) + { + case 0: script_pushint(st, cd->users); break; + case 1: script_pushint(st, cd->limit); break; + case 2: script_pushint(st, cd->trigger&0x7f); break; + case 3: script_pushint(st, ((cd->trigger&0x80)!=0)); break; + case 4: script_pushstrcopy(st, cd->title); break; + case 5: script_pushstrcopy(st, cd->pass); break; + case 16: script_pushstrcopy(st, cd->npc_event);break; + case 32: script_pushint(st, (cd->users >= cd->limit)); break; + case 33: script_pushint(st, (cd->users >= cd->trigger)); break; + default: script_pushint(st, -1); break; + } + return 0; +} + +/// Warps the trigger or target amount of players to the target map and position. +/// Players are automatically removed from the waiting room. +/// Those waiting the longest will get warped first. +/// The target map can be "Random" for a random position in the current map, +/// and "SavePoint" for the savepoint map+position. +/// The map flag noteleport of the current map is only considered when teleporting to the savepoint. +/// +/// The id's of the teleported players are put into the array $@warpwaitingpc[] +/// The total number of teleported players is put into $@warpwaitingpcnum +/// +/// warpwaitingpc "<map name>",<x>,<y>,<number of players>; +/// warpwaitingpc "<map name>",<x>,<y>; +BUILDIN_FUNC(warpwaitingpc) +{ + int x; + int y; + int i; + int n; + const char* map_name; + struct npc_data* nd; + struct chat_data* cd; + TBL_PC* sd; + + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + return 0; + + map_name = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + n = cd->trigger&0x7f; + + if( script_hasdata(st,5) ) + n = script_getnum(st,5); + + for( i = 0; i < n && cd->users > 0; i++ ) + { + sd = cd->usersd[0]; + + if( strcmp(map_name,"SavePoint") == 0 && map[sd->bl.m].flag.noteleport ) + {// can't teleport on this map + break; + } + + if( cd->zeny ) + {// fee set + if( (uint32)sd->status.zeny < cd->zeny ) + {// no zeny to cover set fee + break; + } + pc_payzeny(sd, cd->zeny, LOG_TYPE_NPC, NULL); + } + + mapreg_setreg(reference_uid(add_str("$@warpwaitingpc"), i), sd->bl.id); + + if( strcmp(map_name,"Random") == 0 ) + pc_randomwarp(sd,CLR_TELEPORT); + else if( strcmp(map_name,"SavePoint") == 0 ) + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); + else + pc_setpos(sd, mapindex_name2id(map_name), x, y, CLR_OUTSIGHT); + } + mapreg_setreg(add_str("$@warpwaitingpcnum"), i); + return 0; +} + +///////////////////////////////////////////////////////////////////// +// ... +// + +/// Detaches a character from a script. +/// +/// @param st Script state to detach the character from. +static void script_detach_rid(struct script_state* st) +{ + if(st->rid) + { + script_detach_state(st, false); + st->rid = 0; + } +} + +/*========================================== + * Attach sd char id to script and detach current one if any + *------------------------------------------*/ +BUILDIN_FUNC(attachrid) +{ + int rid = script_getnum(st,2); + struct map_session_data* sd; + + if ((sd = map_id2sd(rid))!=NULL) { + script_detach_rid(st); + + st->rid = rid; + script_attach_state(st); + script_pushint(st,1); + } else + script_pushint(st,0); + return 0; +} +/*========================================== + * Detach script to rid + *------------------------------------------*/ +BUILDIN_FUNC(detachrid) +{ + script_detach_rid(st); + return 0; +} +/*========================================== + * Chk if account connected, (and charid from account if specified) + *------------------------------------------*/ +BUILDIN_FUNC(isloggedin) +{ + TBL_PC* sd = map_id2sd(script_getnum(st,2)); + if (script_hasdata(st,3) && sd && + sd->status.char_id != script_getnum(st,3)) + sd = NULL; + push_val(st->stack,C_INT,sd!=NULL); + return 0; +} + + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(setmapflagnosave) +{ + int16 m,x,y; + unsigned short mapindex; + const char *str,*str2; + + str=script_getstr(st,2); + str2=script_getstr(st,3); + x=script_getnum(st,4); + y=script_getnum(st,5); + m = map_mapname2mapid(str); + mapindex = mapindex_name2id(str2); + + if(m >= 0 && mapindex) { + map[m].flag.nosave=1; + map[m].save.map=mapindex; + map[m].save.x=x; + map[m].save.y=y; + } + + return 0; +} + +BUILDIN_FUNC(getmapflag) +{ + int16 m,i; + const char *str; + + str=script_getstr(st,2); + i=script_getnum(st,3); + + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: script_pushint(st,map[m].flag.nomemo); break; + case MF_NOTELEPORT: script_pushint(st,map[m].flag.noteleport); break; + case MF_NOSAVE: script_pushint(st,map[m].flag.nosave); break; + case MF_NOBRANCH: script_pushint(st,map[m].flag.nobranch); break; + case MF_NOPENALTY: script_pushint(st,map[m].flag.noexppenalty); break; + case MF_NOZENYPENALTY: script_pushint(st,map[m].flag.nozenypenalty); break; + case MF_PVP: script_pushint(st,map[m].flag.pvp); break; + case MF_PVP_NOPARTY: script_pushint(st,map[m].flag.pvp_noparty); break; + case MF_PVP_NOGUILD: script_pushint(st,map[m].flag.pvp_noguild); break; + case MF_GVG: script_pushint(st,map[m].flag.gvg); break; + case MF_GVG_NOPARTY: script_pushint(st,map[m].flag.gvg_noparty); break; + case MF_NOTRADE: script_pushint(st,map[m].flag.notrade); break; + case MF_NOSKILL: script_pushint(st,map[m].flag.noskill); break; + case MF_NOWARP: script_pushint(st,map[m].flag.nowarp); break; + case MF_PARTYLOCK: script_pushint(st,map[m].flag.partylock); break; + case MF_NOICEWALL: script_pushint(st,map[m].flag.noicewall); break; + case MF_SNOW: script_pushint(st,map[m].flag.snow); break; + case MF_FOG: script_pushint(st,map[m].flag.fog); break; + case MF_SAKURA: script_pushint(st,map[m].flag.sakura); break; + case MF_LEAVES: script_pushint(st,map[m].flag.leaves); break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: script_pushint(st,map[m].flag.rain); break; + case MF_NOGO: script_pushint(st,map[m].flag.nogo); break; + case MF_CLOUDS: script_pushint(st,map[m].flag.clouds); break; + case MF_CLOUDS2: script_pushint(st,map[m].flag.clouds2); break; + case MF_FIREWORKS: script_pushint(st,map[m].flag.fireworks); break; + case MF_GVG_CASTLE: script_pushint(st,map[m].flag.gvg_castle); break; + case MF_GVG_DUNGEON: script_pushint(st,map[m].flag.gvg_dungeon); break; + case MF_NIGHTENABLED: script_pushint(st,map[m].flag.nightenabled); break; + case MF_NOBASEEXP: script_pushint(st,map[m].flag.nobaseexp); break; + case MF_NOJOBEXP: script_pushint(st,map[m].flag.nojobexp); break; + case MF_NOMOBLOOT: script_pushint(st,map[m].flag.nomobloot); break; + case MF_NOMVPLOOT: script_pushint(st,map[m].flag.nomvploot); break; + case MF_NORETURN: script_pushint(st,map[m].flag.noreturn); break; + case MF_NOWARPTO: script_pushint(st,map[m].flag.nowarpto); break; + case MF_NIGHTMAREDROP: script_pushint(st,map[m].flag.pvp_nightmaredrop); break; + case MF_RESTRICTED: script_pushint(st,map[m].flag.restricted); break; + case MF_NOCOMMAND: script_pushint(st,map[m].nocommand); break; + case MF_NODROP: script_pushint(st,map[m].flag.nodrop); break; + case MF_JEXP: script_pushint(st,map[m].jexp); break; + case MF_BEXP: script_pushint(st,map[m].bexp); break; + case MF_NOVENDING: script_pushint(st,map[m].flag.novending); break; + case MF_LOADEVENT: script_pushint(st,map[m].flag.loadevent); break; + case MF_NOCHAT: script_pushint(st,map[m].flag.nochat); break; + case MF_NOEXPPENALTY: script_pushint(st,map[m].flag.noexppenalty ); break; + case MF_GUILDLOCK: script_pushint(st,map[m].flag.guildlock); break; + case MF_TOWN: script_pushint(st,map[m].flag.town); break; + case MF_AUTOTRADE: script_pushint(st,map[m].flag.autotrade); break; + case MF_ALLOWKS: script_pushint(st,map[m].flag.allowks); break; + case MF_MONSTER_NOTELEPORT: script_pushint(st,map[m].flag.monster_noteleport); break; + case MF_PVP_NOCALCRANK: script_pushint(st,map[m].flag.pvp_nocalcrank); break; + case MF_BATTLEGROUND: script_pushint(st,map[m].flag.battleground); break; + case MF_RESET: script_pushint(st,map[m].flag.reset); break; + } + } + + return 0; +} +/* pvp timer handling */ +static int script_mapflag_pvp_sub(struct block_list *bl,va_list ap) { + TBL_PC* sd = (TBL_PC*)bl; + if (sd->pvp_timer == INVALID_TIMER) { + sd->pvp_timer = add_timer(gettick() + 200, pc_calc_pvprank_timer, sd->bl.id, 0); + sd->pvp_rank = 0; + sd->pvp_lastusers = 0; + sd->pvp_point = 5; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + return 0; +} +BUILDIN_FUNC(setmapflag) +{ + int16 m,i; + const char *str; + int val=0; + + str=script_getstr(st,2); + i=script_getnum(st,3); + if(script_hasdata(st,4)){ + val=script_getnum(st,4); + } + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: map[m].flag.nomemo = 1; break; + case MF_NOTELEPORT: map[m].flag.noteleport = 1; break; + case MF_NOSAVE: map[m].flag.nosave = 1; break; + case MF_NOBRANCH: map[m].flag.nobranch = 1; break; + case MF_NOPENALTY: map[m].flag.noexppenalty = 1; map[m].flag.nozenypenalty = 1; break; + case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 1; break; + case MF_PVP: + map[m].flag.pvp = 1; + if( !battle_config.pk_mode ) { + map_foreachinmap(script_mapflag_pvp_sub,m,BL_PC); + } + break; + case MF_PVP_NOPARTY: map[m].flag.pvp_noparty = 1; break; + case MF_PVP_NOGUILD: map[m].flag.pvp_noguild = 1; break; + case MF_GVG: + map[m].flag.gvg = 1; + clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); + break; + case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 1; break; + case MF_NOTRADE: map[m].flag.notrade = 1; break; + case MF_NOSKILL: map[m].flag.noskill = 1; break; + case MF_NOWARP: map[m].flag.nowarp = 1; break; + case MF_PARTYLOCK: map[m].flag.partylock = 1; break; + case MF_NOICEWALL: map[m].flag.noicewall = 1; break; + case MF_SNOW: map[m].flag.snow = 1; break; + case MF_FOG: map[m].flag.fog = 1; break; + case MF_SAKURA: map[m].flag.sakura = 1; break; + case MF_LEAVES: map[m].flag.leaves = 1; break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: map[m].flag.rain = 1; break; + case MF_NOGO: map[m].flag.nogo = 1; break; + case MF_CLOUDS: map[m].flag.clouds = 1; break; + case MF_CLOUDS2: map[m].flag.clouds2 = 1; break; + case MF_FIREWORKS: map[m].flag.fireworks = 1; break; + case MF_GVG_CASTLE: map[m].flag.gvg_castle = 1; break; + case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 1; break; + case MF_NIGHTENABLED: map[m].flag.nightenabled = 1; break; + case MF_NOBASEEXP: map[m].flag.nobaseexp = 1; break; + case MF_NOJOBEXP: map[m].flag.nojobexp = 1; break; + case MF_NOMOBLOOT: map[m].flag.nomobloot = 1; break; + case MF_NOMVPLOOT: map[m].flag.nomvploot = 1; break; + case MF_NORETURN: map[m].flag.noreturn = 1; break; + case MF_NOWARPTO: map[m].flag.nowarpto = 1; break; + case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 1; break; + case MF_RESTRICTED: + map[m].zone |= 1<<(val+1); + map[m].flag.restricted=1; + break; + case MF_NOCOMMAND: map[m].nocommand = (val <= 0) ? 100 : val; break; + case MF_NODROP: map[m].flag.nodrop = 1; break; + case MF_JEXP: map[m].jexp = (val <= 0) ? 100 : val; break; + case MF_BEXP: map[m].bexp = (val <= 0) ? 100 : val; break; + case MF_NOVENDING: map[m].flag.novending = 1; break; + case MF_LOADEVENT: map[m].flag.loadevent = 1; break; + case MF_NOCHAT: map[m].flag.nochat = 1; break; + case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 1; break; + case MF_GUILDLOCK: map[m].flag.guildlock = 1; break; + case MF_TOWN: map[m].flag.town = 1; break; + case MF_AUTOTRADE: map[m].flag.autotrade = 1; break; + case MF_ALLOWKS: map[m].flag.allowks = 1; break; + case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 1; break; + case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 1; break; + case MF_BATTLEGROUND: map[m].flag.battleground = (val <= 0 || val > 2) ? 1 : val; break; + case MF_RESET: map[m].flag.reset = 1; break; + } + } + + return 0; +} + +BUILDIN_FUNC(removemapflag) +{ + int16 m,i; + const char *str; + int val=0; + + str=script_getstr(st,2); + i=script_getnum(st,3); + if(script_hasdata(st,4)){ + val=script_getnum(st,4); + } + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: map[m].flag.nomemo = 0; break; + case MF_NOTELEPORT: map[m].flag.noteleport = 0; break; + case MF_NOSAVE: map[m].flag.nosave = 0; break; + case MF_NOBRANCH: map[m].flag.nobranch = 0; break; + case MF_NOPENALTY: map[m].flag.noexppenalty = 0; map[m].flag.nozenypenalty = 0; break; + case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 0; break; + case MF_PVP: + map[m].flag.pvp = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + break; + case MF_PVP_NOPARTY: map[m].flag.pvp_noparty = 0; break; + case MF_PVP_NOGUILD: map[m].flag.pvp_noguild = 0; break; + case MF_GVG: + map[m].flag.gvg = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + break; + case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 0; break; + case MF_NOTRADE: map[m].flag.notrade = 0; break; + case MF_NOSKILL: map[m].flag.noskill = 0; break; + case MF_NOWARP: map[m].flag.nowarp = 0; break; + case MF_PARTYLOCK: map[m].flag.partylock = 0; break; + case MF_NOICEWALL: map[m].flag.noicewall = 0; break; + case MF_SNOW: map[m].flag.snow = 0; break; + case MF_FOG: map[m].flag.fog = 0; break; + case MF_SAKURA: map[m].flag.sakura = 0; break; + case MF_LEAVES: map[m].flag.leaves = 0; break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: map[m].flag.rain = 0; break; + case MF_NOGO: map[m].flag.nogo = 0; break; + case MF_CLOUDS: map[m].flag.clouds = 0; break; + case MF_CLOUDS2: map[m].flag.clouds2 = 0; break; + case MF_FIREWORKS: map[m].flag.fireworks = 0; break; + case MF_GVG_CASTLE: map[m].flag.gvg_castle = 0; break; + case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 0; break; + case MF_NIGHTENABLED: map[m].flag.nightenabled = 0; break; + case MF_NOBASEEXP: map[m].flag.nobaseexp = 0; break; + case MF_NOJOBEXP: map[m].flag.nojobexp = 0; break; + case MF_NOMOBLOOT: map[m].flag.nomobloot = 0; break; + case MF_NOMVPLOOT: map[m].flag.nomvploot = 0; break; + case MF_NORETURN: map[m].flag.noreturn = 0; break; + case MF_NOWARPTO: map[m].flag.nowarpto = 0; break; + case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 0; break; + case MF_RESTRICTED: + map[m].zone ^= 1<<(val+1); + if (map[m].zone == 0){ + map[m].flag.restricted=0; + } + break; + case MF_NOCOMMAND: map[m].nocommand = 0; break; + case MF_NODROP: map[m].flag.nodrop = 0; break; + case MF_JEXP: map[m].jexp = 0; break; + case MF_BEXP: map[m].bexp = 0; break; + case MF_NOVENDING: map[m].flag.novending = 0; break; + case MF_LOADEVENT: map[m].flag.loadevent = 0; break; + case MF_NOCHAT: map[m].flag.nochat = 0; break; + case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 0; break; + case MF_GUILDLOCK: map[m].flag.guildlock = 0; break; + case MF_TOWN: map[m].flag.town = 0; break; + case MF_AUTOTRADE: map[m].flag.autotrade = 0; break; + case MF_ALLOWKS: map[m].flag.allowks = 0; break; + case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 0; break; + case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 0; break; + case MF_BATTLEGROUND: map[m].flag.battleground = 0; break; + case MF_RESET: map[m].flag.reset = 0; break; + } + } + + return 0; +} + +BUILDIN_FUNC(pvpon) +{ + int16 m; + const char *str; + TBL_PC* sd = NULL; + struct s_mapiterator* iter; + + str = script_getstr(st,2); + m = map_mapname2mapid(str); + if( m < 0 || map[m].flag.pvp ) + return 0; // nothing to do + + map[m].flag.pvp = 1; + clif_map_property_mapall(m, MAPPROPERTY_FREEPVPZONE); + + if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return 0; + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if( sd->bl.m != m || sd->pvp_timer != INVALID_TIMER ) + continue; // not applicable + + sd->pvp_timer = add_timer(gettick()+200,pc_calc_pvprank_timer,sd->bl.id,0); + sd->pvp_rank = 0; + sd->pvp_lastusers = 0; + sd->pvp_point = 5; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + mapit_free(iter); + + return 0; +} + +static int buildin_pvpoff_sub(struct block_list *bl,va_list ap) +{ + TBL_PC* sd = (TBL_PC*)bl; + clif_pvpset(sd, 0, 0, 2); + if (sd->pvp_timer != INVALID_TIMER) { + delete_timer(sd->pvp_timer, pc_calc_pvprank_timer); + sd->pvp_timer = INVALID_TIMER; + } + return 0; +} + +BUILDIN_FUNC(pvpoff) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m < 0 || !map[m].flag.pvp) + return 0; //fixed Lupus + + map[m].flag.pvp = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + + if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return 0; + + map_foreachinmap(buildin_pvpoff_sub, m, BL_PC); + return 0; +} + +BUILDIN_FUNC(gvgon) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.gvg) { + map[m].flag.gvg = 1; + clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); + } + + return 0; +} +BUILDIN_FUNC(gvgoff) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.gvg) { + map[m].flag.gvg = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + } + + return 0; +} +/*========================================== + * Shows an emoticon on top of the player/npc + * emotion emotion#, <target: 0 - NPC, 1 - PC>, <NPC/PC name> + *------------------------------------------*/ +//Optional second parameter added by [Skotlex] +BUILDIN_FUNC(emotion) +{ + int type; + int player=0; + + type=script_getnum(st,2); + if(type < 0 || type > 100) + return 0; + + if( script_hasdata(st,3) ) + player=script_getnum(st,3); + + if (player) { + TBL_PC *sd = NULL; + if( script_hasdata(st,4) ) + sd = map_nick2sd(script_getstr(st,4)); + else + sd = script_rid2sd(st); + if (sd) + clif_emotion(&sd->bl,type); + } else + if( script_hasdata(st,4) ) + { + TBL_NPC *nd = npc_name2id(script_getstr(st,4)); + if(nd) + clif_emotion(&nd->bl,type); + } + else + clif_emotion(map_id2bl(st->oid),type); + return 0; +} + +static int buildin_maprespawnguildid_sub_pc(struct map_session_data* sd, va_list ap) +{ + int16 m=va_arg(ap,int); + int g_id=va_arg(ap,int); + int flag=va_arg(ap,int); + + if(!sd || sd->bl.m != m) + return 0; + if( + (sd->status.guild_id == g_id && flag&1) || //Warp out owners + (sd->status.guild_id != g_id && flag&2) || //Warp out outsiders + (sd->status.guild_id == 0) // Warp out players not in guild [Valaris] + ) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + return 1; +} + +static int buildin_maprespawnguildid_sub_mob(struct block_list *bl,va_list ap) +{ + struct mob_data *md=(struct mob_data *)bl; + + if(!md->guardian_data && md->class_ != MOBID_EMPERIUM) + status_kill(bl); + + return 0; +} + +BUILDIN_FUNC(maprespawnguildid) +{ + const char *mapname=script_getstr(st,2); + int g_id=script_getnum(st,3); + int flag=script_getnum(st,4); + + int16 m=map_mapname2mapid(mapname); + + if(m == -1) + return 0; + + //Catch ALL players (in case some are 'between maps' on execution time) + map_foreachpc(buildin_maprespawnguildid_sub_pc,m,g_id,flag); + if (flag&4) //Remove script mobs. + map_foreachinmap(buildin_maprespawnguildid_sub_mob,m,BL_MOB); + return 0; +} + +BUILDIN_FUNC(agitstart) +{ + if(agit_flag==1) return 0; // Agit already Start. + agit_flag=1; + guild_agit_start(); + return 0; +} + +BUILDIN_FUNC(agitend) +{ + if(agit_flag==0) return 0; // Agit already End. + agit_flag=0; + guild_agit_end(); + return 0; +} + +BUILDIN_FUNC(agitstart2) +{ + if(agit2_flag==1) return 0; // Agit2 already Start. + agit2_flag=1; + guild_agit2_start(); + return 0; +} + +BUILDIN_FUNC(agitend2) +{ + if(agit2_flag==0) return 0; // Agit2 already End. + agit2_flag=0; + guild_agit2_end(); + return 0; +} + +/*========================================== + * Returns whether woe is on or off. // choice script + *------------------------------------------*/ +BUILDIN_FUNC(agitcheck) +{ + script_pushint(st,agit_flag); + return 0; +} + +/*========================================== + * Returns whether woese is on or off. // choice script + *------------------------------------------*/ +BUILDIN_FUNC(agitcheck2) +{ + script_pushint(st,agit2_flag); + return 0; +} + +/// Sets the guild_id of this npc. +/// +/// flagemblem <guild_id>; +BUILDIN_FUNC(flagemblem) +{ + TBL_NPC* nd; + int g_id = script_getnum(st,2); + + if(g_id < 0) return 0; + + nd = (TBL_NPC*)map_id2nd(st->oid); + if( nd == NULL ) { + ShowError("script:flagemblem: npc %d not found\n", st->oid); + } else if( nd->subtype != SCRIPT ) { + ShowError("script:flagemblem: unexpected subtype %d for npc %d '%s'\n", nd->subtype, st->oid, nd->exname); + } else { + bool changed = ( nd->u.scr.guild_id != g_id )?true:false; + nd->u.scr.guild_id = g_id; + clif_guild_emblem_area(&nd->bl); + /* guild flag caching */ + if( g_id ) /* adding a id */ + guild_flag_add(nd); + else if( changed ) /* removing a flag */ + guild_flag_remove(nd); + } + return 0; +} + +BUILDIN_FUNC(getcastlename) +{ + const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); + struct guild_castle* gc = guild_mapname2gc(mapname); + const char* name = (gc) ? gc->castle_name : ""; + script_pushstrcopy(st,name); + return 0; +} + +BUILDIN_FUNC(getcastledata) +{ + const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int index = script_getnum(st,3); + struct guild_castle *gc = guild_mapname2gc(mapname); + + if (gc == NULL) { + script_pushint(st,0); + ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); + return 1; + } + + switch (index) { + case 1: + script_pushint(st,gc->guild_id); break; + case 2: + script_pushint(st,gc->economy); break; + case 3: + script_pushint(st,gc->defense); break; + case 4: + script_pushint(st,gc->triggerE); break; + case 5: + script_pushint(st,gc->triggerD); break; + case 6: + script_pushint(st,gc->nextTime); break; + case 7: + script_pushint(st,gc->payTime); break; + case 8: + script_pushint(st,gc->createTime); break; + case 9: + script_pushint(st,gc->visibleC); break; + default: + if (index > 9 && index <= 9+MAX_GUARDIANS) { + script_pushint(st,gc->guardian[index-10].visible); + break; + } + script_pushint(st,0); + ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); + return 1; + } + return 0; +} + +BUILDIN_FUNC(setcastledata) +{ + const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int index = script_getnum(st,3); + int value = script_getnum(st,4); + struct guild_castle *gc = guild_mapname2gc(mapname); + + if (gc == NULL) { + ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); + return 1; + } + + if (index <= 0 || index > 9+MAX_GUARDIANS) { + ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); + return 1; + } + + guild_castledatasave(gc->castle_id, index, value); + return 0; +} + +/* ===================================================================== + * ---------------------------------------------------------------------*/ +BUILDIN_FUNC(requestguildinfo) +{ + int guild_id=script_getnum(st,2); + const char *event=NULL; + + if( script_hasdata(st,3) ){ + event=script_getstr(st,3); + check_event(st, event); + } + + if(guild_id>0) + guild_npc_request_info(guild_id,event); + return 0; +} + +/// Returns the number of cards that have been compounded onto the specified equipped item. +/// getequipcardcnt(<equipment slot>); +BUILDIN_FUNC(getequipcardcnt) +{ + int i=-1,j,num; + TBL_PC *sd; + int count; + + num=script_getnum(st,2); + sd=script_rid2sd(st); + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) { + script_pushint(st,0); + return 0; + } + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + { + script_pushint(st,0); + return 0; + } + + count = 0; + for( j = 0; j < sd->inventory_data[i]->slot; j++ ) + if( sd->status.inventory[i].card[j] && itemdb_type(sd->status.inventory[i].card[j]) == IT_CARD ) + count++; + + script_pushint(st,count); + return 0; +} + +/// Removes all cards from the item found in the specified equipment slot of the invoking character, +/// and give them to the character. If any cards were removed in this manner, it will also show a success effect. +/// successremovecards <slot>; +BUILDIN_FUNC(successremovecards) { + int i=-1,j,c,cardflag=0; + + TBL_PC* sd = script_rid2sd(st); + int num = script_getnum(st,2); + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) { + return 0; + } + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + return 0; + + for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { + if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) {// extract this card from the item + int flag; + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + cardflag = 1; + item_tmp.nameid = sd->status.inventory[i].card[c]; + item_tmp.identify = 1; + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ // get back the cart in inventory + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + if(cardflag == 1) {//if card was remove remplace item with no card + int flag; + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].nameid; + item_tmp.identify = 1; + item_tmp.refine = sd->status.inventory[i].refine; + item_tmp.attribute = sd->status.inventory[i].attribute; + item_tmp.expire_time = sd->status.inventory[i].expire_time; + + for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) + item_tmp.card[j]=sd->status.inventory[i].card[j]; + + pc_delitem(sd,i,1,0,3,LOG_TYPE_SCRIPT); + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ //chk if can be spawn in inventory otherwise put on floor + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + + clif_misceffect(&sd->bl,3); + } + return 0; +} + +/// Removes all cards from the item found in the specified equipment slot of the invoking character. +/// failedremovecards <slot>, <type>; +/// <type>=0 : will destroy both the item and the cards. +/// <type>=1 : will keep the item, but destroy the cards. +/// <type>=2 : will keep the cards, but destroy the item. +/// <type>=? : will just display the failure effect. +BUILDIN_FUNC(failedremovecards) { + int i=-1,j,c,cardflag=0; + + TBL_PC* sd = script_rid2sd(st); + int num = script_getnum(st,2); + int typefail = script_getnum(st,3); + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) + return 0; + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + return 0; + + for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { + if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) { + cardflag = 1; + + if(typefail == 2) {// add cards to inventory, clear + int flag; + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].card[c]; + item_tmp.identify = 1; + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + } + + if(cardflag == 1) { + if(typefail == 0 || typefail == 2){ // destroy the item + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + } + if(typefail == 1){ // destroy the card + int flag; + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].nameid; + item_tmp.identify = 1; + item_tmp.refine = sd->status.inventory[i].refine; + item_tmp.attribute = sd->status.inventory[i].attribute; + item_tmp.expire_time = sd->status.inventory[i].expire_time; + + for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) + item_tmp.card[j]=sd->status.inventory[i].card[j]; + + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/* ================================================================ + * mapwarp "<from map>","<to map>",<x>,<y>,<type>,<ID for Type>; + * type: 0=everyone, 1=guild, 2=party; [Reddozen] + * improved by [Lance] + * ================================================================*/ +BUILDIN_FUNC(mapwarp) // Added by RoVeRT +{ + int x,y,m,check_val=0,check_ID=0,i=0; + struct guild *g = NULL; + struct party_data *p = NULL; + const char *str; + const char *mapname; + unsigned int index; + mapname=script_getstr(st,2); + str=script_getstr(st,3); + x=script_getnum(st,4); + y=script_getnum(st,5); + if(script_hasdata(st,7)){ + check_val=script_getnum(st,6); + check_ID=script_getnum(st,7); + } + + if((m=map_mapname2mapid(mapname))< 0) + return 0; + + if(!(index=mapindex_name2id(str))) + return 0; + + switch(check_val){ + case 1: + g = guild_search(check_ID); + if (g){ + for( i=0; i < g->max_member; i++) + { + if(g->member[i].sd && g->member[i].sd->bl.m==m){ + pc_setpos(g->member[i].sd,index,x,y,CLR_TELEPORT); + } + } + } + break; + case 2: + p = party_search(check_ID); + if(p){ + for(i=0;i<MAX_PARTY; i++){ + if(p->data[i].sd && p->data[i].sd->bl.m == m){ + pc_setpos(p->data[i].sd,index,x,y,CLR_TELEPORT); + } + } + } + break; + default: + map_foreachinmap(buildin_areawarp_sub,m,BL_PC,index,x,y,0,0); + break; + } + + return 0; +} + +static int buildin_mobcount_sub(struct block_list *bl,va_list ap) // Added by RoVeRT +{ + char *event=va_arg(ap,char *); + struct mob_data *md = ((struct mob_data *)bl); + if( md->status.hp > 0 && (!event || strcmp(event,md->npc_event) == 0) ) + return 1; + return 0; +} + +BUILDIN_FUNC(mobcount) // Added by RoVeRT +{ + const char *mapname,*event; + int16 m; + mapname=script_getstr(st,2); + event=script_getstr(st,3); + + if( strcmp(event, "all") == 0 ) + event = NULL; + else + check_event(st, event); + + if( strcmp(mapname, "this") == 0 ) { + struct map_session_data *sd = script_rid2sd(st); + if( sd ) + m = sd->bl.m; + else { + script_pushint(st,-1); + return 0; + } + } + else if( (m = map_mapname2mapid(mapname)) < 0 ) { + script_pushint(st,-1); + return 0; + } + + if( map[m].flag.src4instance && map[m].instance_id == 0 && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + { + script_pushint(st,-1); + return 0; + } + + script_pushint(st,map_foreachinmap(buildin_mobcount_sub, m, BL_MOB, event)); + + return 0; +} + +BUILDIN_FUNC(marriage) +{ + const char *partner=script_getstr(st,2); + TBL_PC *sd=script_rid2sd(st); + TBL_PC *p_sd=map_nick2sd(partner); + + if(sd==NULL || p_sd==NULL || pc_marriage(sd,p_sd) < 0){ + script_pushint(st,0); + return 0; + } + script_pushint(st,1); + return 0; +} +BUILDIN_FUNC(wedding_effect) +{ + TBL_PC *sd=script_rid2sd(st); + struct block_list *bl; + + if(sd==NULL) { + bl=map_id2bl(st->oid); + } else + bl=&sd->bl; + clif_wedding_effect(bl); + return 0; +} +BUILDIN_FUNC(divorce) +{ + TBL_PC *sd=script_rid2sd(st); + if(sd==NULL || pc_divorce(sd) < 0){ + script_pushint(st,0); + return 0; + } + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(ispartneron) +{ + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || !pc_ismarried(sd) || + map_charid2sd(sd->status.partner_id) == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(getpartnerid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.partner_id); + return 0; +} + +BUILDIN_FUNC(getchildid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.child); + return 0; +} + +BUILDIN_FUNC(getmotherid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.mother); + return 0; +} + +BUILDIN_FUNC(getfatherid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.father); + return 0; +} + +BUILDIN_FUNC(warppartner) +{ + int x,y; + unsigned short mapindex; + const char *str; + TBL_PC *sd=script_rid2sd(st); + TBL_PC *p_sd=NULL; + + if(sd==NULL || !pc_ismarried(sd) || + (p_sd=map_charid2sd(sd->status.partner_id)) == NULL) { + script_pushint(st,0); + return 0; + } + + str=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + + mapindex = mapindex_name2id(str); + if (mapindex) { + pc_setpos(p_sd,mapindex,x,y,CLR_OUTSIGHT); + script_pushint(st,1); + } else + script_pushint(st,0); + return 0; +} + +/*================================================ + * Script for Displaying MOB Information [Valaris] + *------------------------------------------------*/ +BUILDIN_FUNC(strmobinfo) +{ + + int num=script_getnum(st,2); + int class_=script_getnum(st,3); + + if(!mobdb_checkid(class_)) + { + if (num < 3) //requested a string + script_pushconststr(st,""); + else + script_pushint(st,0); + return 0; + } + + switch (num) { + case 1: script_pushstrcopy(st,mob_db(class_)->name); break; + case 2: script_pushstrcopy(st,mob_db(class_)->jname); break; + case 3: script_pushint(st,mob_db(class_)->lv); break; + case 4: script_pushint(st,mob_db(class_)->status.max_hp); break; + case 5: script_pushint(st,mob_db(class_)->status.max_sp); break; + case 6: script_pushint(st,mob_db(class_)->base_exp); break; + case 7: script_pushint(st,mob_db(class_)->job_exp); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Summon guardians [Valaris] + * guardian("<map name>",<x>,<y>,"<name to show>",<mob id>{,"<event label>"}{,<guardian index>}) -> <id> + *------------------------------------------*/ +BUILDIN_FUNC(guardian) +{ + int class_=0,x=0,y=0,guardian=0; + const char *str,*map,*evt=""; + struct script_data *data; + bool has_index = false; + + map =script_getstr(st,2); + x =script_getnum(st,3); + y =script_getnum(st,4); + str =script_getstr(st,5); + class_=script_getnum(st,6); + + if( script_hasdata(st,8) ) + {// "<event label>",<guardian index> + evt=script_getstr(st,7); + guardian=script_getnum(st,8); + has_index = true; + } else if( script_hasdata(st,7) ){ + data=script_getdata(st,7); + get_val(st,data); + if( data_isstring(data) ) + {// "<event label>" + evt=script_getstr(st,7); + } else if( data_isint(data) ) + {// <guardian index> + guardian=script_getnum(st,7); + has_index = true; + } else { + ShowError("script:guardian: invalid data type for argument #6 (from 1)\n"); + script_reportdata(data); + return 1; + } + } + + check_event(st, evt); + script_pushint(st, mob_spawn_guardian(map,x,y,str,class_,evt,guardian,has_index)); + + return 0; +} +/*========================================== + * Invisible Walls [Zephyrus] + *------------------------------------------*/ +BUILDIN_FUNC(setwall) +{ + const char *map, *name; + int x, y, m, size, dir; + bool shootable; + + map = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + size = script_getnum(st,5); + dir = script_getnum(st,6); + shootable = script_getnum(st,7); + name = script_getstr(st,8); + + if( (m = map_mapname2mapid(map)) < 0 ) + return 0; // Invalid Map + + map_iwall_set(m, x, y, size, dir, shootable, name); + return 0; +} +BUILDIN_FUNC(delwall) +{ + const char *name = script_getstr(st,2); + map_iwall_remove(name); + + return 0; +} + +/// Retrieves various information about the specified guardian. +/// +/// guardianinfo("<map_name>", <index>, <type>) -> <value> +/// type: 0 - whether it is deployed or not +/// 1 - maximum hp +/// 2 - current hp +/// +BUILDIN_FUNC(guardianinfo) +{ + const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int id = script_getnum(st,3); + int type = script_getnum(st,4); + + struct guild_castle* gc = guild_mapname2gc(mapname); + struct mob_data* gd; + + if( gc == NULL || id < 0 || id >= MAX_GUARDIANS ) + { + script_pushint(st,-1); + return 0; + } + + if( type == 0 ) + script_pushint(st, gc->guardian[id].visible); + else + if( !gc->guardian[id].visible ) + script_pushint(st,-1); + else + if( (gd = map_id2md(gc->guardian[id].id)) == NULL ) + script_pushint(st,-1); + else + { + if ( type == 1 ) script_pushint(st,gd->status.max_hp); + else if( type == 2 ) script_pushint(st,gd->status.hp); + else + script_pushint(st,-1); + } + + return 0; +} + +/*========================================== + * Get the item name by item_id or null + *------------------------------------------*/ +BUILDIN_FUNC(getitemname) +{ + int item_id=0; + struct item_data *i_data; + char *item_name; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + item_id=item_data->nameid; + }else + item_id=conv_num(st,data); + + i_data = itemdb_exists(item_id); + if (i_data == NULL) + { + script_pushconststr(st,"null"); + return 0; + } + item_name=(char *)aMalloc(ITEM_NAME_LENGTH*sizeof(char)); + + memcpy(item_name, i_data->jname, ITEM_NAME_LENGTH); + script_pushstr(st,item_name); + return 0; +} +/*========================================== + * Returns number of slots an item has. [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(getitemslots) +{ + int item_id; + struct item_data *i_data; + + item_id=script_getnum(st,2); + + i_data = itemdb_exists(item_id); + + if (i_data) + script_pushint(st,i_data->slot); + else + script_pushint(st,-1); + return 0; +} + +// TODO: add matk here if needed/once we get rid of RENEWAL + +/*========================================== + * Returns some values of an item [Lupus] + * Price, Weight, etc... + getiteminfo(itemID,n), where n + 0 value_buy; + 1 value_sell; + 2 type; + 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. + if = 0, then monsters don't drop it at all (rare or a quest item) + if = -1, then this item is sold in NPC shops only + 4 sex; + 5 equip; + 6 weight; + 7 atk; + 8 def; + 9 range; + 10 slot; + 11 look; + 12 elv; + 13 wlv; + 14 view id + *------------------------------------------*/ +BUILDIN_FUNC(getiteminfo) +{ + int item_id,n; + int *item_arr; + struct item_data *i_data; + + item_id = script_getnum(st,2); + n = script_getnum(st,3); + i_data = itemdb_exists(item_id); + + if (i_data && n>=0 && n<=14) { + item_arr = (int*)&i_data->value_buy; + script_pushint(st,item_arr[n]); + } else + script_pushint(st,-1); + return 0; +} + +/*========================================== + * Set some values of an item [Lupus] + * Price, Weight, etc... + setiteminfo(itemID,n,Value), where n + 0 value_buy; + 1 value_sell; + 2 type; + 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. + if = 0, then monsters don't drop it at all (rare or a quest item) + if = -1, then this item is sold in NPC shops only + 4 sex; + 5 equip; + 6 weight; + 7 atk; + 8 def; + 9 range; + 10 slot; + 11 look; + 12 elv; + 13 wlv; + 14 view id + * Returns Value or -1 if the wrong field's been set + *------------------------------------------*/ +BUILDIN_FUNC(setiteminfo) +{ + int item_id,n,value; + int *item_arr; + struct item_data *i_data; + + item_id = script_getnum(st,2); + n = script_getnum(st,3); + value = script_getnum(st,4); + i_data = itemdb_exists(item_id); + + if (i_data && n>=0 && n<=14) { + item_arr = (int*)&i_data->value_buy; + item_arr[n] = value; + script_pushint(st,value); + } else + script_pushint(st,-1); + return 0; +} + +/*========================================== + * Returns value from equipped item slot n [Lupus] + getequipcardid(num,slot) + where + num = eqip position slot + slot = 0,1,2,3 (Card Slot N) + + This func returns CARD ID, 255,254,-255 (for card 0, if the item is produced) + it's useful when you want to check item cards or if it's signed + Useful for such quests as "Sign this refined item with players name" etc + Hat[0] +4 -> Player's Hat[0] +4 + *------------------------------------------*/ +BUILDIN_FUNC(getequipcardid) +{ + int i=-1,num,slot; + TBL_PC *sd; + + num=script_getnum(st,2); + slot=script_getnum(st,3); + sd=script_rid2sd(st); + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && slot>=0 && slot<4) + script_pushint(st,sd->status.inventory[i].card[slot]); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * petskillbonus [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petskillbonus) +{ + struct pet_data *pd; + + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->bonus) + { //Clear previous bonus + if (pd->bonus->timer != INVALID_TIMER) + delete_timer(pd->bonus->timer, pet_skill_bonus_timer); + } else //init + pd->bonus = (struct pet_bonus *) aMalloc(sizeof(struct pet_bonus)); + + pd->bonus->type=script_getnum(st,2); + pd->bonus->val=script_getnum(st,3); + pd->bonus->duration=script_getnum(st,4); + pd->bonus->delay=script_getnum(st,5); + + if (pd->state.skillbonus == 1) + pd->state.skillbonus=0; // waiting state + + // wait for timer to start + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->bonus->timer = INVALID_TIMER; + else + pd->bonus->timer = add_timer(gettick()+pd->bonus->delay*1000, pet_skill_bonus_timer, sd->bl.id, 0); + + return 0; +} + +/*========================================== + * pet looting [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petloot) +{ + int max; + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + max=script_getnum(st,2); + + if(max < 1) + max = 1; //Let'em loot at least 1 item. + else if (max > MAX_PETLOOT_SIZE) + max = MAX_PETLOOT_SIZE; + + pd = sd->pd; + if (pd->loot != NULL) + { //Release whatever was there already and reallocate memory + pet_lootitem_drop(pd, pd->msd); + aFree(pd->loot->item); + } + else + pd->loot = (struct pet_loot *)aMalloc(sizeof(struct pet_loot)); + + pd->loot->item = (struct item *)aCalloc(max,sizeof(struct item)); + + pd->loot->max=max; + pd->loot->count = 0; + pd->loot->weight = 0; + + return 0; +} +/*========================================== + * Set arrays with info of all sd inventory : + * @inventorylist_id, @inventorylist_amount, @inventorylist_equip, + * @inventorylist_refine, @inventorylist_identify, @inventorylist_attribute, + * @inventorylist_card(0..3), @inventorylist_expire + * @inventorylist_count = scalar + *------------------------------------------*/ +BUILDIN_FUNC(getinventorylist) +{ + TBL_PC *sd=script_rid2sd(st); + char card_var[NAME_LENGTH]; + + int i,j=0,k; + if(!sd) return 0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount > 0){ + pc_setreg(sd,reference_uid(add_str("@inventorylist_id"), j),sd->status.inventory[i].nameid); + pc_setreg(sd,reference_uid(add_str("@inventorylist_amount"), j),sd->status.inventory[i].amount); + pc_setreg(sd,reference_uid(add_str("@inventorylist_equip"), j),sd->status.inventory[i].equip); + pc_setreg(sd,reference_uid(add_str("@inventorylist_refine"), j),sd->status.inventory[i].refine); + pc_setreg(sd,reference_uid(add_str("@inventorylist_identify"), j),sd->status.inventory[i].identify); + pc_setreg(sd,reference_uid(add_str("@inventorylist_attribute"), j),sd->status.inventory[i].attribute); + for (k = 0; k < MAX_SLOTS; k++) + { + sprintf(card_var, "@inventorylist_card%d",k+1); + pc_setreg(sd,reference_uid(add_str(card_var), j),sd->status.inventory[i].card[k]); + } + pc_setreg(sd,reference_uid(add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time); + j++; + } + } + pc_setreg(sd,add_str("@inventorylist_count"),j); + return 0; +} + +BUILDIN_FUNC(getskilllist) +{ + TBL_PC *sd=script_rid2sd(st); + int i,j=0; + if(!sd) return 0; + for(i=0;i<MAX_SKILL;i++){ + if(sd->status.skill[i].id > 0 && sd->status.skill[i].lv > 0){ + pc_setreg(sd,reference_uid(add_str("@skilllist_id"), j),sd->status.skill[i].id); + pc_setreg(sd,reference_uid(add_str("@skilllist_lv"), j),sd->status.skill[i].lv); + pc_setreg(sd,reference_uid(add_str("@skilllist_flag"), j),sd->status.skill[i].flag); + j++; + } + } + pc_setreg(sd,add_str("@skilllist_count"),j); + return 0; +} + +BUILDIN_FUNC(clearitem) +{ + TBL_PC *sd=script_rid2sd(st); + int i; + if(sd==NULL) return 0; + for (i=0; i<MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_SCRIPT); + } + } + return 0; +} + +/*========================================== + * Disguise Player (returns Mob/NPC ID if success, 0 on fail) + *------------------------------------------*/ +BUILDIN_FUNC(disguise) +{ + int id; + TBL_PC* sd = script_rid2sd(st); + if (sd == NULL) return 0; + + id = script_getnum(st,2); + + if (mobdb_checkid(id) || npcdb_checkid(id)) { + pc_disguise(sd, id); + script_pushint(st,id); + } else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Undisguise Player (returns 1 if success, 0 on fail) + *------------------------------------------*/ +BUILDIN_FUNC(undisguise) +{ + TBL_PC* sd = script_rid2sd(st); + if (sd == NULL) return 0; + + if (sd->disguise) { + pc_disguise(sd, 0); + script_pushint(st,0); + } else { + script_pushint(st,1); + } + return 0; +} + +/*========================================== + * Transform a bl to another _class, + * @type unused + *------------------------------------------*/ +BUILDIN_FUNC(classchange) +{ + int _class,type; + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) return 0; + + _class=script_getnum(st,2); + type=script_getnum(st,3); + clif_class_change(bl,_class,type); + return 0; +} + +/*========================================== + * Display an effect + *------------------------------------------*/ +BUILDIN_FUNC(misceffect) +{ + int type; + + type=script_getnum(st,2); + if(st->oid && st->oid != fake_nd->bl.id) { + struct block_list *bl = map_id2bl(st->oid); + if (bl) + clif_specialeffect(bl,type,AREA); + } else{ + TBL_PC *sd=script_rid2sd(st); + if(sd) + clif_specialeffect(&sd->bl,type,AREA); + } + return 0; +} +/*========================================== + * Play a BGM on a single client [Rikter/Yommy] + *------------------------------------------*/ +BUILDIN_FUNC(playBGM) +{ + const char* name; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) != NULL ) + { + name = script_getstr(st,2); + + clif_playBGM(sd, name); + } + + return 0; +} + +static int playBGM_sub(struct block_list* bl,va_list ap) +{ + const char* name = va_arg(ap,const char*); + + clif_playBGM(BL_CAST(BL_PC, bl), name); + + return 0; +} + +static int playBGM_foreachpc_sub(struct map_session_data* sd, va_list args) +{ + const char* name = va_arg(args, const char*); + + clif_playBGM(sd, name); + return 0; +} + +/*========================================== + * Play a BGM on multiple client [Rikter/Yommy] + *------------------------------------------*/ +BUILDIN_FUNC(playBGMall) +{ + const char* name; + + name = script_getstr(st,2); + + if( script_hasdata(st,7) ) + {// specified part of map + const char* map = script_getstr(st,3); + int x0 = script_getnum(st,4); + int y0 = script_getnum(st,5); + int x1 = script_getnum(st,6); + int y1 = script_getnum(st,7); + + map_foreachinarea(playBGM_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name); + } + else if( script_hasdata(st,3) ) + {// entire map + const char* map = script_getstr(st,3); + + map_foreachinmap(playBGM_sub, map_mapname2mapid(map), BL_PC, name); + } + else + {// entire server + map_foreachpc(&playBGM_foreachpc_sub, name); + } + + return 0; +} + +/*========================================== + * Play a .wav sound for sd + *------------------------------------------*/ +BUILDIN_FUNC(soundeffect) +{ + TBL_PC* sd = script_rid2sd(st); + const char* name = script_getstr(st,2); + int type = script_getnum(st,3); + + if(sd) + { + clif_soundeffect(sd,&sd->bl,name,type); + } + return 0; +} + +int soundeffect_sub(struct block_list* bl,va_list ap) +{ + char* name = va_arg(ap,char*); + int type = va_arg(ap,int); + + clif_soundeffect((TBL_PC *)bl, bl, name, type); + + return 0; +} + +/*========================================== + * Play a sound effect (.wav) on multiple clients + * soundeffectall "<filepath>",<type>{,"<map name>"}{,<x0>,<y0>,<x1>,<y1>}; + *------------------------------------------*/ +BUILDIN_FUNC(soundeffectall) +{ + struct block_list* bl; + const char* name; + int type; + + bl = (st->rid) ? &(script_rid2sd(st)->bl) : map_id2bl(st->oid); + if (!bl) + return 0; + + name = script_getstr(st,2); + type = script_getnum(st,3); + + //FIXME: enumerating map squares (map_foreach) is slower than enumerating the list of online players (map_foreachpc?) [ultramage] + + if(!script_hasdata(st,4)) + { // area around + clif_soundeffectall(bl, name, type, AREA); + } + else + if(!script_hasdata(st,5)) + { // entire map + const char* map = script_getstr(st,4); + map_foreachinmap(soundeffect_sub, map_mapname2mapid(map), BL_PC, name, type); + } + else + if(script_hasdata(st,8)) + { // specified part of map + const char* map = script_getstr(st,4); + int x0 = script_getnum(st,5); + int y0 = script_getnum(st,6); + int x1 = script_getnum(st,7); + int y1 = script_getnum(st,8); + map_foreachinarea(soundeffect_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name, type); + } + else + { + ShowError("buildin_soundeffectall: insufficient arguments for specific area broadcast.\n"); + } + + return 0; +} +/*========================================== + * pet status recovery [Valaris] / Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petrecovery) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if (pd->recovery) + { //Halt previous bonus + if (pd->recovery->timer != INVALID_TIMER) + delete_timer(pd->recovery->timer, pet_recovery_timer); + } else //Init + pd->recovery = (struct pet_recovery *)aMalloc(sizeof(struct pet_recovery)); + + pd->recovery->type = (sc_type)script_getnum(st,2); + pd->recovery->delay = script_getnum(st,3); + pd->recovery->timer = INVALID_TIMER; + + return 0; +} + +/*========================================== + * pet healing [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petheal) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->s_skill) + { //Clear previous skill + if (pd->s_skill->timer != INVALID_TIMER) + { + if (pd->s_skill->id) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + } + } else //init memory + pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); + + pd->s_skill->id=0; //This id identifies that it IS petheal rather than pet_skillsupport + //Use the lv as the amount to heal + pd->s_skill->lv=script_getnum(st,2); + pd->s_skill->delay=script_getnum(st,3); + pd->s_skill->hp=script_getnum(st,4); + pd->s_skill->sp=script_getnum(st,5); + + //Use delay as initial offset to avoid skill/heal exploits + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->s_skill->timer = INVALID_TIMER; + else + pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +/// petskillattack <skill id>,<level>,<rate>,<bonusrate> +/// petskillattack "<skill name>",<level>,<rate>,<bonusrate> +BUILDIN_FUNC(petskillattack) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->a_skill == NULL) + pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); + + pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->a_skill->lv=script_getnum(st,3); + pd->a_skill->div_ = 0; + pd->a_skill->rate=script_getnum(st,4); + pd->a_skill->bonusrate=script_getnum(st,5); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] + *------------------------------------------*/ +/// petskillattack2 <skill id>,<level>,<div>,<rate>,<bonusrate> +/// petskillattack2 "<skill name>",<level>,<div>,<rate>,<bonusrate> +BUILDIN_FUNC(petskillattack2) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->a_skill == NULL) + pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); + + pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->a_skill->lv=script_getnum(st,3); + pd->a_skill->div_ = script_getnum(st,4); + pd->a_skill->rate=script_getnum(st,5); + pd->a_skill->bonusrate=script_getnum(st,6); + + return 0; +} + +/*========================================== + * pet support skills [Skotlex] + *------------------------------------------*/ +/// petskillsupport <skill id>,<level>,<delay>,<hp>,<sp> +/// petskillsupport "<skill name>",<level>,<delay>,<hp>,<sp> +BUILDIN_FUNC(petskillsupport) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->s_skill) + { //Clear previous skill + if (pd->s_skill->timer != INVALID_TIMER) + { + if (pd->s_skill->id) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + } + } else //init memory + pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); + + pd->s_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->s_skill->lv=script_getnum(st,3); + pd->s_skill->delay=script_getnum(st,4); + pd->s_skill->hp=script_getnum(st,5); + pd->s_skill->sp=script_getnum(st,6); + + //Use delay as initial offset to avoid skill/heal exploits + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->s_skill->timer = INVALID_TIMER; + else + pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_skill_support_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * Scripted skill effects [Celest] + *------------------------------------------*/ +/// skilleffect <skill id>,<level> +/// skilleffect "<skill name>",<level> +BUILDIN_FUNC(skilleffect) +{ + TBL_PC *sd; + + uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + uint16 skill_lv=script_getnum(st,3); + sd=script_rid2sd(st); + + clif_skill_nodamage(&sd->bl,&sd->bl,skill_id,skill_lv,1); + + return 0; +} + +/*========================================== + * NPC skill effects [Valaris] + *------------------------------------------*/ +/// npcskilleffect <skill id>,<level>,<x>,<y> +/// npcskilleffect "<skill name>",<level>,<x>,<y> +BUILDIN_FUNC(npcskilleffect) +{ + struct block_list *bl= map_id2bl(st->oid); + + uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + uint16 skill_lv=script_getnum(st,3); + int x=script_getnum(st,4); + int y=script_getnum(st,5); + + if (bl) + clif_skill_poseffect(bl,skill_id,skill_lv,x,y,gettick()); + + return 0; +} + +/*========================================== + * Special effects [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(specialeffect) +{ + struct block_list *bl=map_id2bl(st->oid); + int type = script_getnum(st,2); + enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; + + if(bl==NULL) + return 0; + + if( script_hasdata(st,4) ) + { + TBL_NPC *nd = npc_name2id(script_getstr(st,4)); + if(nd) + clif_specialeffect(&nd->bl, type, target); + } + else + { + if (target == SELF) { + TBL_PC *sd=script_rid2sd(st); + if (sd) + clif_specialeffect_single(bl,type,sd->fd); + } else { + clif_specialeffect(bl, type, target); + } + } + + return 0; +} + +BUILDIN_FUNC(specialeffect2) +{ + TBL_PC *sd=script_rid2sd(st); + int type = script_getnum(st,2); + enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; + + if( script_hasdata(st,4) ) + sd = map_nick2sd(script_getstr(st,4)); + + if (sd) + clif_specialeffect(&sd->bl, type, target); + + return 0; +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(nude) +{ + TBL_PC *sd = script_rid2sd(st); + int i, calcflag = 0; + + if( sd == NULL ) + return 0; + + for( i = 0 ; i < EQI_MAX; i++ ) { + if( sd->equip_index[ i ] >= 0 ) { + if( !calcflag ) + calcflag = 1; + pc_unequipitem( sd , sd->equip_index[ i ] , 2); + } + } + + if( calcflag ) + status_calc_pc(sd,0); + + return 0; +} + +/*========================================== + * gmcommand [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(atcommand) +{ + TBL_PC dummy_sd; + TBL_PC* sd; + int fd; + const char* cmd; + + cmd = script_getstr(st,2); + + if (st->rid) { + sd = script_rid2sd(st); + fd = sd->fd; + } else { //Use a dummy character. + sd = &dummy_sd; + fd = 0; + + memset(&dummy_sd, 0, sizeof(TBL_PC)); + if (st->oid) + { + struct block_list* bl = map_id2bl(st->oid); + memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); + if (bl->type == BL_NPC) + safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); + } + } + + if (!is_atcommand(fd, sd, cmd, 0)) { + ShowWarning("script: buildin_atcommand: failed to execute command '%s'\n", cmd); + script_reportsrc(st); + return 1; + } + + return 0; +} + +/*========================================== + * Displays a message for the player only (like system messages like "you got an apple" ) + *------------------------------------------*/ +BUILDIN_FUNC(dispbottom) +{ + TBL_PC *sd=script_rid2sd(st); + const char *message; + message=script_getstr(st,2); + if(sd) + clif_disp_onlyself(sd,message,(int)strlen(message)); + return 0; +} + +/*========================================== + * All The Players Full Recovery + * (HP/SP full restore and resurrect if need) + *------------------------------------------*/ +BUILDIN_FUNC(recovery) +{ + TBL_PC* sd; + struct s_mapiterator* iter; + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if(pc_isdead(sd)) + status_revive(&sd->bl, 100, 100); + else + status_percent_heal(&sd->bl, 100, 100); + clif_displaymessage(sd->fd,msg_txt(680)); + } + mapit_free(iter); + return 0; +} +/*========================================== + * Get your pet info: getpetinfo(n) + * n -> 0:pet_id 1:pet_class 2:pet_name + * 3:friendly 4:hungry, 5: rename flag. + *------------------------------------------*/ +BUILDIN_FUNC(getpetinfo) +{ + TBL_PC *sd=script_rid2sd(st); + TBL_PET *pd; + int type=script_getnum(st,2); + + if(!sd || !sd->pd) { + if (type == 2) + script_pushconststr(st,"null"); + else + script_pushint(st,0); + return 0; + } + pd = sd->pd; + switch(type){ + case 0: script_pushint(st,pd->pet.pet_id); break; + case 1: script_pushint(st,pd->pet.class_); break; + case 2: script_pushstrcopy(st,pd->pet.name); break; + case 3: script_pushint(st,pd->pet.intimate); break; + case 4: script_pushint(st,pd->pet.hungry); break; + case 5: script_pushint(st,pd->pet.rename_flag); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Get your homunculus info: gethominfo(n) + * n -> 0:hom_id 1:class 2:name + * 3:friendly 4:hungry, 5: rename flag. + * 6: level + *------------------------------------------*/ +BUILDIN_FUNC(gethominfo) +{ + TBL_PC *sd=script_rid2sd(st); + TBL_HOM *hd; + int type=script_getnum(st,2); + + hd = sd?sd->hd:NULL; + if(!merc_is_hom_active(hd)) + { + if (type == 2) + script_pushconststr(st,"null"); + else + script_pushint(st,0); + return 0; + } + + switch(type){ + case 0: script_pushint(st,hd->homunculus.hom_id); break; + case 1: script_pushint(st,hd->homunculus.class_); break; + case 2: script_pushstrcopy(st,hd->homunculus.name); break; + case 3: script_pushint(st,hd->homunculus.intimacy); break; + case 4: script_pushint(st,hd->homunculus.hunger); break; + case 5: script_pushint(st,hd->homunculus.rename_flag); break; + case 6: script_pushint(st,hd->homunculus.level); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/// Retrieves information about character's mercenary +/// getmercinfo <type>[,<char id>]; +BUILDIN_FUNC(getmercinfo) +{ + int type, char_id; + struct map_session_data* sd; + struct mercenary_data* md; + + type = script_getnum(st,2); + + if( script_hasdata(st,3) ) + { + char_id = script_getnum(st,3); + + if( ( sd = map_charid2sd(char_id) ) == NULL ) + { + ShowError("buildin_getmercinfo: No such character (char_id=%d).\n", char_id); + script_pushnil(st); + return 1; + } + } + else + { + if( ( sd = script_rid2sd(st) ) == NULL ) + { + script_pushnil(st); + return 0; + } + } + + md = ( sd->status.mer_id && sd->md ) ? sd->md : NULL; + + switch( type ) + { + case 0: script_pushint(st,md ? md->mercenary.mercenary_id : 0); break; + case 1: script_pushint(st,md ? md->mercenary.class_ : 0); break; + case 2: + if( md ) + script_pushstrcopy(st,md->db->name); + else + script_pushconststr(st,""); + break; + case 3: script_pushint(st,md ? mercenary_get_faith(md) : 0); break; + case 4: script_pushint(st,md ? mercenary_get_calls(md) : 0); break; + case 5: script_pushint(st,md ? md->mercenary.kill_count : 0); break; + case 6: script_pushint(st,md ? mercenary_get_lifetime(md) : 0); break; + case 7: script_pushint(st,md ? md->db->lv : 0); break; + default: + ShowError("buildin_getmercinfo: Invalid type %d (char_id=%d).\n", type, sd->status.char_id); + script_pushnil(st); + return 1; + } + + return 0; +} + +/*========================================== + * Shows wether your inventory(and equips) contain + selected card or not. + checkequipedcard(4001); + *------------------------------------------*/ +BUILDIN_FUNC(checkequipedcard) +{ + TBL_PC *sd=script_rid2sd(st); + + if(sd){ + int n,i,c=0; + c=script_getnum(st,2); + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount && sd->inventory_data[i]){ + if (itemdb_isspecial(sd->status.inventory[i].card[0])) + continue; + for(n=0;n<sd->inventory_data[i]->slot;n++){ + if(sd->status.inventory[i].card[n]==c){ + script_pushint(st,1); + return 0; + } + } + } + } + } + script_pushint(st,0); + return 0; +} + +BUILDIN_FUNC(jump_zero) +{ + int sel; + sel=script_getnum(st,2); + if(!sel) { + int pos; + if( !data_islabel(script_getdata(st,3)) ){ + ShowError("script: jump_zero: not label !\n"); + st->state=END; + return 1; + } + + pos=script_getnum(st,3); + st->pos=pos; + st->state=GOTO; + } + return 0; +} + +/*========================================== + * movenpc [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(movenpc) +{ + TBL_NPC *nd = NULL; + const char *npc; + int x,y; + + npc = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + + if ((nd = npc_name2id(npc)) == NULL) + return -1; + + if (script_hasdata(st,5)) + nd->ud.dir = script_getnum(st,5) % 8; + npc_movenpc(nd, x, y); + return 0; +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(message) +{ + const char *msg,*player; + TBL_PC *pl_sd = NULL; + + player = script_getstr(st,2); + msg = script_getstr(st,3); + + if((pl_sd=map_nick2sd((char *) player)) == NULL) + return 0; + clif_displaymessage(pl_sd->fd, msg); + + return 0; +} + +/*========================================== + * npctalk (sends message to surrounding area) + *------------------------------------------*/ +BUILDIN_FUNC(npctalk) +{ + const char* str; + char name[NAME_LENGTH], message[256]; + + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + str = script_getstr(st,2); + + if(nd) + { + safestrncpy(name, nd->name, sizeof(name)); + strtok(name, "#"); // discard extra name identifier if present + safesnprintf(message, sizeof(message), "%s : %s", name, str); + clif_message(&nd->bl, message); + } + + return 0; +} + +// change npc walkspeed [Valaris] +BUILDIN_FUNC(npcspeed) +{ + struct npc_data* nd; + int speed; + + speed = script_getnum(st,2); + nd =(struct npc_data *)map_id2bl(st->oid); + + if( nd ) + { + nd->speed = speed; + nd->ud.state.speed_changed = 1; + } + + return 0; +} +// make an npc walk to a position [Valaris] +BUILDIN_FUNC(npcwalkto) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + int x=0,y=0; + + x=script_getnum(st,2); + y=script_getnum(st,3); + + if(nd) { + unit_walktoxy(&nd->bl,x,y,0); + } + + return 0; +} +// stop an npc's movement [Valaris] +BUILDIN_FUNC(npcstop) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd) { + unit_stop_walking(&nd->bl,1|4); + } + + return 0; +} + + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------*/ +BUILDIN_FUNC(getlook) +{ + int type,val; + TBL_PC *sd; + sd=script_rid2sd(st); + + type=script_getnum(st,2); + val=-1; + switch(type) { + case LOOK_HAIR: val=sd->status.hair; break; //1 + case LOOK_WEAPON: val=sd->status.weapon; break; //2 + case LOOK_HEAD_BOTTOM: val=sd->status.head_bottom; break; //3 + case LOOK_HEAD_TOP: val=sd->status.head_top; break; //4 + case LOOK_HEAD_MID: val=sd->status.head_mid; break; //5 + case LOOK_HAIR_COLOR: val=sd->status.hair_color; break; //6 + case LOOK_CLOTHES_COLOR: val=sd->status.clothes_color; break; //7 + case LOOK_SHIELD: val=sd->status.shield; break; //8 + case LOOK_SHOES: break; //9 + } + + script_pushint(st,val); + return 0; +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------*/ +BUILDIN_FUNC(getsavepoint) +{ + TBL_PC* sd; + int type; + + sd = script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + type = script_getnum(st,2); + + switch(type) { + case 0: script_pushstrcopy(st,mapindex_id2name(sd->status.save_point.map)); break; + case 1: script_pushint(st,sd->status.save_point.x); break; + case 2: script_pushint(st,sd->status.save_point.y); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Get position for char/npc/pet/mob objects. Added by Lorky + * + * int getMapXY(MapName$,MapX,MapY,type,[CharName$]); + * where type: + * MapName$ - String variable for output map name + * MapX - Integer variable for output coord X + * MapY - Integer variable for output coord Y + * type - type of object + * 0 - Character coord + * 1 - NPC coord + * 2 - Pet coord + * 3 - Mob coord (not released) + * 4 - Homun coord + * 5 - Mercenary coord + * 6 - Elemental coord + * CharName$ - Name object. If miss or "this" the current object + * + * Return: + * 0 - success + * -1 - some error, MapName$,MapX,MapY contains unknown value. + *------------------------------------------*/ +BUILDIN_FUNC(getmapxy) +{ + struct block_list *bl = NULL; + TBL_PC *sd=NULL; + + int num; + const char *name; + char prefix; + + int x,y,type; + char mapname[MAP_NAME_LENGTH]; + + if( !data_isreference(script_getdata(st,2)) ){ + ShowWarning("script: buildin_getmapxy: not mapname variable\n"); + script_pushint(st,-1); + return 1; + } + if( !data_isreference(script_getdata(st,3)) ){ + ShowWarning("script: buildin_getmapxy: not mapx variable\n"); + script_pushint(st,-1); + return 1; + } + if( !data_isreference(script_getdata(st,4)) ){ + ShowWarning("script: buildin_getmapxy: not mapy variable\n"); + script_pushint(st,-1); + return 1; + } + + // Possible needly check function parameters on C_STR,C_INT,C_INT + type=script_getnum(st,5); + + switch (type){ + case 0: //Get Character Position + if( script_hasdata(st,6) ) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd) + bl = &sd->bl; + break; + case 1: //Get NPC Position + if( script_hasdata(st,6) ) + { + struct npc_data *nd; + nd=npc_name2id(script_getstr(st,6)); + if (nd) + bl = &nd->bl; + } else //In case the origin is not an npc? + bl=map_id2bl(st->oid); + break; + case 2: //Get Pet Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->pd) + bl = &sd->pd->bl; + break; + case 3: //Get Mob Position + break; //Not supported? + case 4: //Get Homun Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->hd) + bl = &sd->hd->bl; + break; + case 5: //Get Mercenary Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->md) + bl = &sd->md->bl; + break; + case 6: //Get Elemental Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->ed) + bl = &sd->ed->bl; + break; + default: + ShowWarning("script: buildin_getmapxy: Invalid type %d\n", type); + script_pushint(st,-1); + return 1; + } + if (!bl) { //No object found. + script_pushint(st,-1); + return 0; + } + + x= bl->x; + y= bl->y; + safestrncpy(mapname, map[bl->m].name, MAP_NAME_LENGTH); + + //Set MapName$ + num=st->stack->stack_data[st->start+2].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)mapname,script_getref(st,2)); + + //Set MapX + num=st->stack->stack_data[st->start+3].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(x),script_getref(st,3)); + + //Set MapY + num=st->stack->stack_data[st->start+4].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(y),script_getref(st,4)); + + //Return Success value + script_pushint(st,0); + return 0; +} + +/*========================================== + * Allows player to write NPC logs (i.e. Bank NPC, etc) [Lupus] + *------------------------------------------*/ +BUILDIN_FUNC(logmes) +{ + const char *str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 1; + + str = script_getstr(st,2); + log_npc(sd,str); + return 0; +} + +BUILDIN_FUNC(summon) +{ + int _class, timeout=0; + const char *str,*event=""; + TBL_PC *sd; + struct mob_data *md; + int tick = gettick(); + + sd=script_rid2sd(st); + if (!sd) return 0; + + str =script_getstr(st,2); + _class=script_getnum(st,3); + if( script_hasdata(st,4) ) + timeout=script_getnum(st,4); + if( script_hasdata(st,5) ){ + event=script_getstr(st,5); + check_event(st, event); + } + + clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,sd->bl.x,sd->bl.y,tick); + + md = mob_once_spawn_sub(&sd->bl, sd->bl.m, sd->bl.x, sd->bl.y, str, _class, event, SZ_SMALL, AI_NONE); + if (md) { + md->master_id=sd->bl.id; + md->special_state.ai = AI_ATTACK; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer(tick+(timeout>0?timeout*1000:60000),mob_timer_delete,md->bl.id,0); + mob_spawn (md); //Now it is ready for spawning. + clif_specialeffect(&md->bl,344,AREA); + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000); + } + return 0; +} + +/*========================================== + * Checks whether it is daytime/nighttime + *------------------------------------------*/ +BUILDIN_FUNC(isnight) +{ + script_pushint(st,(night_flag == 1)); + return 0; +} + +BUILDIN_FUNC(isday) +{ + script_pushint(st,(night_flag == 0)); + return 0; +} + +/*================================================ + * Check how many items/cards in the list are + * equipped - used for 2/15's cards patch [celest] + *------------------------------------------------*/ +BUILDIN_FUNC(isequippedcnt) +{ + TBL_PC *sd; + int i, j, k, id = 1; + int ret = 0; + + sd = script_rid2sd(st); + if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... + script_pushint(st,0); + return 0; + } + + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + + for (j=0; j<EQI_MAX; j++) { + int index; + index = sd->equip_index[j]; + if(index < 0) continue; + if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; + if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; + if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; + + if(!sd->inventory_data[index]) + continue; + + if (itemdb_type(id) != IT_CARD) { //No card. Count amount in inventory. + if (sd->inventory_data[index]->nameid == id) + ret+= sd->status.inventory[index].amount; + } else { //Count cards. + if (itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; //No cards + for(k=0; k<sd->inventory_data[index]->slot; k++) { + if (sd->status.inventory[index].card[k] == id) + ret++; //[Lupus] + } + } + } + } + + script_pushint(st,ret); + return 0; +} + +/*================================================ + * Check whether another card has been + * equipped - used for 2/15's cards patch [celest] + * -- Items checked cannot be reused in another + * card set to prevent exploits + *------------------------------------------------*/ +BUILDIN_FUNC(isequipped) +{ + TBL_PC *sd; + int i, j, k, id = 1; + int index, flag; + int ret = -1; + //Original hash to reverse it when full check fails. + unsigned int setitem_hash = 0, setitem_hash2 = 0; + + sd = script_rid2sd(st); + + if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... + script_pushint(st,0); + return 0; + } + + setitem_hash = sd->bonus.setitem_hash; + setitem_hash2 = sd->bonus.setitem_hash2; + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + flag = 0; + for (j=0; j<EQI_MAX; j++) { + index = sd->equip_index[j]; + if(index < 0) continue; + if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; + if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; + if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; + + if(!sd->inventory_data[index]) + continue; + + if (itemdb_type(id) != IT_CARD) { + if (sd->inventory_data[index]->nameid != id) + continue; + flag = 1; + break; + } else { //Cards + if (sd->inventory_data[index]->slot == 0 || + itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + + for (k = 0; k < sd->inventory_data[index]->slot; k++) + { //New hash system which should support up to 4 slots on any equipment. [Skotlex] + unsigned int hash = 0; + if (sd->status.inventory[index].card[k] != id) + continue; + + hash = 1<<((j<5?j:j-5)*4 + k); + // check if card is already used by another set + if ( ( j < 5 ? sd->bonus.setitem_hash : sd->bonus.setitem_hash2 ) & hash) + continue; + + // We have found a match + flag = 1; + // Set hash so this card cannot be used by another + if (j<5) + sd->bonus.setitem_hash |= hash; + else + sd->bonus.setitem_hash2 |= hash; + break; + } + } + if (flag) break; //Card found + } + if (ret == -1) + ret = flag; + else + ret &= flag; + if (!ret) break; + } + if (!ret) {//When check fails, restore original hash values. [Skotlex] + sd->bonus.setitem_hash = setitem_hash; + sd->bonus.setitem_hash2 = setitem_hash2; + } + script_pushint(st,ret); + return 0; +} + +/*================================================ + * Check how many given inserted cards in the CURRENT + * weapon - used for 2/15's cards patch [Lupus] + *------------------------------------------------*/ +BUILDIN_FUNC(cardscnt) +{ + TBL_PC *sd; + int i, k, id = 1; + int ret = 0; + int index; + + sd = script_rid2sd(st); + + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + + index = current_equip_item_index; //we get CURRENT WEAPON inventory index from status.c [Lupus] + if(index < 0) continue; + + if(!sd->inventory_data[index]) + continue; + + if(itemdb_type(id) != IT_CARD) { + if (sd->inventory_data[index]->nameid == id) + ret+= sd->status.inventory[index].amount; + } else { + if (itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + for(k=0; k<sd->inventory_data[index]->slot; k++) { + if (sd->status.inventory[index].card[k] == id) + ret++; + } + } + } + script_pushint(st,ret); +// script_pushint(st,current_equip_item_index); + return 0; +} + +/*======================================================= + * Returns the refined number of the current item, or an + * item with inventory index specified + *-------------------------------------------------------*/ +BUILDIN_FUNC(getrefine) +{ + TBL_PC *sd; + if ((sd = script_rid2sd(st))!= NULL) + script_pushint(st,sd->status.inventory[current_equip_item_index].refine); + else + script_pushint(st,0); + return 0; +} + +/*======================================================= + * Day/Night controls + *-------------------------------------------------------*/ +BUILDIN_FUNC(night) +{ + if (night_flag != 1) map_night_timer(night_timer_tid, 0, 0, 1); + return 0; +} +BUILDIN_FUNC(day) +{ + if (night_flag != 0) map_day_timer(day_timer_tid, 0, 0, 1); + return 0; +} + +//======================================================= +// Unequip [Spectre] +//------------------------------------------------------- +BUILDIN_FUNC(unequip) +{ + int i; + size_t num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd != NULL && num >= 1 && num <= ARRAYLENGTH(equip) ) + { + i = pc_checkequip(sd,equip[num-1]); + if (i >= 0) + pc_unequipitem(sd,i,1|2); + } + return 0; +} + +BUILDIN_FUNC(equip) +{ + int nameid=0,i; + TBL_PC *sd; + struct item_data *item_data; + + sd = script_rid2sd(st); + + nameid=script_getnum(st,2); + if((item_data = itemdb_exists(nameid)) == NULL) + { + ShowError("wrong item ID : equipitem(%i)\n",nameid); + return 1; + } + ARR_FIND( 0, MAX_INVENTORY, i, sd->status.inventory[i].nameid == nameid ); + if( i < MAX_INVENTORY ) + pc_equipitem(sd,i,item_data->equip); + + return 0; +} + +BUILDIN_FUNC(autoequip) +{ + int nameid, flag; + struct item_data *item_data; + nameid=script_getnum(st,2); + flag=script_getnum(st,3); + + if( ( item_data = itemdb_exists(nameid) ) == NULL ) + { + ShowError("buildin_autoequip: Invalid item '%d'.\n", nameid); + return 1; + } + + if( !itemdb_isequip2(item_data) ) + { + ShowError("buildin_autoequip: Item '%d' cannot be equipped.\n", nameid); + return 1; + } + + item_data->flag.autoequip = flag>0?1:0; + return 0; +} + +BUILDIN_FUNC(setbattleflag) +{ + const char *flag, *value; + + flag = script_getstr(st,2); + value = script_getstr(st,3); // HACK: Retrieve number as string (auto-converted) for battle_set_value + + if (battle_set_value(flag, value) == 0) + ShowWarning("buildin_setbattleflag: unknown battle_config flag '%s'\n",flag); + else + ShowInfo("buildin_setbattleflag: battle_config flag '%s' is now set to '%s'.\n",flag,value); + + return 0; +} + +BUILDIN_FUNC(getbattleflag) +{ + const char *flag; + flag = script_getstr(st,2); + script_pushint(st,battle_get_value(flag)); + return 0; +} + +//======================================================= +// strlen [Valaris] +//------------------------------------------------------- +BUILDIN_FUNC(getstrlen) +{ + + const char *str = script_getstr(st,2); + int len = (str) ? (int)strlen(str) : 0; + + script_pushint(st,len); + return 0; +} + +//======================================================= +// isalpha [Valaris] +//------------------------------------------------------- +BUILDIN_FUNC(charisalpha) +{ + const char *str=script_getstr(st,2); + int pos=script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISALPHA( str[pos] ) != 0 : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charisupper <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charisupper) +{ + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISUPPER( str[pos] ) : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charislower <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charislower) +{ + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISLOWER( str[pos] ) : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charat <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charat) { + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + if( pos >= 0 && (unsigned int)pos < strlen(str) ) { + char output[2]; + output[0] = str[pos]; + output[1] = '\0'; + script_pushstrcopy(st, output); + } else + script_pushconststr(st, ""); + return 0; +} + +//======================================================= +// setchar <string>, <char>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(setchar) +{ + const char *str = script_getstr(st,2); + const char *c = script_getstr(st,3); + int index = script_getnum(st,4); + char *output = aStrdup(str); + + if(index >= 0 && index < strlen(output)) + output[index] = *c; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// insertchar <string>, <char>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(insertchar) +{ + const char *str = script_getstr(st,2); + const char *c = script_getstr(st,3); + int index = script_getnum(st,4); + char *output; + size_t len = strlen(str); + + if(index < 0) + index = 0; + else if(index > len) + index = len; + + output = (char*)aMalloc(len + 2); + + memcpy(output, str, index); + output[index] = c[0]; + memcpy(&output[index+1], &str[index], len - index); + output[len+1] = '\0'; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// delchar <string>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(delchar) +{ + const char *str = script_getstr(st,2); + int index = script_getnum(st,3); + char *output; + size_t len = strlen(str); + + if(index < 0 || index > len) { + //return original + output = aStrdup(str); + script_pushstr(st, output); + return 0; + } + + output = (char*)aMalloc(len); + + memcpy(output, str, index); + memcpy(&output[index], &str[index+1], len - index); + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// strtoupper <str> +//------------------------------------------------------- +BUILDIN_FUNC(strtoupper) +{ + const char *str = script_getstr(st,2); + char *output = aStrdup(str); + char *cursor = output; + + while (*cursor != '\0') { + *cursor = TOUPPER(*cursor); + cursor++; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// strtolower <str> +//------------------------------------------------------- +BUILDIN_FUNC(strtolower) +{ + const char *str = script_getstr(st,2); + char *output = aStrdup(str); + char *cursor = output; + + while (*cursor != '\0') { + *cursor = TOLOWER(*cursor); + cursor++; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// substr <str>, <start>, <end> +//------------------------------------------------------- +BUILDIN_FUNC(substr) +{ + const char *str = script_getstr(st,2); + char *output; + int start = script_getnum(st,3); + int end = script_getnum(st,4); + + int len = 0; + + if(start >= 0 && end < strlen(str) && start <= end) { + len = end - start + 1; + output = (char*)aMalloc(len + 1); + memcpy(output, &str[start], len); + } else + output = (char*)aMalloc(1); + + output[len] = '\0'; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// explode <dest_string_array>, <str>, <delimiter> +// Note: delimiter is limited to 1 char +//------------------------------------------------------- +BUILDIN_FUNC(explode) +{ + struct script_data* data = script_getdata(st, 2); + const char *str = script_getstr(st,3); + const char delimiter = script_getstr(st, 4)[0]; + int32 id; + size_t len = strlen(str); + int i = 0, j = 0; + int start; + + + char *temp; + const char* name; + + TBL_PC* sd = NULL; + + temp = (char*)aMalloc(len + 1); + + if( !data_isreference(data) ) + { + ShowError("script:explode: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + + if( not_array_variable(*name) ) + { + ShowError("script:explode: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( !is_string_variable(name) ) + { + ShowError("script:explode: not string array\n"); + script_reportdata(data); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + while(str[i] != '\0') { + if(str[i] == delimiter && start < SCRIPT_MAX_ARRAYSIZE-1) { //break at delimiter but ignore after reaching last array index + temp[j] = '\0'; + set_reg(st, sd, reference_uid(id, start++), name, (void*)temp, reference_getref(data)); + j = 0; + ++i; + } else { + temp[j++] = str[i++]; + } + } + //set last string + temp[j] = '\0'; + set_reg(st, sd, reference_uid(id, start), name, (void*)temp, reference_getref(data)); + + aFree(temp); + return 0; +} + +//======================================================= +// implode <string_array> +// implode <string_array>, <glue> +//------------------------------------------------------- +BUILDIN_FUNC(implode) +{ + struct script_data* data = script_getdata(st, 2); + const char *glue = NULL, *name, *temp; + int32 glue_len = 0, array_size, id; + size_t len = 0; + int i, k = 0; + + TBL_PC* sd = NULL; + + char *output; + + if( !data_isreference(data) ) + { + ShowError("script:implode: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + name = reference_getname(data); + + if( not_array_variable(*name) ) + { + ShowError("script:implode: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( !is_string_variable(name) ) + { + ShowError("script:implode: not string array\n"); + script_reportdata(data); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + //count chars + array_size = getarraysize(st, id, reference_getindex(data), is_string_variable(name), reference_getref(data)) - 1; + + if(array_size == -1) //empty array check (AmsTaff) + { + ShowWarning("script:implode: array length = 0\n"); + output = (char*)aMalloc(sizeof(char)*5); + sprintf(output,"%s","NULL"); + } else { + for(i = 0; i <= array_size; ++i) { + temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); + len += strlen(temp); + script_removetop(st, -1, 0); + } + + //allocate mem + if( script_hasdata(st,3) ) { + glue = script_getstr(st,3); + glue_len = strlen(glue); + len += glue_len * (array_size); + } + output = (char*)aMalloc(len + 1); + + //build output + for(i = 0; i < array_size; ++i) { + temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); + len = strlen(temp); + memcpy(&output[k], temp, len); + k += len; + if(glue_len != 0) { + memcpy(&output[k], glue, glue_len); + k += glue_len; + } + script_removetop(st, -1, 0); + } + temp = (char*) get_val2(st, reference_uid(id, array_size), reference_getref(data)); + len = strlen(temp); + memcpy(&output[k], temp, len); + k += len; + script_removetop(st, -1, 0); + + output[k] = '\0'; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// sprintf(<format>, ...); +// Implements C sprintf, except format %n. The resulting string is +// returned, instead of being saved in variable by reference. +//------------------------------------------------------- +BUILDIN_FUNC(sprintf) +{ + unsigned int len, argc = 0, arg = 0, buf2_len = 0; + const char* format; + char* p; + char* q; + char* buf = NULL; + char* buf2 = NULL; + struct script_data* data; + StringBuf final_buf; + + // Fetch init data + format = script_getstr(st, 2); + argc = script_lastdata(st)-2; + len = strlen(format); + + // Skip parsing, where no parsing is required. + if(len==0){ + script_pushconststr(st,""); + return 0; + } + + // Pessimistic alloc + CREATE(buf, char, len+1); + + // Need not be parsed, just solve stuff like %%. + if(argc==0){ + memcpy(buf,format,len+1); + script_pushstrcopy(st, buf); + aFree(buf); + return 0; + } + + safestrncpy(buf, format, len+1); + + // Issue sprintf for each parameter + StringBuf_Init(&final_buf); + q = buf; + while((p = strchr(q, '%'))!=NULL){ + if(p!=q){ + len = p-q+1; + if(buf2_len<len){ + RECREATE(buf2, char, len); + buf2_len = len; + } + safestrncpy(buf2, q, len); + StringBuf_AppendStr(&final_buf, buf2); + q = p; + } + p = q+1; + if(*p=='%'){ // %% + StringBuf_AppendStr(&final_buf, "%"); + q+=2; + continue; + } + if(*p=='n'){ // %n + ShowWarning("buildin_sprintf: Format %%n not supported! Skipping...\n"); + script_reportsrc(st); + q+=2; + continue; + } + if(arg>=argc){ + ShowError("buildin_sprintf: Not enough arguments passed!\n"); + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + script_pushconststr(st,""); + return 1; + } + if((p = strchr(q+1, '%'))==NULL){ + p = strchr(q, 0); // EOS + } + len = p-q+1; + if(buf2_len<len){ + RECREATE(buf2, char, len); + buf2_len = len; + } + safestrncpy(buf2, q, len); + q = p; + + // Note: This assumes the passed value being the correct + // type to the current format specifier. If not, the server + // probably crashes or returns anything else, than expected, + // but it would behave in normal code the same way so it's + // the scripter's responsibility. + data = script_getdata(st, arg+3); + if(data_isstring(data)){ // String + StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); + }else if(data_isint(data)){ // Number + StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); + }else if(data_isreference(data)){ // Variable + char* name = reference_getname(data); + if(name[strlen(name)-1]=='$'){ // var Str + StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); + }else{ // var Int + StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); + } + }else{ // Unsupported type + ShowError("buildin_sprintf: Unknown argument type!\n"); + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + script_pushconststr(st,""); + return 1; + } + arg++; + } + + // Append anything left + if(*q){ + StringBuf_AppendStr(&final_buf, q); + } + + // Passed more, than needed + if(arg<argc){ + ShowWarning("buildin_sprintf: Unused arguments passed.\n"); + script_reportsrc(st); + } + + script_pushstrcopy(st, StringBuf_Value(&final_buf)); + + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + + return 0; +} + +//======================================================= +// sscanf(<str>, <format>, ...); +// Implements C sscanf. +//------------------------------------------------------- +BUILDIN_FUNC(sscanf){ + unsigned int argc, arg = 0, len; + struct script_data* data; + struct map_session_data* sd = NULL; + const char* str; + const char* format; + const char* p; + const char* q; + char* buf = NULL; + char* buf_p; + char* ref_str = NULL; + int ref_int; + + // Get data + str = script_getstr(st, 2); + format = script_getstr(st, 3); + argc = script_lastdata(st)-3; + + len = strlen(format); + CREATE(buf, char, len*2+1); + + // Issue sscanf for each parameter + *buf = 0; + q = format; + while((p = strchr(q, '%'))){ + if(p!=q){ + strncat(buf, q, (size_t)(p-q)); + q = p; + } + p = q+1; + if(*p=='*' || *p=='%'){ // Skip + strncat(buf, q, 2); + q+=2; + continue; + } + if(arg>=argc){ + ShowError("buildin_sscanf: Not enough arguments passed!\n"); + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 1; + } + if((p = strchr(q+1, '%'))==NULL){ + p = strchr(q, 0); // EOS + } + len = p-q; + strncat(buf, q, len); + q = p; + + // Validate output + data = script_getdata(st, arg+4); + if(!data_isreference(data) || !reference_tovariable(data)){ + ShowError("buildin_sscanf: Target argument is not a variable!\n"); + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 1; + } + buf_p = reference_getname(data); + if(not_server_variable(*buf_p) && (sd = script_rid2sd(st))==NULL){ + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 0; + } + + // Save value if any + if(buf_p[strlen(buf_p)-1]=='$'){ // String + if(ref_str==NULL){ + CREATE(ref_str, char, strlen(str)+1); + } + if(sscanf(str, buf, ref_str)==0){ + break; + } + set_reg(st, sd, add_str(buf_p), buf_p, (void *)(ref_str), reference_getref(data)); + }else{ // Number + if(sscanf(str, buf, &ref_int)==0){ + break; + } + set_reg(st, sd, add_str(buf_p), buf_p, (void *)__64BPRTSIZE(ref_int), reference_getref(data)); + } + arg++; + + // Disable used format (%... -> %*...) + buf_p = strchr(buf, 0); + memmove(buf_p-len+2, buf_p-len+1, len); + *(buf_p-len+1) = '*'; + } + + script_pushint(st, arg); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + + return 0; +} + +//======================================================= +// strpos(<haystack>, <needle>) +// strpos(<haystack>, <needle>, <offset>) +// +// Implements PHP style strpos. Adapted from code from +// http://www.daniweb.com/code/snippet313.html, Dave Sinkula +//------------------------------------------------------- +BUILDIN_FUNC(strpos) { + const char *haystack = script_getstr(st,2); + const char *needle = script_getstr(st,3); + int i; + size_t len; + + if( script_hasdata(st,4) ) + i = script_getnum(st,4); + else + i = 0; + + if (needle[0] == '\0') { + script_pushint(st, -1); + return 0; + } + + len = strlen(haystack); + for ( ; i < len; ++i ) { + if ( haystack[i] == *needle ) { + // matched starting char -- loop through remaining chars + const char *h, *n; + for ( h = &haystack[i], n = needle; *h && *n; ++h, ++n ) { + if ( *h != *n ) { + break; + } + } + if ( !*n ) { // matched all of 'needle' to null termination + script_pushint(st, i); + return 0; + } + } + } + script_pushint(st, -1); + return 0; +} + +//=============================================================== +// replacestr <input>, <search>, <replace>{, <usecase>{, <count>}} +// +// Note: Finds all instances of <search> in <input> and replaces +// with <replace>. If specified will only replace as many +// instances as specified in <count>. By default will be case +// sensitive. +//--------------------------------------------------------------- +BUILDIN_FUNC(replacestr) +{ + const char *input = script_getstr(st, 2); + const char *find = script_getstr(st, 3); + const char *replace = script_getstr(st, 4); + size_t inputlen = strlen(input); + size_t findlen = strlen(find); + struct StringBuf output; + bool usecase = true; + + int count = 0; + int numFinds = 0; + int i = 0, f = 0; + + if(findlen == 0) { + ShowError("script:replacestr: Invalid search length.\n"); + st->state = END; + return 1; + } + + if(script_hasdata(st, 5)) { + if( !script_isstring(st,5) ) + usecase = script_getnum(st, 5) != 0; + else { + ShowError("script:replacestr: Invalid usecase value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + if(script_hasdata(st, 6)) { + count = script_getnum(st, 6); + if(count == 0) { + ShowError("script:replacestr: Invalid count value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + StringBuf_Init(&output); + + for(; i < inputlen; i++) { + if(count && count == numFinds) { //found enough, stop looking + break; + } + + for(f = 0; f <= findlen; f++) { + if(f == findlen) { //complete match + numFinds++; + StringBuf_AppendStr(&output, replace); + + i += findlen - 1; + break; + } else { + if(usecase) { + if((i + f) > inputlen || input[i + f] != find[f]) { + StringBuf_Printf(&output, "%c", input[i]); + break; + } + } else { + if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { + StringBuf_Printf(&output, "%c", input[i]); + break; + } + } + } + } + } + + //append excess after enough found + if(i < inputlen) + StringBuf_AppendStr(&output, &(input[i])); + + script_pushstrcopy(st, StringBuf_Value(&output)); + StringBuf_Destroy(&output); + return 0; +} + +//======================================================== +// countstr <input>, <search>{, <usecase>} +// +// Note: Counts the number of times <search> occurs in +// <input>. By default will be case sensitive. +//-------------------------------------------------------- +BUILDIN_FUNC(countstr) +{ + const char *input = script_getstr(st, 2); + const char *find = script_getstr(st, 3); + size_t inputlen = strlen(input); + size_t findlen = strlen(find); + bool usecase = true; + + int numFinds = 0; + int i = 0, f = 0; + + if(findlen == 0) { + ShowError("script:countstr: Invalid search length.\n"); + st->state = END; + return 1; + } + + if(script_hasdata(st, 4)) { + if( !script_isstring(st,4) ) + usecase = script_getnum(st, 4) != 0; + else { + ShowError("script:countstr: Invalid usecase value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + for(; i < inputlen; i++) { + for(f = 0; f <= findlen; f++) { + if(f == findlen) { //complete match + numFinds++; + i += findlen - 1; + break; + } else { + if(usecase) { + if((i + f) > inputlen || input[i + f] != find[f]) { + break; + } + } else { + if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { + break; + } + } + } + } + } + script_pushint(st, numFinds); + return 0; +} + + +/// Changes the display name and/or display class of the npc. +/// Returns 0 is successful, 1 if the npc does not exist. +/// +/// setnpcdisplay("<npc name>", "<new display name>", <new class id>, <new size>) -> <int> +/// setnpcdisplay("<npc name>", "<new display name>", <new class id>) -> <int> +/// setnpcdisplay("<npc name>", "<new display name>") -> <int> +/// setnpcdisplay("<npc name>", <new class id>) -> <int> +BUILDIN_FUNC(setnpcdisplay) +{ + const char* name; + const char* newname = NULL; + int class_ = -1, size = -1; + struct script_data* data; + struct npc_data* nd; + + name = script_getstr(st,2); + data = script_getdata(st,3); + + if( script_hasdata(st,4) ) + class_ = script_getnum(st,4); + if( script_hasdata(st,5) ) + size = script_getnum(st,5); + + get_val(st, data); + if( data_isstring(data) ) + newname = conv_str(st,data); + else if( data_isint(data) ) + class_ = conv_num(st,data); + else + { + ShowError("script:setnpcdisplay: expected a string or number\n"); + script_reportdata(data); + return 1; + } + + nd = npc_name2id(name); + if( nd == NULL ) + {// not found + script_pushint(st,1); + return 0; + } + + // update npc + if( newname ) + npc_setdisplayname(nd, newname); + + if( size != -1 && size != (int)nd->size ) + nd->size = size; + else + size = -1; + + if( class_ != -1 && nd->class_ != class_ ) + npc_setclass(nd, class_); + else if( size != -1 ) + { // Required to update the visual size + clif_clearunit_area(&nd->bl, CLR_OUTSIGHT); + clif_spawn(&nd->bl); + } + + script_pushint(st,0); + return 0; +} + +BUILDIN_FUNC(atoi) +{ + const char *value; + value = script_getstr(st,2); + script_pushint(st,atoi(value)); + return 0; +} + +// case-insensitive substring search [lordalfa] +BUILDIN_FUNC(compare) +{ + const char *message; + const char *cmpstring; + message = script_getstr(st,2); + cmpstring = script_getstr(st,3); + script_pushint(st,(stristr(message,cmpstring) != NULL)); + return 0; +} + +// [zBuffer] List of mathematics commands ---> +BUILDIN_FUNC(sqrt) +{ + double i, a; + i = script_getnum(st,2); + a = sqrt(i); + script_pushint(st,(int)a); + return 0; +} + +BUILDIN_FUNC(pow) +{ + double i, a, b; + a = script_getnum(st,2); + b = script_getnum(st,3); + i = pow(a,b); + script_pushint(st,(int)i); + return 0; +} + +BUILDIN_FUNC(distance) +{ + int x0, y0, x1, y1; + + x0 = script_getnum(st,2); + y0 = script_getnum(st,3); + x1 = script_getnum(st,4); + y1 = script_getnum(st,5); + + script_pushint(st,distance_xy(x0,y0,x1,y1)); + return 0; +} + +// <--- [zBuffer] List of mathematics commands + +BUILDIN_FUNC(md5) +{ + const char *tmpstr; + char *md5str; + + tmpstr = script_getstr(st,2); + md5str = (char *)aMalloc((32+1)*sizeof(char)); + MD5_String(tmpstr, md5str); + script_pushstr(st, md5str); + return 0; +} + +// [zBuffer] List of dynamic var commands ---> + +BUILDIN_FUNC(setd) +{ + TBL_PC *sd=NULL; + char varname[100]; + const char *buffer; + int elem; + buffer = script_getstr(st, 2); + + if(sscanf(buffer, "%99[^[][%d]", varname, &elem) < 2) + elem = 0; + + if( not_server_variable(*varname) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + { + ShowError("script:setd: no player attached for player variable '%s'\n", buffer); + return 0; + } + } + + if( is_string_variable(varname) ) { + setd_sub(st, sd, varname, elem, (void *)script_getstr(st, 3), NULL); + } else { + setd_sub(st, sd, varname, elem, (void *)__64BPRTSIZE(script_getnum(st, 3)), NULL); + } + + return 0; +} + +int buildin_query_sql_sub(struct script_state* st, Sql* handle) +{ + int i, j; + TBL_PC* sd = NULL; + const char* query; + struct script_data* data; + const char* name; + int max_rows = SCRIPT_MAX_ARRAYSIZE; // maximum number of rows + int num_vars; + int num_cols; + + // check target variables + for( i = 3; script_hasdata(st,i); ++i ) { + data = script_getdata(st, i); + if( data_isreference(data) ) { // it's a variable + name = reference_getname(data); + if( not_server_variable(*name) && sd == NULL ) { // requires a player + sd = script_rid2sd(st); + if( sd == NULL ) { // no player attached + script_reportdata(data); + st->state = END; + return 1; + } + } + if( not_array_variable(*name) ) + max_rows = 1;// not an array, limit to one row + } else { + ShowError("script:query_sql: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1; + } + } + num_vars = i - 3; + + // Execute the query + query = script_getstr(st,2); + + if( SQL_ERROR == Sql_QueryStr(handle, query) ) { + Sql_ShowDebug(handle); + script_pushint(st, 0); + return 1; + } + + if( Sql_NumRows(handle) == 0 ) { // No data received + Sql_FreeResult(handle); + script_pushint(st, 0); + return 0; + } + + // Count the number of columns to store + num_cols = Sql_NumColumns(handle); + if( num_vars < num_cols ) { + ShowWarning("script:query_sql: Too many columns, discarding last %u columns.\n", (unsigned int)(num_cols-num_vars)); + script_reportsrc(st); + } else if( num_vars > num_cols ) { + ShowWarning("script:query_sql: Too many variables (%u extra).\n", (unsigned int)(num_vars-num_cols)); + script_reportsrc(st); + } + + // Store data + for( i = 0; i < max_rows && SQL_SUCCESS == Sql_NextRow(handle); ++i ) { + for( j = 0; j < num_vars; ++j ) { + char* str = NULL; + + if( j < num_cols ) + Sql_GetData(handle, j, &str, NULL); + + data = script_getdata(st, j+3); + name = reference_getname(data); + if( is_string_variable(name) ) + setd_sub(st, sd, name, i, (void *)(str?str:""), reference_getref(data)); + else + setd_sub(st, sd, name, i, (void *)__64BPRTSIZE((str?atoi(str):0)), reference_getref(data)); + } + } + if( i == max_rows && max_rows < Sql_NumRows(handle) ) { + ShowWarning("script:query_sql: Only %d/%u rows have been stored.\n", max_rows, (unsigned int)Sql_NumRows(handle)); + script_reportsrc(st); + } + + // Free data + Sql_FreeResult(handle); + script_pushint(st, i); + + return 0; +} + +BUILDIN_FUNC(query_sql) { +#ifdef BETA_THREAD_TEST + if( st->state != RERUNLINE ) { + queryThread_add(st,false); + + st->state = RERUNLINE;/* will continue when the query is finished running. */ + } else + st->state = RUN; + + return 0; +#else + return buildin_query_sql_sub(st, mmysql_handle); +#endif +} + +BUILDIN_FUNC(query_logsql) { + if( !log_config.sql_logs ) {// logmysql_handle == NULL + ShowWarning("buildin_query_logsql: SQL logs are disabled, query '%s' will not be executed.\n", script_getstr(st,2)); + script_pushint(st,-1); + return 1; + } +#ifdef BETA_THREAD_TEST + if( st->state != RERUNLINE ) { + queryThread_add(st,true); + + st->state = RERUNLINE;/* will continue when the query is finished running. */ + } else + st->state = RUN; + + return 0; +#else + return buildin_query_sql_sub(st, logmysql_handle); +#endif +} + +//Allows escaping of a given string. +BUILDIN_FUNC(escape_sql) +{ + const char *str; + char *esc_str; + size_t len; + + str = script_getstr(st,2); + len = strlen(str); + esc_str = (char*)aMalloc(len*2+1); + Sql_EscapeStringLen(mmysql_handle, esc_str, str, len); + script_pushstr(st, esc_str); + return 0; +} + +BUILDIN_FUNC(getd) +{ + char varname[100]; + const char *buffer; + int elem; + + buffer = script_getstr(st, 2); + + if(sscanf(buffer, "%[^[][%d]", varname, &elem) < 2) + elem = 0; + + // Push the 'pointer' so it's more flexible [Lance] + push_val(st->stack, C_NAME, reference_uid(add_str(varname), elem)); + + return 0; +} + +// <--- [zBuffer] List of dynamic var commands +// Pet stat [Lance] +BUILDIN_FUNC(petstat) +{ + TBL_PC *sd = NULL; + struct pet_data *pd; + int flag = script_getnum(st,2); + sd = script_rid2sd(st); + if(!sd || !sd->status.pet_id || !sd->pd){ + if(flag == 2) + script_pushconststr(st, ""); + else + script_pushint(st,0); + return 0; + } + pd = sd->pd; + switch(flag){ + case 1: script_pushint(st,(int)pd->pet.class_); break; + case 2: script_pushstrcopy(st, pd->pet.name); break; + case 3: script_pushint(st,(int)pd->pet.level); break; + case 4: script_pushint(st,(int)pd->pet.hungry); break; + case 5: script_pushint(st,(int)pd->pet.intimate); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +BUILDIN_FUNC(callshop) +{ + TBL_PC *sd = NULL; + struct npc_data *nd; + const char *shopname; + int flag = 0; + sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + shopname = script_getstr(st, 2); + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + nd = npc_name2id(shopname); + if( !nd || nd->bl.type != BL_NPC || (nd->subtype != SHOP && nd->subtype != CASHSHOP) ) + { + ShowError("buildin_callshop: Shop [%s] not found (or NPC is not shop type)\n", shopname); + script_pushint(st,0); + return 1; + } + + if( nd->subtype == SHOP ) + { + // flag the user as using a valid script call for opening the shop (for floating NPCs) + sd->state.callshop = 1; + + switch( flag ) + { + case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window + case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window + default: clif_npcbuysell(sd,nd->bl.id); break; //Show menu + } + } + else + clif_cashshop_show(sd, nd); + + sd->npc_shopid = nd->bl.id; + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopitem) +{ + const char* npcname = script_getstr(st, 2); + struct npc_data* nd = npc_name2id(npcname); + int n, i; + int amount; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + // get the count of new entries + amount = (script_lastdata(st)-2)/2; + + // generate new shop item list + RECREATE(nd->u.shop.shop_item, struct npc_item_list, amount); + for( n = 0, i = 3; n < amount; n++, i+=2 ) + { + nd->u.shop.shop_item[n].nameid = script_getnum(st,i); + nd->u.shop.shop_item[n].value = script_getnum(st,i+1); + } + nd->u.shop.count = n; + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopadditem) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + int n, i; + int amount; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + // get the count of new entries + amount = (script_lastdata(st)-2)/2; + + // append new items to existing shop item list + RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+amount); + for( n = nd->u.shop.count, i = 3; n < nd->u.shop.count+amount; n++, i+=2 ) + { + nd->u.shop.shop_item[n].nameid = script_getnum(st,i); + nd->u.shop.shop_item[n].value = script_getnum(st,i+1); + } + nd->u.shop.count = n; + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopdelitem) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + unsigned int nameid; + int n, i; + int amount; + int size; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + amount = script_lastdata(st)-2; + size = nd->u.shop.count; + + // remove specified items from the shop item list + for( i = 3; i < 3 + amount; i++ ) + { + nameid = script_getnum(st,i); + + ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid ); + if( n < size ) + { + memmove(&nd->u.shop.shop_item[n], &nd->u.shop.shop_item[n+1], sizeof(nd->u.shop.shop_item[0])*(size-n)); + size--; + } + } + + RECREATE(nd->u.shop.shop_item, struct npc_item_list, size); + nd->u.shop.count = size; + + script_pushint(st,1); + return 0; +} + +//Sets a script to attach to a shop npc. +BUILDIN_FUNC(npcshopattach) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + int flag = 1; + + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + + if( !nd || nd->subtype != SHOP ) + { //Not found. + script_pushint(st,0); + return 0; + } + + if (flag) + nd->master_nd = ((struct npc_data *)map_id2bl(st->oid)); + else + nd->master_nd = NULL; + + script_pushint(st,1); + return 0; +} + +/*========================================== + * Returns some values of an item [Lupus] + * Price, Weight, etc... + setitemscript(itemID,"{new item bonus script}",[n]); + Where n: + 0 - script + 1 - Equip script + 2 - Unequip script + *------------------------------------------*/ +BUILDIN_FUNC(setitemscript) +{ + int item_id,n=0; + const char *script; + struct item_data *i_data; + struct script_code **dstscript; + + item_id = script_getnum(st,2); + script = script_getstr(st,3); + if( script_hasdata(st,4) ) + n=script_getnum(st,4); + i_data = itemdb_exists(item_id); + + if (!i_data || script==NULL || ( script[0] && script[0]!='{' )) { + script_pushint(st,0); + return 0; + } + switch (n) { + case 2: + dstscript = &i_data->unequip_script; + break; + case 1: + dstscript = &i_data->equip_script; + break; + default: + dstscript = &i_data->script; + break; + } + if(*dstscript) + script_free_code(*dstscript); + + *dstscript = script[0] ? parse_script(script, "script_setitemscript", 0, 0) : NULL; + script_pushint(st,1); + return 0; +} + +/* Work In Progress [Lupus] +BUILDIN_FUNC(addmonsterdrop) +{ + int class_,item_id,chance; + class_=script_getnum(st,2); + item_id=script_getnum(st,3); + chance=script_getnum(st,4); + if(class_>1000 && item_id>500 && chance>0) { + script_pushint(st,1); + } else { + script_pushint(st,0); + } +} + +BUILDIN_FUNC(delmonsterdrop) +{ + int class_,item_id; + class_=script_getnum(st,2); + item_id=script_getnum(st,3); + if(class_>1000 && item_id>500) { + script_pushint(st,1); + } else { + script_pushint(st,0); + } +} +*/ + +/*========================================== + * Returns some values of a monster [Lupus] + * Name, Level, race, size, etc... + getmonsterinfo(monsterID,queryIndex); + *------------------------------------------*/ +BUILDIN_FUNC(getmonsterinfo) +{ + struct mob_db *mob; + int mob_id; + + mob_id = script_getnum(st,2); + if (!mobdb_checkid(mob_id)) { + ShowError("buildin_getmonsterinfo: Wrong Monster ID: %i\n", mob_id); + if ( !script_getnum(st,3) ) //requested a string + script_pushconststr(st,"null"); + else + script_pushint(st,-1); + return -1; + } + mob = mob_db(mob_id); + switch ( script_getnum(st,3) ) { + case 0: script_pushstrcopy(st,mob->jname); break; + case 1: script_pushint(st,mob->lv); break; + case 2: script_pushint(st,mob->status.max_hp); break; + case 3: script_pushint(st,mob->base_exp); break; + case 4: script_pushint(st,mob->job_exp); break; + case 5: script_pushint(st,mob->status.rhw.atk); break; + case 6: script_pushint(st,mob->status.rhw.atk2); break; + case 7: script_pushint(st,mob->status.def); break; + case 8: script_pushint(st,mob->status.mdef); break; + case 9: script_pushint(st,mob->status.str); break; + case 10: script_pushint(st,mob->status.agi); break; + case 11: script_pushint(st,mob->status.vit); break; + case 12: script_pushint(st,mob->status.int_); break; + case 13: script_pushint(st,mob->status.dex); break; + case 14: script_pushint(st,mob->status.luk); break; + case 15: script_pushint(st,mob->status.rhw.range); break; + case 16: script_pushint(st,mob->range2); break; + case 17: script_pushint(st,mob->range3); break; + case 18: script_pushint(st,mob->status.size); break; + case 19: script_pushint(st,mob->status.race); break; + case 20: script_pushint(st,mob->status.def_ele); break; + case 21: script_pushint(st,mob->status.mode); break; + case 22: script_pushint(st,mob->mexp); break; + default: script_pushint(st,-1); //wrong Index + } + return 0; +} + +BUILDIN_FUNC(checkvending) // check vending [Nab4] +{ + TBL_PC *sd = NULL; + + if(script_hasdata(st,2)) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if(sd) + script_pushint(st, sd->state.autotrade ? 2 : sd->state.vending); + else + script_pushint(st,0); + + return 0; +} + + +BUILDIN_FUNC(checkchatting) // check chatting [Marka] +{ + TBL_PC *sd = NULL; + + if(script_hasdata(st,2)) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if(sd) + script_pushint(st,(sd->chatID != 0)); + else + script_pushint(st,0); + + return 0; +} + +BUILDIN_FUNC(searchitem) +{ + struct script_data* data = script_getdata(st, 2); + const char *itemname = script_getstr(st,3); + struct item_data *items[MAX_SEARCH]; + int count; + + char* name; + int32 start; + int32 id; + int32 i; + TBL_PC* sd = NULL; + + if ((items[0] = itemdb_exists(atoi(itemname)))) + count = 1; + else { + count = itemdb_searchname_array(items, ARRAYLENGTH(items), itemname); + if (count > MAX_SEARCH) count = MAX_SEARCH; + } + + if (!count) { + script_pushint(st, 0); + return 0; + } + + if( !data_isreference(data) ) + { + ShowError("script:searchitem: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:searchitem: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + if( is_string_variable(name) ) + {// string array + ShowError("script:searchitem: not an integer array reference\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + for( i = 0; i < count; ++start, ++i ) + {// Set array + void* v = (void*)__64BPRTSIZE((int)items[i]->nameid); + set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); + } + + script_pushint(st, count); + return 0; +} + +int axtoi(const char *hexStg) +{ + int n = 0; // position in string + int16 m = 0; // position in digit[] to shift + int count; // loop index + int intValue = 0; // integer value of hex string + int digit[11]; // hold values to convert + while (n < 10) { + if (hexStg[n]=='\0') + break; + if (hexStg[n] > 0x29 && hexStg[n] < 0x40 ) //if 0 to 9 + digit[n] = hexStg[n] & 0x0f; //convert to int + else if (hexStg[n] >='a' && hexStg[n] <= 'f') //if a to f + digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int + else if (hexStg[n] >='A' && hexStg[n] <= 'F') //if A to F + digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int + else break; + n++; + } + count = n; + m = n - 1; + n = 0; + while(n < count) { + // digit[n] is value of hex digit at position n + // (m << 2) is the number of positions to shift + // OR the bits into return value + intValue = intValue | (digit[n] << (m << 2)); + m--; // adjust the position to set + n++; // next digit to process + } + return (intValue); +} + +// [Lance] Hex string to integer converter +BUILDIN_FUNC(axtoi) +{ + const char *hex = script_getstr(st,2); + script_pushint(st,axtoi(hex)); + return 0; +} + +// [zBuffer] List of player cont commands ---> +BUILDIN_FUNC(rid2name) +{ + struct block_list *bl = NULL; + int rid = script_getnum(st,2); + if((bl = map_id2bl(rid))) + { + switch(bl->type) { + case BL_MOB: script_pushstrcopy(st,((TBL_MOB*)bl)->name); break; + case BL_PC: script_pushstrcopy(st,((TBL_PC*)bl)->status.name); break; + case BL_NPC: script_pushstrcopy(st,((TBL_NPC*)bl)->exname); break; + case BL_PET: script_pushstrcopy(st,((TBL_PET*)bl)->pet.name); break; + case BL_HOM: script_pushstrcopy(st,((TBL_HOM*)bl)->homunculus.name); break; + case BL_MER: script_pushstrcopy(st,((TBL_MER*)bl)->db->name); break; + default: + ShowError("buildin_rid2name: BL type unknown.\n"); + script_pushconststr(st,""); + break; + } + } else { + ShowError("buildin_rid2name: invalid RID\n"); + script_pushconststr(st,"(null)"); + } + return 0; +} + +BUILDIN_FUNC(pcblockmove) +{ + int id, flag; + TBL_PC *sd = NULL; + + id = script_getnum(st,2); + flag = script_getnum(st,3); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + sd->state.blockedmove = flag > 0; + + return 0; +} + +BUILDIN_FUNC(pcfollow) +{ + int id, targetid; + TBL_PC *sd = NULL; + + + id = script_getnum(st,2); + targetid = script_getnum(st,3); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + pc_follow(sd, targetid); + + return 0; +} + +BUILDIN_FUNC(pcstopfollow) +{ + int id; + TBL_PC *sd = NULL; + + + id = script_getnum(st,2); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + pc_stop_following(sd); + + return 0; +} +// <--- [zBuffer] List of player cont commands +// [zBuffer] List of mob control commands ---> +//## TODO always return if the request/whatever was successfull [FlavioJS] + +/// Makes the unit walk to target position or map +/// Returns if it was successfull +/// +/// unitwalk(<unit_id>,<x>,<y>) -> <bool> +/// unitwalk(<unit_id>,<map_id>) -> <bool> +BUILDIN_FUNC(unitwalk) +{ + struct block_list* bl; + + bl = map_id2bl(script_getnum(st,2)); + if( bl == NULL ) + { + script_pushint(st, 0); + } + else if( script_hasdata(st,4) ) + { + int x = script_getnum(st,3); + int y = script_getnum(st,4); + script_pushint(st, unit_walktoxy(bl,x,y,0));// We'll use harder calculations. + } + else + { + int map_id = script_getnum(st,3); + script_pushint(st, unit_walktobl(bl,map_id2bl(map_id),65025,1)); + } + + return 0; +} + +/// Kills the unit +/// +/// unitkill <unit_id>; +BUILDIN_FUNC(unitkill) +{ + struct block_list* bl = map_id2bl(script_getnum(st,2)); + if( bl != NULL ) + status_kill(bl); + + return 0; +} + +/// Warps the unit to the target position in the target map +/// Returns if it was successfull +/// +/// unitwarp(<unit_id>,"<map name>",<x>,<y>) -> <bool> +BUILDIN_FUNC(unitwarp) +{ + int unit_id; + int map; + short x; + short y; + struct block_list* bl; + const char *mapname; + + unit_id = script_getnum(st,2); + mapname = script_getstr(st, 3); + x = (short)script_getnum(st,4); + y = (short)script_getnum(st,5); + + if (!unit_id) //Warp the script's runner + bl = map_id2bl(st->rid); + else + bl = map_id2bl(unit_id); + + if( strcmp(mapname,"this") == 0 ) + map = bl?bl->m:-1; + else + map = map_mapname2mapid(mapname); + + if( map >= 0 && bl != NULL ) + script_pushint(st, unit_warp(bl,map,x,y,CLR_OUTSIGHT)); + else + script_pushint(st, 0); + + return 0; +} + +/// Makes the unit attack the target. +/// If the unit is a player and <action type> is not 0, it does a continuous +/// attack instead of a single attack. +/// Returns if the request was successfull. +/// +/// unitattack(<unit_id>,"<target name>"{,<action type>}) -> <bool> +/// unitattack(<unit_id>,<target_id>{,<action type>}) -> <bool> +BUILDIN_FUNC(unitattack) +{ + struct block_list* unit_bl; + struct block_list* target_bl = NULL; + struct script_data* data; + int actiontype = 0; + + // get unit + unit_bl = map_id2bl(script_getnum(st,2)); + if( unit_bl == NULL ) { + script_pushint(st, 0); + return 0; + } + + data = script_getdata(st, 3); + get_val(st, data); + if( data_isstring(data) ) + { + TBL_PC* sd = map_nick2sd(conv_str(st, data)); + if( sd != NULL ) + target_bl = &sd->bl; + } else + target_bl = map_id2bl(conv_num(st, data)); + // request the attack + if( target_bl == NULL ) + { + script_pushint(st, 0); + return 0; + } + + // get actiontype + if( script_hasdata(st,4) ) + actiontype = script_getnum(st,4); + + switch( unit_bl->type ) + { + case BL_PC: + clif_parse_ActionRequest_sub(((TBL_PC *)unit_bl), actiontype > 0 ? 0x07 : 0x00, target_bl->id, gettick()); + script_pushint(st, 1); + return 0; + case BL_MOB: + ((TBL_MOB *)unit_bl)->target_id = target_bl->id; + break; + case BL_PET: + ((TBL_PET *)unit_bl)->target_id = target_bl->id; + break; + default: + ShowError("script:unitattack: unsupported source unit type %d\n", unit_bl->type); + script_pushint(st, 0); + return 1; + } + script_pushint(st, unit_walktobl(unit_bl, target_bl, 65025, 2)); + return 0; +} + +/// Makes the unit stop attacking and moving +/// +/// unitstop <unit_id>; +BUILDIN_FUNC(unitstop) +{ + int unit_id; + struct block_list* bl; + + unit_id = script_getnum(st,2); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + { + unit_stop_attack(bl); + unit_stop_walking(bl,4); + if( bl->type == BL_MOB ) + ((TBL_MOB*)bl)->target_id = 0; + } + + return 0; +} + +/// Makes the unit say the message +/// +/// unittalk <unit_id>,"<message>"; +BUILDIN_FUNC(unittalk) +{ + int unit_id; + const char* message; + struct block_list* bl; + + unit_id = script_getnum(st,2); + message = script_getstr(st, 3); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + { + struct StringBuf sbuf; + StringBuf_Init(&sbuf); + StringBuf_Printf(&sbuf, "%s : %s", status_get_name(bl), message); + clif_message(bl, StringBuf_Value(&sbuf)); + if( bl->type == BL_PC ) + clif_displaymessage(((TBL_PC*)bl)->fd, StringBuf_Value(&sbuf)); + StringBuf_Destroy(&sbuf); + } + + return 0; +} + +/// Makes the unit do an emotion +/// +/// unitemote <unit_id>,<emotion>; +/// +/// @see e_* in const.txt +BUILDIN_FUNC(unitemote) +{ + int unit_id; + int emotion; + struct block_list* bl; + + unit_id = script_getnum(st,2); + emotion = script_getnum(st,3); + bl = map_id2bl(unit_id); + if( bl != NULL ) + clif_emotion(bl, emotion); + + return 0; +} + +/// Makes the unit cast the skill on the target or self if no target is specified +/// +/// unitskilluseid <unit_id>,<skill_id>,<skill_lv>{,<target_id>}; +/// unitskilluseid <unit_id>,"<skill name>",<skill_lv>{,<target_id>}; +BUILDIN_FUNC(unitskilluseid) +{ + int unit_id; + uint16 skill_id; + uint16 skill_lv; + int target_id; + struct block_list* bl; + + unit_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + skill_lv = script_getnum(st,4); + target_id = ( script_hasdata(st,5) ? script_getnum(st,5) : unit_id ); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + unit_skilluse_id(bl, target_id, skill_id, skill_lv); + + return 0; +} + +/// Makes the unit cast the skill on the target position. +/// +/// unitskillusepos <unit_id>,<skill_id>,<skill_lv>,<target_x>,<target_y>; +/// unitskillusepos <unit_id>,"<skill name>",<skill_lv>,<target_x>,<target_y>; +BUILDIN_FUNC(unitskillusepos) +{ + int unit_id; + uint16 skill_id; + uint16 skill_lv; + int skill_x; + int skill_y; + struct block_list* bl; + + unit_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + skill_lv = script_getnum(st,4); + skill_x = script_getnum(st,5); + skill_y = script_getnum(st,6); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + unit_skilluse_pos(bl, skill_x, skill_y, skill_id, skill_lv); + + return 0; +} + +// <--- [zBuffer] List of mob control commands + +/// Pauses the execution of the script, detaching the player +/// +/// sleep <mili seconds>; +BUILDIN_FUNC(sleep) +{ + int ticks; + + ticks = script_getnum(st,2); + + // detach the player + script_detach_rid(st); + + if( ticks <= 0 ) + {// do nothing + } + else if( st->sleep.tick == 0 ) + {// sleep for the target amount of time + st->state = RERUNLINE; + st->sleep.tick = ticks; + } + else + {// sleep time is over + st->state = RUN; + st->sleep.tick = 0; + } + return 0; +} + +/// Pauses the execution of the script, keeping the player attached +/// Returns if a player is still attached +/// +/// sleep2(<mili secconds>) -> <bool> +BUILDIN_FUNC(sleep2) +{ + int ticks; + + ticks = script_getnum(st,2); + + if( ticks <= 0 ) + {// do nothing + script_pushint(st, (map_id2sd(st->rid)!=NULL)); + } + else if( !st->sleep.tick ) + {// sleep for the target amount of time + st->state = RERUNLINE; + st->sleep.tick = ticks; + } + else + {// sleep time is over + st->state = RUN; + st->sleep.tick = 0; + script_pushint(st, (map_id2sd(st->rid)!=NULL)); + } + return 0; +} + +/// Awakes all the sleep timers of the target npc +/// +/// awake "<npc name>"; +BUILDIN_FUNC(awake) +{ + struct npc_data* nd; + struct linkdb_node *node = (struct linkdb_node *)sleep_db; + + nd = npc_name2id(script_getstr(st, 2)); + if( nd == NULL ) { + ShowError("awake: NPC \"%s\" not found\n", script_getstr(st, 2)); + return 1; + } + + while( node ) + { + if( (int)__64BPRTSIZE(node->key) == nd->bl.id ) + {// sleep timer for the npc + struct script_state* tst = (struct script_state*)node->data; + TBL_PC* sd = map_id2sd(tst->rid); + + if( tst->sleep.timer == INVALID_TIMER ) + {// already awake ??? + node = node->next; + continue; + } + if( (sd && sd->status.char_id != tst->sleep.charid) || (tst->rid && !sd)) + {// char not online anymore / another char of the same account is online - Cancel execution + tst->state = END; + tst->rid = 0; + } + + delete_timer(tst->sleep.timer, run_script_timer); + node = script_erase_sleepdb(node); + tst->sleep.timer = INVALID_TIMER; + if(tst->state != RERUNLINE) + tst->sleep.tick = 0; + run_script_main(tst); + } + else + { + node = node->next; + } + } + return 0; +} + +/// Returns a reference to a variable of the target NPC. +/// Returns 0 if an error occurs. +/// +/// getvariableofnpc(<variable>, "<npc name>") -> <reference> +BUILDIN_FUNC(getvariableofnpc) +{ + struct script_data* data; + const char* name; + struct npc_data* nd; + + data = script_getdata(st,2); + if( !data_isreference(data) ) + {// Not a reference (aka varaible name) + ShowError("script:getvariableofnpc: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1; + } + + name = reference_getname(data); + if( *name != '.' || name[1] == '@' ) + {// not a npc variable + ShowError("script:getvariableofnpc: invalid scope (not npc variable)\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1; + } + + nd = npc_name2id(script_getstr(st,3)); + if( nd == NULL || nd->subtype != SCRIPT || nd->u.scr.script == NULL ) + {// NPC not found or has no script + ShowError("script:getvariableofnpc: can't find npc %s\n", script_getstr(st,3)); + script_pushnil(st); + st->state = END; + return 1; + } + + push_val2(st->stack, C_NAME, reference_getuid(data), &nd->u.scr.script->script_vars ); + return 0; +} + +/// Opens a warp portal. +/// Has no "portal opening" effect/sound, it opens the portal immediately. +/// +/// warpportal <source x>,<source y>,"<target map>",<target x>,<target y>; +/// +/// @author blackhole89 +BUILDIN_FUNC(warpportal) +{ + int spx; + int spy; + unsigned short mapindex; + int tpx; + int tpy; + struct skill_unit_group* group; + struct block_list* bl; + + bl = map_id2bl(st->oid); + if( bl == NULL ) + { + ShowError("script:warpportal: npc is needed\n"); + return 1; + } + + spx = script_getnum(st,2); + spy = script_getnum(st,3); + mapindex = mapindex_name2id(script_getstr(st, 4)); + tpx = script_getnum(st,5); + tpy = script_getnum(st,6); + + if( mapindex == 0 ) + return 0;// map not found + + group = skill_unitsetting(bl, AL_WARP, 4, spx, spy, 0); + if( group == NULL ) + return 0;// failed + group->val2 = (tpx<<16) | tpy; + group->val3 = mapindex; + + return 0; +} + +BUILDIN_FUNC(openmail) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + mail_openmail(sd); + + return 0; +} + +BUILDIN_FUNC(openauction) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_Auction_openwindow(sd); + + return 0; +} + +/// Retrieves the value of the specified flag of the specified cell. +/// +/// checkcell("<map name>",<x>,<y>,<type>) -> <bool> +/// +/// @see cell_chk* constants in const.txt for the types +BUILDIN_FUNC(checkcell) +{ + int16 m = map_mapname2mapid(script_getstr(st,2)); + int16 x = script_getnum(st,3); + int16 y = script_getnum(st,4); + cell_chk type = (cell_chk)script_getnum(st,5); + + script_pushint(st, map_getcell(m, x, y, type)); + + return 0; +} + +/// Modifies flags of cells in the specified area. +/// +/// setcell "<map name>",<x1>,<y1>,<x2>,<y2>,<type>,<flag>; +/// +/// @see cell_* constants in const.txt for the types +BUILDIN_FUNC(setcell) +{ + int16 m = map_mapname2mapid(script_getstr(st,2)); + int16 x1 = script_getnum(st,3); + int16 y1 = script_getnum(st,4); + int16 x2 = script_getnum(st,5); + int16 y2 = script_getnum(st,6); + cell_t type = (cell_t)script_getnum(st,7); + bool flag = (bool)script_getnum(st,8); + + int x,y; + + if( x1 > x2 ) swap(x1,x2); + if( y1 > y2 ) swap(y1,y2); + + for( y = y1; y <= y2; ++y ) + for( x = x1; x <= x2; ++x ) + map_setcell(m, x, y, type, flag); + + return 0; +} + +/*========================================== + * Mercenary Commands + *------------------------------------------*/ +BUILDIN_FUNC(mercenary_create) +{ + struct map_session_data *sd; + int class_, contract_time; + + if( (sd = script_rid2sd(st)) == NULL || sd->md || sd->status.mer_id != 0 ) + return 0; + + class_ = script_getnum(st,2); + + if( !merc_class(class_) ) + return 0; + + contract_time = script_getnum(st,3); + merc_create(sd, class_, contract_time); + return 0; +} + +BUILDIN_FUNC(mercenary_heal) +{ + struct map_session_data *sd = script_rid2sd(st); + int hp, sp; + + if( sd == NULL || sd->md == NULL ) + return 0; + hp = script_getnum(st,2); + sp = script_getnum(st,3); + + status_heal(&sd->md->bl, hp, sp, 0); + return 0; +} + +BUILDIN_FUNC(mercenary_sc_start) +{ + struct map_session_data *sd = script_rid2sd(st); + enum sc_type type; + int tick, val1; + + if( sd == NULL || sd->md == NULL ) + return 0; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + + status_change_start(&sd->md->bl, type, 10000, val1, 0, 0, 0, tick, 2); + return 0; +} + +BUILDIN_FUNC(mercenary_get_calls) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + switch( guild ) + { + case ARCH_MERC_GUILD: + script_pushint(st,sd->status.arch_calls); + break; + case SPEAR_MERC_GUILD: + script_pushint(st,sd->status.spear_calls); + break; + case SWORD_MERC_GUILD: + script_pushint(st,sd->status.sword_calls); + break; + default: + script_pushint(st,0); + break; + } + + return 0; +} + +BUILDIN_FUNC(mercenary_set_calls) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild, value, *calls; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + value = script_getnum(st,3); + + switch( guild ) + { + case ARCH_MERC_GUILD: + calls = &sd->status.arch_calls; + break; + case SPEAR_MERC_GUILD: + calls = &sd->status.spear_calls; + break; + case SWORD_MERC_GUILD: + calls = &sd->status.sword_calls; + break; + default: + return 0; // Invalid Guild + } + + *calls += value; + *calls = cap_value(*calls, 0, INT_MAX); + + return 0; +} + +BUILDIN_FUNC(mercenary_get_faith) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + switch( guild ) + { + case ARCH_MERC_GUILD: + script_pushint(st,sd->status.arch_faith); + break; + case SPEAR_MERC_GUILD: + script_pushint(st,sd->status.spear_faith); + break; + case SWORD_MERC_GUILD: + script_pushint(st,sd->status.sword_faith); + break; + default: + script_pushint(st,0); + break; + } + + return 0; +} + +BUILDIN_FUNC(mercenary_set_faith) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild, value, *calls; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + value = script_getnum(st,3); + + switch( guild ) + { + case ARCH_MERC_GUILD: + calls = &sd->status.arch_faith; + break; + case SPEAR_MERC_GUILD: + calls = &sd->status.spear_faith; + break; + case SWORD_MERC_GUILD: + calls = &sd->status.sword_faith; + break; + default: + return 0; // Invalid Guild + } + + *calls += value; + *calls = cap_value(*calls, 0, INT_MAX); + if( mercenary_get_guild(sd->md) == guild ) + clif_mercenary_updatestatus(sd,SP_MERCFAITH); + + return 0; +} + +/*------------------------------------------ + * Book Reading + *------------------------------------------*/ +BUILDIN_FUNC(readbook) +{ + struct map_session_data *sd; + int book_id, page; + + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + + book_id = script_getnum(st,2); + page = script_getnum(st,3); + + clif_readbook(sd->fd, book_id, page); + return 0; +} + +/****************** +Questlog script commands +*******************/ + +BUILDIN_FUNC(setquest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_add(sd, script_getnum(st, 2)); + return 0; +} + +BUILDIN_FUNC(erasequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_delete(sd, script_getnum(st, 2)); + return 0; +} + +BUILDIN_FUNC(completequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_update_status(sd, script_getnum(st, 2), Q_COMPLETE); + return 0; +} + +BUILDIN_FUNC(changequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_change(sd, script_getnum(st, 2),script_getnum(st, 3)); + return 0; +} + +BUILDIN_FUNC(checkquest) +{ + struct map_session_data *sd = script_rid2sd(st); + quest_check_type type = HAVEQUEST; + + nullpo_ret(sd); + + if( script_hasdata(st, 3) ) + type = (quest_check_type)script_getnum(st, 3); + + script_pushint(st, quest_check(sd, script_getnum(st, 2), type)); + + return 0; +} + +BUILDIN_FUNC(showevent) +{ + TBL_PC *sd = script_rid2sd(st); + struct npc_data *nd = map_id2nd(st->oid); + int state, color; + + if( sd == NULL || nd == NULL ) + return 0; + state = script_getnum(st, 2); + color = script_getnum(st, 3); + + if( color < 0 || color > 3 ) + color = 0; // set default color + + clif_quest_show_event(sd, &nd->bl, state, color); + return 0; +} + +/*========================================== + * BattleGround System + *------------------------------------------*/ +BUILDIN_FUNC(waitingroom2bg) +{ + struct npc_data *nd; + struct chat_data *cd; + const char *map_name, *ev = "", *dev = ""; + int x, y, i, mapindex = 0, bg_id, n; + struct map_session_data *sd; + + if( script_hasdata(st,7) ) + nd = npc_name2id(script_getstr(st,7)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + { + script_pushint(st,0); + return 0; + } + + map_name = script_getstr(st,2); + if( strcmp(map_name,"-") != 0 ) + { + mapindex = mapindex_name2id(map_name); + if( mapindex == 0 ) + { // Invalid Map + script_pushint(st,0); + return 0; + } + } + + x = script_getnum(st,3); + y = script_getnum(st,4); + ev = script_getstr(st,5); // Logout Event + dev = script_getstr(st,6); // Die Event + + if( (bg_id = bg_create(mapindex, x, y, ev, dev)) == 0 ) + { // Creation failed + script_pushint(st,0); + return 0; + } + + n = cd->users; + for( i = 0; i < n && i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = cd->usersd[i]) != NULL && bg_team_join(bg_id, sd) ) + mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), sd->bl.id); + else + mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), 0); + } + + mapreg_setreg(add_str("$@arenamembersnum"), i); + script_pushint(st,bg_id); + return 0; +} + +BUILDIN_FUNC(waitingroom2bg_single) +{ + const char* map_name; + struct npc_data *nd; + struct chat_data *cd; + struct map_session_data *sd; + int x, y, mapindex, bg_id; + + bg_id = script_getnum(st,2); + map_name = script_getstr(st,3); + if( (mapindex = mapindex_name2id(map_name)) == 0 ) + return 0; // Invalid Map + + x = script_getnum(st,4); + y = script_getnum(st,5); + nd = npc_name2id(script_getstr(st,6)); + + if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL || cd->users <= 0 ) + return 0; + + if( (sd = cd->usersd[0]) == NULL ) + return 0; + + if( bg_team_join(bg_id, sd) ) + { + pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); + script_pushint(st,1); + } + else + script_pushint(st,0); + + return 0; +} + +BUILDIN_FUNC(bg_team_setxy) +{ + struct battleground_data *bg; + int bg_id; + + bg_id = script_getnum(st,2); + if( (bg = bg_team_search(bg_id)) == NULL ) + return 0; + + bg->x = script_getnum(st,3); + bg->y = script_getnum(st,4); + return 0; +} + +BUILDIN_FUNC(bg_warp) +{ + int x, y, mapindex, bg_id; + const char* map_name; + + bg_id = script_getnum(st,2); + map_name = script_getstr(st,3); + if( (mapindex = mapindex_name2id(map_name)) == 0 ) + return 0; // Invalid Map + x = script_getnum(st,4); + y = script_getnum(st,5); + bg_team_warp(bg_id, mapindex, x, y); + return 0; +} + +BUILDIN_FUNC(bg_monster) +{ + int class_ = 0, x = 0, y = 0, bg_id = 0; + const char *str,*map, *evt=""; + + bg_id = script_getnum(st,2); + map = script_getstr(st,3); + x = script_getnum(st,4); + y = script_getnum(st,5); + str = script_getstr(st,6); + class_ = script_getnum(st,7); + if( script_hasdata(st,8) ) evt = script_getstr(st,8); + check_event(st, evt); + script_pushint(st, mob_spawn_bg(map,x,y,str,class_,evt,bg_id)); + return 0; +} + +BUILDIN_FUNC(bg_monster_set_team) +{ + struct mob_data *md; + struct block_list *mbl; + int id = script_getnum(st,2), + bg_id = script_getnum(st,3); + + if( (mbl = map_id2bl(id)) == NULL || mbl->type != BL_MOB ) + return 0; + md = (TBL_MOB *)mbl; + md->bg_id = bg_id; + + mob_stop_attack(md); + mob_stop_walking(md, 0); + md->target_id = md->attacked_id = 0; + clif_charnameack(0, &md->bl); + + return 0; +} + +BUILDIN_FUNC(bg_leave) +{ + struct map_session_data *sd = script_rid2sd(st); + if( sd == NULL || !sd->bg_id ) + return 0; + + bg_team_leave(sd,0); + return 0; +} + +BUILDIN_FUNC(bg_destroy) +{ + int bg_id = script_getnum(st,2); + bg_team_delete(bg_id); + return 0; +} + +BUILDIN_FUNC(bg_getareausers) +{ + const char *str; + int16 m, x0, y0, x1, y1; + int bg_id; + int i = 0, c = 0; + struct battleground_data *bg = NULL; + struct map_session_data *sd; + + bg_id = script_getnum(st,2); + str = script_getstr(st,3); + + if( (bg = bg_team_search(bg_id)) == NULL || (m = map_mapname2mapid(str)) < 0 ) + { + script_pushint(st,0); + return 0; + } + + x0 = script_getnum(st,4); + y0 = script_getnum(st,5); + x1 = script_getnum(st,6); + y1 = script_getnum(st,7); + + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL ) + continue; + if( sd->bl.m != m || sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1 ) + continue; + c++; + } + + script_pushint(st,c); + return 0; +} + +BUILDIN_FUNC(bg_updatescore) +{ + const char *str; + int16 m; + + str = script_getstr(st,2); + if( (m = map_mapname2mapid(str)) < 0 ) + return 0; + + map[m].bgscore_lion = script_getnum(st,3); + map[m].bgscore_eagle = script_getnum(st,4); + + clif_bg_updatescore(m); + return 0; +} + +BUILDIN_FUNC(bg_get_data) +{ + struct battleground_data *bg; + int bg_id = script_getnum(st,2), + type = script_getnum(st,3); + + if( (bg = bg_team_search(bg_id)) == NULL ) + { + script_pushint(st,0); + return 0; + } + + switch( type ) + { + case 0: script_pushint(st, bg->count); break; + default: + ShowError("script:bg_get_data: unknown data identifier %d\n", type); + break; + } + + return 0; +} + +/*========================================== + * Instancing Script Commands + *------------------------------------------*/ + +BUILDIN_FUNC(instance_create) +{ + const char *name; + int party_id, res; + + name = script_getstr(st, 2); + party_id = script_getnum(st, 3); + + res = instance_create(party_id, name); + if( res == -4 ) // Already exists + { + script_pushint(st, -1); + return 0; + } + else if( res < 0 ) + { + const char *err; + switch(res) + { + case -3: err = "No free instances"; break; + case -2: err = "Invalid party ID"; break; + case -1: err = "Invalid type"; break; + default: err = "Unknown"; break; + } + ShowError("buildin_instance_create: %s [%d].\n", err, res); + script_pushint(st, -2); + return 0; + } + + script_pushint(st, res); + return 0; +} + +BUILDIN_FUNC(instance_destroy) +{ + int instance_id; + struct map_session_data *sd; + struct party_data *p; + + if( script_hasdata(st, 2) ) + instance_id = script_getnum(st, 2); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + { + ShowError("buildin_instance_destroy: Trying to destroy invalid instance %d.\n", instance_id); + return 0; + } + + instance_destroy(instance_id); + return 0; +} + +BUILDIN_FUNC(instance_attachmap) +{ + const char *name; + int16 m; + int instance_id; + bool usebasename = false; + + name = script_getstr(st,2); + instance_id = script_getnum(st,3); + if( script_hasdata(st,4) && script_getnum(st,4) > 0) + usebasename = true; + + if( (m = instance_add_map(name, instance_id, usebasename)) < 0 ) // [Saithis] + { + ShowError("buildin_instance_attachmap: instance creation failed (%s): %d\n", name, m); + script_pushconststr(st, ""); + return 0; + } + script_pushconststr(st, map[m].name); + + return 0; +} + +BUILDIN_FUNC(instance_detachmap) +{ + struct map_session_data *sd; + struct party_data *p; + const char *str; + int16 m; + int instance_id; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m,instance_id)) < 0 ) + { + ShowError("buildin_instance_detachmap: Trying to detach invalid map %s\n", str); + return 0; + } + + instance_del_map(m); + return 0; +} + +BUILDIN_FUNC(instance_attach) +{ + int instance_id; + + instance_id = script_getnum(st, 2); + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + return 0; + + st->instance_id = instance_id; + return 0; +} + +BUILDIN_FUNC(instance_id) +{ + int type, instance_id; + struct map_session_data *sd; + struct party_data *p; + + if( script_hasdata(st, 2) ) + { + type = script_getnum(st, 2); + if( type == 0 ) + instance_id = st->instance_id; + else if( type == 1 && (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL ) + instance_id = p->instance_id; + else + instance_id = 0; + } + else + instance_id = st->instance_id; + + script_pushint(st, instance_id); + return 0; +} + +BUILDIN_FUNC(instance_set_timeout) +{ + int progress_timeout, idle_timeout; + int instance_id; + struct map_session_data *sd; + struct party_data *p; + + progress_timeout = script_getnum(st, 2); + idle_timeout = script_getnum(st, 3); + + if( script_hasdata(st, 4) ) + instance_id = script_getnum(st, 4); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( instance_id > 0 ) + instance_set_timeout(instance_id, progress_timeout, idle_timeout); + + return 0; +} + +BUILDIN_FUNC(instance_init) +{ + int instance_id = script_getnum(st, 2); + + if( instance[instance_id].state != INSTANCE_IDLE ) + { + ShowError("instance_init: instance already initialized.\n"); + return 0; + } + + instance_init(instance_id); + return 0; +} + +BUILDIN_FUNC(instance_announce) +{ + int instance_id = script_getnum(st,2); + const char *mes = script_getstr(st,3); + int flag = script_getnum(st,4); + const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; + int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize + int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign + int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY + + int i; + struct map_session_data *sd; + struct party_data *p; + + if( instance_id == 0 ) + { + if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + } + + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + return 0; + + for( i = 0; i < instance[instance_id].num_map; i++ ) + map_foreachinmap(buildin_announce_sub, instance[instance_id].map[i], BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + + return 0; +} + +BUILDIN_FUNC(instance_npcname) +{ + const char *str; + int instance_id = 0; + + struct map_session_data *sd; + struct party_data *p; + struct npc_data *nd; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + + if( instance_id && (nd = npc_name2id(str)) != NULL ) + { + static char npcname[NAME_LENGTH]; + snprintf(npcname, sizeof(npcname), "dup_%d_%d", instance_id, nd->bl.id); + script_pushconststr(st,npcname); + } + else + { + ShowError("script:instance_npcname: invalid instance NPC (instance_id: %d, NPC name: \"%s\".)\n", instance_id, str); + st->state = END; + return 1; + } + + return 0; +} + +BUILDIN_FUNC(has_instance) +{ + struct map_session_data *sd; + struct party_data *p; + const char *str; + int16 m; + int instance_id = 0; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + + if( !instance_id || (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m, instance_id)) < 0 ) + { + script_pushconststr(st, ""); + return 0; + } + + script_pushconststr(st, map[m].name); + return 0; +} + +BUILDIN_FUNC(instance_warpall) +{ + struct map_session_data *pl_sd; + int16 m, i; + int instance_id; + const char *mapn; + int x, y; + unsigned short mapindex; + struct party_data *p = NULL; + + mapn = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + if( script_hasdata(st,5) ) + instance_id = script_getnum(st,5); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (pl_sd = script_rid2sd(st)) != NULL && pl_sd->status.party_id && (p = party_search(pl_sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( (m = map_mapname2mapid(mapn)) < 0 || (map[m].flag.src4instance && (m = instance_mapid2imapid(m, instance_id)) < 0) ) + return 0; + + if( !(p = party_search(instance[instance_id].party_id)) ) + return 0; + + mapindex = map_id2index(m); + for( i = 0; i < MAX_PARTY; i++ ) + if( (pl_sd = p->data[i].sd) && map[pl_sd->bl.m].instance_id == st->instance_id ) pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); + + return 0; +} + +/*========================================== + * instance_check_party [malufett] + * Values: + * party_id : Party ID of the invoking character. [Required Parameter] + * amount : Amount of needed Partymembers for the Instance. [Optional Parameter] + * min : Minimum Level needed to join the Instance. [Optional Parameter] + * max : Maxium Level allowed to join the Instance. [Optional Parameter] + * Example: instance_check_party (getcharid(1){,amount}{,min}{,max}); + * Example 2: instance_check_party (getcharid(1),1,1,99); + *------------------------------------------*/ +BUILDIN_FUNC(instance_check_party) +{ + struct map_session_data *pl_sd; + int amount, min, max, i, party_id, c = 0; + struct party_data *p = NULL; + + amount = script_hasdata(st,3) ? script_getnum(st,3) : 1; // Amount of needed Partymembers for the Instance. + min = script_hasdata(st,4) ? script_getnum(st,4) : 1; // Minimum Level needed to join the Instance. + max = script_hasdata(st,5) ? script_getnum(st,5) : MAX_LEVEL; // Maxium Level allowed to join the Instance. + + if( min < 1 || min > MAX_LEVEL){ + ShowError("instance_check_party: Invalid min level, %d\n", min); + return 0; + }else if( max < 1 || max > MAX_LEVEL){ + ShowError("instance_check_party: Invalid max level, %d\n", max); + return 0; + } + + if( script_hasdata(st,2) ) + party_id = script_getnum(st,2); + else return 0; + + if( !(p = party_search(party_id)) ){ + script_pushint(st, 0); // Returns false if party does not exist. + return 0; + } + + for( i = 0; i < MAX_PARTY; i++ ) + if( (pl_sd = p->data[i].sd) ) + if(map_id2bl(pl_sd->bl.id)){ + if(pl_sd->status.base_level < min){ + script_pushint(st, 0); + return 0; + }else if(pl_sd->status.base_level > max){ + script_pushint(st, 0); + return 0; + } + c++; + } + + if(c < amount){ + script_pushint(st, 0); // Not enough Members in the Party to join Instance. + }else + script_pushint(st, 1); + + return 0; +} + +/*========================================== + * Custom Fonts + *------------------------------------------*/ +BUILDIN_FUNC(setfont) +{ + struct map_session_data *sd = script_rid2sd(st); + int font = script_getnum(st,2); + if( sd == NULL ) + return 0; + + if( sd->user_font != font ) + sd->user_font = font; + else + sd->user_font = 0; + + clif_font(sd); + return 0; +} + +static int buildin_mobuseskill_sub(struct block_list *bl,va_list ap) +{ + TBL_MOB* md = (TBL_MOB*)bl; + struct block_list *tbl; + int mobid = va_arg(ap,int); + uint16 skill_id = va_arg(ap,int); + uint16 skill_lv = va_arg(ap,int); + int casttime = va_arg(ap,int); + int cancel = va_arg(ap,int); + int emotion = va_arg(ap,int); + int target = va_arg(ap,int); + + if( md->class_ != mobid ) + return 0; + + // 0:self, 1:target, 2:master, default:random + switch( target ) + { + case 0: tbl = map_id2bl(md->bl.id); break; + case 1: tbl = map_id2bl(md->target_id); break; + case 2: tbl = map_id2bl(md->master_id); break; + default:tbl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),skill_get_range2(&md->bl, skill_id, skill_lv)); break; + } + + if( !tbl ) + return 0; + + if( md->ud.skilltimer != INVALID_TIMER ) // Cancel the casting skill. + unit_skillcastcancel(bl,0); + + if( skill_get_casttype(skill_id) == CAST_GROUND ) + unit_skilluse_pos2(&md->bl, tbl->x, tbl->y, skill_id, skill_lv, casttime, cancel); + else + unit_skilluse_id2(&md->bl, tbl->id, skill_id, skill_lv, casttime, cancel); + + clif_emotion(&md->bl, emotion); + + return 0; +} +/*========================================== + * areamobuseskill "Map Name",<x>,<y>,<range>,<Mob ID>,"Skill Name"/<Skill ID>,<Skill Lv>,<Cast Time>,<Cancelable>,<Emotion>,<Target Type>; + *------------------------------------------*/ +BUILDIN_FUNC(areamobuseskill) +{ + struct block_list center; + int16 m; + int range,mobid,skill_id,skill_lv,casttime,emotion,target,cancel; + + if( (m = map_mapname2mapid(script_getstr(st,2))) < 0 ) + { + ShowError("areamobuseskill: invalid map name.\n"); + return 0; + } + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + center.m = m; + center.x = script_getnum(st,3); + center.y = script_getnum(st,4); + range = script_getnum(st,5); + mobid = script_getnum(st,6); + skill_id = ( script_isstring(st,7) ? skill_name2id(script_getstr(st,7)) : script_getnum(st,7) ); + if( (skill_lv = script_getnum(st,8)) > battle_config.mob_max_skilllvl ) + skill_lv = battle_config.mob_max_skilllvl; + + casttime = script_getnum(st,9); + cancel = script_getnum(st,10); + emotion = script_getnum(st,11); + target = script_getnum(st,12); + + map_foreachinrange(buildin_mobuseskill_sub, ¢er, range, BL_MOB, mobid, skill_id, skill_lv, casttime, cancel, emotion, target); + return 0; +} + + +BUILDIN_FUNC(progressbar) +{ + struct map_session_data * sd = script_rid2sd(st); + const char * color; + unsigned int second; + + if( !st || !sd ) + return 0; + + st->state = STOP; + + color = script_getstr(st,2); + second = script_getnum(st,3); + + sd->progressbar.npc_id = st->oid; + sd->progressbar.timeout = gettick() + second*1000; + + clif_progressbar(sd, strtol(color, (char **)NULL, 0), second); + return 0; +} + +BUILDIN_FUNC(pushpc) +{ + uint8 dir; + int cells, dx, dy; + struct map_session_data* sd; + + if((sd = script_rid2sd(st))==NULL) + { + return 0; + } + + dir = script_getnum(st,2); + cells = script_getnum(st,3); + + if(dir>7) + { + ShowWarning("buildin_pushpc: Invalid direction %d specified.\n", dir); + script_reportsrc(st); + + dir%= 8; // trim spin-over + } + + if(!cells) + {// zero distance + return 0; + } + else if(cells<0) + {// pushing backwards + dir = (dir+4)%8; // turn around + cells = -cells; + } + + dx = dirx[dir]; + dy = diry[dir]; + + unit_blown(&sd->bl, dx, dy, cells, 0); + return 0; +} + + +/// Invokes buying store preparation window +/// buyingstore <slots>; +BUILDIN_FUNC(buyingstore) +{ + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + buyingstore_setup(sd, script_getnum(st,2)); + return 0; +} + + +/// Invokes search store info window +/// searchstores <uses>,<effect>; +BUILDIN_FUNC(searchstores) +{ + unsigned short effect; + unsigned int uses; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + uses = script_getnum(st,2); + effect = script_getnum(st,3); + + if( !uses ) + { + ShowError("buildin_searchstores: Amount of uses cannot be zero.\n"); + return 1; + } + + if( effect > 1 ) + { + ShowError("buildin_searchstores: Invalid effect id %hu, specified.\n", effect); + return 1; + } + + searchstore_open(sd, uses, effect); + return 0; +} +/// Displays a number as large digital clock. +/// showdigit <value>[,<type>]; +BUILDIN_FUNC(showdigit) +{ + unsigned int type = 0; + int value; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + value = script_getnum(st,2); + + if( script_hasdata(st,3) ) + { + type = script_getnum(st,3); + + if( type > 3 ) + { + ShowError("buildin_showdigit: Invalid type %u.\n", type); + return 1; + } + } + + clif_showdigit(sd, (unsigned char)type, value); + return 0; +} +/** + * Rune Knight + **/ +BUILDIN_FUNC(makerune) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + clif_skill_produce_mix_list(sd,RK_RUNEMASTERY,24); + sd->itemid = script_getnum(st,2); + return 0; +} +/** + * checkdragon() returns 1 if mounting a dragon or 0 otherwise. + **/ +BUILDIN_FUNC(checkdragon) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( pc_isridingdragon(sd) ) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} +/** + * setdragon({optional Color}) returns 1 on success or 0 otherwise + * - Toggles the dragon on a RK if he can mount; + * @param Color - when not provided uses the green dragon; + * - 1 : Green Dragon + * - 2 : Brown Dragon + * - 3 : Gray Dragon + * - 4 : Blue Dragon + * - 5 : Red Dragon + **/ +BUILDIN_FUNC(setdragon) { + TBL_PC* sd; + int color = script_hasdata(st,2) ? script_getnum(st,2) : 0; + unsigned int option = OPTION_DRAGON1; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( !pc_checkskill(sd,RK_DRAGONTRAINING) || (sd->class_&MAPID_THIRDMASK) != MAPID_RUNE_KNIGHT ) + script_pushint(st,0);//Doesn't have the skill or it's not a Rune Knight + else if ( pc_isridingdragon(sd) ) {//Is mounted; release + pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + script_pushint(st,1); + } else {//Not mounted; Mount now. + if( color ) { + option = ( color == 1 ? OPTION_DRAGON1 : + color == 2 ? OPTION_DRAGON2 : + color == 3 ? OPTION_DRAGON3 : + color == 4 ? OPTION_DRAGON4 : + color == 5 ? OPTION_DRAGON5 : 0); + if( !option ) { + ShowWarning("script_setdragon: Unknown Color %d used; changing to green (1)\n",color); + option = OPTION_DRAGON1; + } + } + pc_setoption(sd, sd->sc.option|option); + script_pushint(st,1); + } + return 0; +} + +/** + * ismounting() returns 1 if mounting a new mount or 0 otherwise + **/ +BUILDIN_FUNC(ismounting) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( sd->sc.option&OPTION_MOUNTING ) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} + +/** + * setmounting() returns 1 on success or 0 otherwise + * - Toggles new mounts on a player when he can mount + * - Will fail if the player is mounting a non-new mount, e.g. dragon, peco, wug, etc. + * - Will unmount the player is he is already mounting + **/ +BUILDIN_FUNC(setmounting) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( sd->sc.option&(OPTION_WUGRIDER|OPTION_RIDING|OPTION_DRAGON|OPTION_MADOGEAR) ) + script_pushint(st,0);//can't mount with one of these + else { + if( sd->sc.option&OPTION_MOUNTING ) + pc_setoption(sd, sd->sc.option&~OPTION_MOUNTING);//release mount + else + pc_setoption(sd, sd->sc.option|OPTION_MOUNTING);//mount + script_pushint(st,1);//in both cases, return 1. + } + return 0; +} +/** + * Retrieves quantity of arguments provided to callfunc/callsub. + * getargcount() -> amount of arguments received in a function + **/ +BUILDIN_FUNC(getargcount) { + struct script_retinfo* ri; + + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) { + ShowError("script:getargcount: used out of function or callsub label!\n"); + st->state = END; + return 1; + } + ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; + + script_pushint(st, ri->nargs); + + return 0; +} +/** + * getcharip(<account ID>/<character ID>/<character name>) + **/ +BUILDIN_FUNC(getcharip) +{ + struct map_session_data* sd = NULL; + int id = 0; + + /* check if a character name is specified */ + if( script_hasdata(st, 2) ) + { + if (script_isstring(st, 2)) + sd = map_nick2sd(script_getstr(st, 2)); + else if (script_isint(st, 2) || script_getnum(st, 2)) + { + id = script_getnum(st, 2); + sd = (map_id2sd(id) ? map_id2sd(id) : map_charid2sd(id)); + } + } + else + sd = script_rid2sd(st); + + /* check for sd and IP */ + if (!sd || !session[sd->fd]->client_addr) + { + script_pushconststr(st, ""); + return 0; + } + + /* return the client ip_addr converted for output */ + if (sd && sd->fd && session[sd->fd]) + { + /* initiliaze */ + const char *ip_addr = NULL; + uint32 ip; + + /* set ip, ip_addr and convert to ip and push str */ + ip = session[sd->fd]->client_addr; + ip_addr = ip2str(ip, NULL); + script_pushstrcopy(st, ip_addr); + } + + return 0; +} +/** + * is_function(<function name>) -> 1 if function exists, 0 otherwise + **/ +BUILDIN_FUNC(is_function) { + const char* str = script_getstr(st,2); + + if( strdb_exists(userfunc_db, str) ) + script_pushint(st,1); + else + script_pushint(st,0); + + return 0; +} +/** + * get_revision() -> retrieves the current svn revision (if available) + **/ +BUILDIN_FUNC(get_revision) { + const char * revision; + + if ( (revision = get_svn_revision()) != 0 ) + script_pushint(st,atoi(revision)); + else + script_pushint(st,-1);//unknown + + return 0; +} +/** + * freeloop(<toggle>) -> toggles this script instance's looping-check ability + **/ +BUILDIN_FUNC(freeloop) { + + if( script_getnum(st,2) ) + st->freeloop = 1; + else + st->freeloop = 0; + + script_pushint(st, st->freeloop); + + return 0; +} + +/** + * @commands (script based) + **/ +BUILDIN_FUNC(bindatcmd) { + const char* atcmd; + const char* eventName; + int i, level = 0, level2 = 0; + bool create = false; + + atcmd = script_getstr(st,2); + eventName = script_getstr(st,3); + + if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) + atcmd++; + + if( script_hasdata(st,4) ) level = script_getnum(st,4); + if( script_hasdata(st,5) ) level2 = script_getnum(st,5); + + if( atcmd_binding_count == 0 ) { + CREATE(atcmd_binding,struct atcmd_binding_data*,1); + + create = true; + } else { + ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command,atcmd) == 0); + if( i < atcmd_binding_count ) {/* update existent entry */ + safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); + atcmd_binding[i]->level = level; + atcmd_binding[i]->level2 = level2; + } else + create = true; + } + + if( create ) { + i = atcmd_binding_count; + + if( atcmd_binding_count++ != 0 ) + RECREATE(atcmd_binding,struct atcmd_binding_data*,atcmd_binding_count); + + CREATE(atcmd_binding[i],struct atcmd_binding_data,1); + + safestrncpy(atcmd_binding[i]->command, atcmd, 50); + safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); + atcmd_binding[i]->level = level; + atcmd_binding[i]->level2 = level2; + } + + return 0; +} + +BUILDIN_FUNC(unbindatcmd) { + const char* atcmd; + int i = 0; + + atcmd = script_getstr(st, 2); + + if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) + atcmd++; + + if( atcmd_binding_count == 0 ) { + script_pushint(st, 0); + return 0; + } + + ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command, atcmd) == 0); + if( i < atcmd_binding_count ) { + int cursor = 0; + aFree(atcmd_binding[i]); + atcmd_binding[i] = NULL; + /* compact the list now that we freed a slot somewhere */ + for( i = 0, cursor = 0; i < atcmd_binding_count; i++ ) { + if( atcmd_binding[i] == NULL ) + continue; + + if( cursor != i ) { + memmove(&atcmd_binding[cursor], &atcmd_binding[i], sizeof(struct atcmd_binding_data*)); + } + + cursor++; + } + + if( (atcmd_binding_count = cursor) == 0 ) + aFree(atcmd_binding); + + script_pushint(st, 1); + } else + script_pushint(st, 0);/* not found */ + + return 0; +} + +BUILDIN_FUNC(useatcmd) +{ + TBL_PC dummy_sd; + TBL_PC* sd; + int fd; + const char* cmd; + + cmd = script_getstr(st,2); + + if( st->rid ) + { + sd = script_rid2sd(st); + fd = sd->fd; + } + else + { // Use a dummy character. + sd = &dummy_sd; + fd = 0; + + memset(&dummy_sd, 0, sizeof(TBL_PC)); + if( st->oid ) + { + struct block_list* bl = map_id2bl(st->oid); + memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); + if( bl->type == BL_NPC ) + safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); + } + } + + // compatibility with previous implementation (deprecated!) + if( cmd[0] != atcommand_symbol ) + { + cmd += strlen(sd->status.name); + while( *cmd != atcommand_symbol && *cmd != 0 ) + cmd++; + } + + is_atcommand(fd, sd, cmd, 1); + return 0; +} + +BUILDIN_FUNC(checkre) +{ + int num; + + num=script_getnum(st,2); + switch(num){ + case 0: + #ifdef RENEWAL + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 1: + #ifdef RENEWAL_CAST + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 2: + #ifdef RENEWAL_DROP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 3: + #ifdef RENEWAL_EXP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 4: + #ifdef RENEWAL_LVDMG + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 5: + #ifdef RENEWAL_EDP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 6: + #ifdef RENEWAL_ASPD + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + default: + ShowWarning("buildin_checkre: unknown parameter.\n"); + break; + } + return 0; +} + +/* getrandgroupitem <group_id>,<quantity> */ +BUILDIN_FUNC(getrandgroupitem) { + TBL_PC* sd; + int i, get_count = 0, flag, nameid, group = script_getnum(st, 2), qty = script_getnum(st,3); + struct item item_tmp; + + if( !( sd = script_rid2sd(st) ) ) + return 0; + + if( qty <= 0 ) { + ShowError("getrandgroupitem: qty is <= 0!\n"); + return 1; + } + if( (nameid = itemdb_searchrandomid(group)) == UNKNOWN_ITEM_ID ) { + return 1;/* itemdb_searchrandomid will already scream a error */ + } + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = nameid; + item_tmp.identify = itemdb_isidentified(nameid); + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = qty; + + for (i = 0; i < qty; i += get_count) { + // if not pet egg + if (!pet_create_egg(sd, nameid)) { + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&item_tmp) ) + map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + return 0; +} + +/* cleanmap <map_name>; + * cleanarea <map_name>, <x0>, <y0>, <x1>, <y1>; */ +static int atcommand_cleanfloor_sub(struct block_list *bl, va_list ap) +{ + nullpo_ret(bl); + map_clearflooritem(bl); + + return 0; +} + +BUILDIN_FUNC(cleanmap) +{ + const char *map; + int16 m = -1; + int16 x0 = 0, y0 = 0, x1 = 0, y1 = 0; + + map = script_getstr(st, 2); + m = map_mapname2mapid(map); + if (!m) + return 1; + + if ((script_lastdata(st) - 2) < 4) { + map_foreachinmap(atcommand_cleanfloor_sub, m, BL_ITEM); + } else { + x0 = script_getnum(st, 3); + y0 = script_getnum(st, 4); + x1 = script_getnum(st, 5); + y1 = script_getnum(st, 6); + if (x0 > 0 && y0 > 0 && x1 > 0 && y1 > 0) { + map_foreachinarea(atcommand_cleanfloor_sub, m, x0, y0, x1, y1, BL_ITEM); + } else { + ShowError("cleanarea: invalid coordinate defined!\n"); + return 1; + } + } + + return 0; +} +/* Cast a skill on the attached player. + * npcskill <skill id>, <skill lvl>, <stat point>, <NPC level>; + * npcskill "<skill name>", <skill lvl>, <stat point>, <NPC level>; */ +BUILDIN_FUNC(npcskill) +{ + uint16 skill_id; + unsigned short skill_level; + unsigned int stat_point; + unsigned int npc_level; + struct npc_data *nd; + struct map_session_data *sd; + + skill_id = script_isstring(st, 2) ? skill_name2id(script_getstr(st, 2)) : script_getnum(st, 2); + skill_level = script_getnum(st, 3); + stat_point = script_getnum(st, 4); + npc_level = script_getnum(st, 5); + sd = script_rid2sd(st); + nd = (struct npc_data *)map_id2bl(sd->npc_id); + + if (stat_point > battle_config.max_third_parameter) { + ShowError("npcskill: stat point exceeded maximum of %d.\n",battle_config.max_third_parameter ); + return 1; + } + if (npc_level > MAX_LEVEL) { + ShowError("npcskill: level exceeded maximum of %d.\n", MAX_LEVEL); + return 1; + } + if (sd == NULL || nd == NULL) { //ain't possible, but I don't trust people. + return 1; + } + + nd->level = npc_level; + nd->stat_point = stat_point; + + if (!nd->status.hp) { + status_calc_npc(nd, true); + } else { + status_calc_npc(nd, false); + } + + if (skill_get_inf(skill_id)&INF_GROUND_SKILL) { + unit_skilluse_pos(&nd->bl, sd->bl.x, sd->bl.y, skill_id, skill_level); + } else { + unit_skilluse_id(&nd->bl, sd->bl.id, skill_id, skill_level); + } + + return 0; +} + +// declarations that were supposed to be exported from npc_chat.c +#ifdef PCRE_SUPPORT +BUILDIN_FUNC(defpattern); +BUILDIN_FUNC(activatepset); +BUILDIN_FUNC(deactivatepset); +BUILDIN_FUNC(deletepset); +#endif + +/// script command definitions +/// for an explanation on args, see add_buildin_func +struct script_function buildin_func[] = { + // NPC interaction + BUILDIN_DEF(mes,"s*"), + BUILDIN_DEF(next,""), + BUILDIN_DEF(close,""), + BUILDIN_DEF(close2,""), + BUILDIN_DEF(menu,"sl*"), + BUILDIN_DEF(select,"s*"), //for future jA script compatibility + BUILDIN_DEF(prompt,"s*"), + // + BUILDIN_DEF(goto,"l"), + BUILDIN_DEF(callsub,"l*"), + BUILDIN_DEF(callfunc,"s*"), + BUILDIN_DEF(return,"?"), + BUILDIN_DEF(getarg,"i?"), + BUILDIN_DEF(jobchange,"i?"), + BUILDIN_DEF(jobname,"i"), + BUILDIN_DEF(input,"r??"), + BUILDIN_DEF(warp,"sii"), + BUILDIN_DEF(areawarp,"siiiisii??"), + BUILDIN_DEF(warpchar,"siii"), // [LuzZza] + BUILDIN_DEF(warpparty,"siii?"), // [Fredzilla] [Paradox924X] + BUILDIN_DEF(warpguild,"siii"), // [Fredzilla] + BUILDIN_DEF(setlook,"ii"), + BUILDIN_DEF(changelook,"ii"), // Simulates but don't Store it + BUILDIN_DEF(set,"rv"), + BUILDIN_DEF(setarray,"rv*"), + BUILDIN_DEF(cleararray,"rvi"), + BUILDIN_DEF(copyarray,"rri"), + BUILDIN_DEF(getarraysize,"r"), + BUILDIN_DEF(deletearray,"r?"), + BUILDIN_DEF(getelementofarray,"ri"), + BUILDIN_DEF(getitem,"vi?"), + BUILDIN_DEF(rentitem,"vi"), + BUILDIN_DEF(getitem2,"viiiiiiii?"), + BUILDIN_DEF(getnameditem,"vv"), + BUILDIN_DEF2(grouprandomitem,"groupranditem","i"), + BUILDIN_DEF(makeitem,"visii"), + BUILDIN_DEF(delitem,"vi?"), + BUILDIN_DEF(delitem2,"viiiiiiii?"), + BUILDIN_DEF2(enableitemuse,"enable_items",""), + BUILDIN_DEF2(disableitemuse,"disable_items",""), + BUILDIN_DEF(cutin,"si"), + BUILDIN_DEF(viewpoint,"iiiii"), + BUILDIN_DEF(heal,"ii"), + BUILDIN_DEF(itemheal,"ii"), + BUILDIN_DEF(percentheal,"ii"), + BUILDIN_DEF(rand,"i?"), + BUILDIN_DEF(countitem,"v"), + BUILDIN_DEF(countitem2,"viiiiiii"), + BUILDIN_DEF(checkweight,"vi*"), + BUILDIN_DEF(checkweight2,"rr"), + BUILDIN_DEF(readparam,"i?"), + BUILDIN_DEF(getcharid,"i?"), + BUILDIN_DEF(getnpcid,"i?"), + BUILDIN_DEF(getpartyname,"i"), + BUILDIN_DEF(getpartymember,"i?"), + BUILDIN_DEF(getpartyleader,"i?"), + BUILDIN_DEF(getguildname,"i"), + BUILDIN_DEF(getguildmaster,"i"), + BUILDIN_DEF(getguildmasterid,"i"), + BUILDIN_DEF(strcharinfo,"i"), + BUILDIN_DEF(strnpcinfo,"i"), + BUILDIN_DEF(getequipid,"i"), + BUILDIN_DEF(getequipname,"i"), + BUILDIN_DEF(getbrokenid,"i"), // [Valaris] + BUILDIN_DEF(repair,"i"), // [Valaris] + BUILDIN_DEF(repairall,""), + BUILDIN_DEF(getequipisequiped,"i"), + BUILDIN_DEF(getequipisenableref,"i"), + BUILDIN_DEF(getequipisidentify,"i"), + BUILDIN_DEF(getequiprefinerycnt,"i"), + BUILDIN_DEF(getequipweaponlv,"i"), + BUILDIN_DEF(getequippercentrefinery,"i"), + BUILDIN_DEF(successrefitem,"i"), + BUILDIN_DEF(failedrefitem,"i"), + BUILDIN_DEF(downrefitem,"i"), + BUILDIN_DEF(statusup,"i"), + BUILDIN_DEF(statusup2,"ii"), + BUILDIN_DEF(bonus,"iv"), + BUILDIN_DEF2(bonus,"bonus2","ivi"), + BUILDIN_DEF2(bonus,"bonus3","ivii"), + BUILDIN_DEF2(bonus,"bonus4","ivvii"), + BUILDIN_DEF2(bonus,"bonus5","ivviii"), + BUILDIN_DEF(autobonus,"sii??"), + BUILDIN_DEF(autobonus2,"sii??"), + BUILDIN_DEF(autobonus3,"siiv?"), + BUILDIN_DEF(skill,"vi?"), + BUILDIN_DEF(addtoskill,"vi?"), // [Valaris] + BUILDIN_DEF(guildskill,"vi"), + BUILDIN_DEF(getskilllv,"v"), + BUILDIN_DEF(getgdskilllv,"iv"), + BUILDIN_DEF(basicskillcheck,""), + BUILDIN_DEF(getgmlevel,""), + BUILDIN_DEF(getgroupid,""), + BUILDIN_DEF(end,""), + BUILDIN_DEF(checkoption,"i"), + BUILDIN_DEF(setoption,"i?"), + BUILDIN_DEF(setcart,"?"), + BUILDIN_DEF(checkcart,""), + BUILDIN_DEF(setfalcon,"?"), + BUILDIN_DEF(checkfalcon,""), + BUILDIN_DEF(setriding,"?"), + BUILDIN_DEF(checkriding,""), + BUILDIN_DEF(checkwug,""), + BUILDIN_DEF(checkmadogear,""), + BUILDIN_DEF(setmadogear,"?"), + BUILDIN_DEF2(savepoint,"save","sii"), + BUILDIN_DEF(savepoint,"sii"), + BUILDIN_DEF(gettimetick,"i"), + BUILDIN_DEF(gettime,"i"), + BUILDIN_DEF(gettimestr,"si"), + BUILDIN_DEF(openstorage,""), + BUILDIN_DEF(guildopenstorage,""), + BUILDIN_DEF(itemskill,"vi"), + BUILDIN_DEF(produce,"i"), + BUILDIN_DEF(cooking,"i"), + BUILDIN_DEF(monster,"siisii???"), + BUILDIN_DEF(getmobdrops,"i"), + BUILDIN_DEF(areamonster,"siiiisii???"), + BUILDIN_DEF(killmonster,"ss?"), + BUILDIN_DEF(killmonsterall,"s?"), + BUILDIN_DEF(clone,"siisi????"), + BUILDIN_DEF(doevent,"s"), + BUILDIN_DEF(donpcevent,"s"), + BUILDIN_DEF(cmdothernpc,"ss"), + BUILDIN_DEF(addtimer,"is"), + BUILDIN_DEF(deltimer,"s"), + BUILDIN_DEF(addtimercount,"si"), + BUILDIN_DEF(initnpctimer,"??"), + BUILDIN_DEF(stopnpctimer,"??"), + BUILDIN_DEF(startnpctimer,"??"), + BUILDIN_DEF(setnpctimer,"i?"), + BUILDIN_DEF(getnpctimer,"i?"), + BUILDIN_DEF(attachnpctimer,"?"), // attached the player id to the npc timer [Celest] + BUILDIN_DEF(detachnpctimer,"?"), // detached the player id from the npc timer [Celest] + BUILDIN_DEF(playerattached,""), // returns id of the current attached player. [Skotlex] + BUILDIN_DEF(announce,"si?????"), + BUILDIN_DEF(mapannounce,"ssi?????"), + BUILDIN_DEF(areaannounce,"siiiisi?????"), + BUILDIN_DEF(getusers,"i"), + BUILDIN_DEF(getmapguildusers,"si"), + BUILDIN_DEF(getmapusers,"s"), + BUILDIN_DEF(getareausers,"siiii"), + BUILDIN_DEF(getareadropitem,"siiiiv"), + BUILDIN_DEF(enablenpc,"s"), + BUILDIN_DEF(disablenpc,"s"), + BUILDIN_DEF(hideoffnpc,"s"), + BUILDIN_DEF(hideonnpc,"s"), + BUILDIN_DEF(sc_start,"iii?"), + BUILDIN_DEF(sc_start2,"iiii?"), + BUILDIN_DEF(sc_start4,"iiiiii?"), + BUILDIN_DEF(sc_end,"i?"), + BUILDIN_DEF(getstatus, "i?"), + BUILDIN_DEF(getscrate,"ii?"), + BUILDIN_DEF(debugmes,"s"), + BUILDIN_DEF2(catchpet,"pet","i"), + BUILDIN_DEF2(birthpet,"bpet",""), + BUILDIN_DEF(resetlvl,"i"), + BUILDIN_DEF(resetstatus,""), + BUILDIN_DEF(resetskill,""), + BUILDIN_DEF(skillpointcount,""), + BUILDIN_DEF(changebase,"i?"), + BUILDIN_DEF(changesex,""), + BUILDIN_DEF(waitingroom,"si?????"), + BUILDIN_DEF(delwaitingroom,"?"), + BUILDIN_DEF2(waitingroomkickall,"kickwaitingroomall","?"), + BUILDIN_DEF(enablewaitingroomevent,"?"), + BUILDIN_DEF(disablewaitingroomevent,"?"), + BUILDIN_DEF2(enablewaitingroomevent,"enablearena",""), // Added by RoVeRT + BUILDIN_DEF2(disablewaitingroomevent,"disablearena",""), // Added by RoVeRT + BUILDIN_DEF(getwaitingroomstate,"i?"), + BUILDIN_DEF(warpwaitingpc,"sii?"), + BUILDIN_DEF(attachrid,"i"), + BUILDIN_DEF(detachrid,""), + BUILDIN_DEF(isloggedin,"i?"), + BUILDIN_DEF(setmapflagnosave,"ssii"), + BUILDIN_DEF(getmapflag,"si"), + BUILDIN_DEF(setmapflag,"si?"), + BUILDIN_DEF(removemapflag,"si?"), + BUILDIN_DEF(pvpon,"s"), + BUILDIN_DEF(pvpoff,"s"), + BUILDIN_DEF(gvgon,"s"), + BUILDIN_DEF(gvgoff,"s"), + BUILDIN_DEF(emotion,"i??"), + BUILDIN_DEF(maprespawnguildid,"sii"), + BUILDIN_DEF(agitstart,""), // <Agit> + BUILDIN_DEF(agitend,""), + BUILDIN_DEF(agitcheck,""), // <Agitcheck> + BUILDIN_DEF(flagemblem,"i"), // Flag Emblem + BUILDIN_DEF(getcastlename,"s"), + BUILDIN_DEF(getcastledata,"si"), + BUILDIN_DEF(setcastledata,"sii"), + BUILDIN_DEF(requestguildinfo,"i?"), + BUILDIN_DEF(getequipcardcnt,"i"), + BUILDIN_DEF(successremovecards,"i"), + BUILDIN_DEF(failedremovecards,"ii"), + BUILDIN_DEF(marriage,"s"), + BUILDIN_DEF2(wedding_effect,"wedding",""), + BUILDIN_DEF(divorce,""), + BUILDIN_DEF(ispartneron,""), + BUILDIN_DEF(getpartnerid,""), + BUILDIN_DEF(getchildid,""), + BUILDIN_DEF(getmotherid,""), + BUILDIN_DEF(getfatherid,""), + BUILDIN_DEF(warppartner,"sii"), + BUILDIN_DEF(getitemname,"v"), + BUILDIN_DEF(getitemslots,"i"), + BUILDIN_DEF(makepet,"i"), + BUILDIN_DEF(getexp,"ii"), + BUILDIN_DEF(getinventorylist,""), + BUILDIN_DEF(getskilllist,""), + BUILDIN_DEF(clearitem,""), + BUILDIN_DEF(classchange,"ii"), + BUILDIN_DEF(misceffect,"i"), + BUILDIN_DEF(playBGM,"s"), + BUILDIN_DEF(playBGMall,"s?????"), + BUILDIN_DEF(soundeffect,"si"), + BUILDIN_DEF(soundeffectall,"si?????"), // SoundEffectAll [Codemaster] + BUILDIN_DEF(strmobinfo,"ii"), // display mob data [Valaris] + BUILDIN_DEF(guardian,"siisi??"), // summon guardians + BUILDIN_DEF(guardianinfo,"sii"), // display guardian data [Valaris] + BUILDIN_DEF(petskillbonus,"iiii"), // [Valaris] + BUILDIN_DEF(petrecovery,"ii"), // [Valaris] + BUILDIN_DEF(petloot,"i"), // [Valaris] + BUILDIN_DEF(petheal,"iiii"), // [Valaris] + BUILDIN_DEF(petskillattack,"viii"), // [Skotlex] + BUILDIN_DEF(petskillattack2,"viiii"), // [Valaris] + BUILDIN_DEF(petskillsupport,"viiii"), // [Skotlex] + BUILDIN_DEF(skilleffect,"vi"), // skill effect [Celest] + BUILDIN_DEF(npcskilleffect,"viii"), // npc skill effect [Valaris] + BUILDIN_DEF(specialeffect,"i??"), // npc skill effect [Valaris] + BUILDIN_DEF(specialeffect2,"i??"), // skill effect on players[Valaris] + BUILDIN_DEF(nude,""), // nude command [Valaris] + BUILDIN_DEF(mapwarp,"ssii??"), // Added by RoVeRT + BUILDIN_DEF(atcommand,"s"), // [MouseJstr] + BUILDIN_DEF2(atcommand,"charcommand","s"), // [MouseJstr] + BUILDIN_DEF(movenpc,"sii?"), // [MouseJstr] + BUILDIN_DEF(message,"ss"), // [MouseJstr] + BUILDIN_DEF(npctalk,"s"), // [Valaris] + BUILDIN_DEF(mobcount,"ss"), + BUILDIN_DEF(getlook,"i"), + BUILDIN_DEF(getsavepoint,"i"), + BUILDIN_DEF(npcspeed,"i"), // [Valaris] + BUILDIN_DEF(npcwalkto,"ii"), // [Valaris] + BUILDIN_DEF(npcstop,""), // [Valaris] + BUILDIN_DEF(getmapxy,"rrri?"), //by Lorky [Lupus] + BUILDIN_DEF(checkoption1,"i"), + BUILDIN_DEF(checkoption2,"i"), + BUILDIN_DEF(guildgetexp,"i"), + BUILDIN_DEF(guildchangegm,"is"), + BUILDIN_DEF(logmes,"s"), //this command actls as MES but rints info into LOG file either SQL/TXT [Lupus] + BUILDIN_DEF(summon,"si??"), // summons a slave monster [Celest] + BUILDIN_DEF(isnight,""), // check whether it is night time [Celest] + BUILDIN_DEF(isday,""), // check whether it is day time [Celest] + BUILDIN_DEF(isequipped,"i*"), // check whether another item/card has been equipped [Celest] + BUILDIN_DEF(isequippedcnt,"i*"), // check how many items/cards are being equipped [Celest] + BUILDIN_DEF(cardscnt,"i*"), // check how many items/cards are being equipped in the same arm [Lupus] + BUILDIN_DEF(getrefine,""), // returns the refined number of the current item, or an item with index specified [celest] + BUILDIN_DEF(night,""), // sets the server to night time + BUILDIN_DEF(day,""), // sets the server to day time +#ifdef PCRE_SUPPORT + BUILDIN_DEF(defpattern,"iss"), // Define pattern to listen for [MouseJstr] + BUILDIN_DEF(activatepset,"i"), // Activate a pattern set [MouseJstr] + BUILDIN_DEF(deactivatepset,"i"), // Deactive a pattern set [MouseJstr] + BUILDIN_DEF(deletepset,"i"), // Delete a pattern set [MouseJstr] +#endif + BUILDIN_DEF(dispbottom,"s"), //added from jA [Lupus] + BUILDIN_DEF(getusersname,""), + BUILDIN_DEF(recovery,""), + BUILDIN_DEF(getpetinfo,"i"), + BUILDIN_DEF(gethominfo,"i"), + BUILDIN_DEF(getmercinfo,"i?"), + BUILDIN_DEF(checkequipedcard,"i"), + BUILDIN_DEF(jump_zero,"il"), //for future jA script compatibility + BUILDIN_DEF(globalmes,"s?"), //end jA addition + BUILDIN_DEF(unequip,"i"), // unequip command [Spectre] + BUILDIN_DEF(getstrlen,"s"), //strlen [Valaris] + BUILDIN_DEF(charisalpha,"si"), //isalpha [Valaris] + BUILDIN_DEF(charat,"si"), + BUILDIN_DEF(setchar,"ssi"), + BUILDIN_DEF(insertchar,"ssi"), + BUILDIN_DEF(delchar,"si"), + BUILDIN_DEF(strtoupper,"s"), + BUILDIN_DEF(strtolower,"s"), + BUILDIN_DEF(charisupper, "si"), + BUILDIN_DEF(charislower, "si"), + BUILDIN_DEF(substr,"sii"), + BUILDIN_DEF(explode, "rss"), + BUILDIN_DEF(implode, "r?"), + BUILDIN_DEF(sprintf,"s*"), // [Mirei] + BUILDIN_DEF(sscanf,"ss*"), // [Mirei] + BUILDIN_DEF(strpos,"ss?"), + BUILDIN_DEF(replacestr,"sss??"), + BUILDIN_DEF(countstr,"ss?"), + BUILDIN_DEF(setnpcdisplay,"sv??"), + BUILDIN_DEF(compare,"ss"), // Lordalfa - To bring strstr to scripting Engine. + BUILDIN_DEF(getiteminfo,"ii"), //[Lupus] returns Items Buy / sell Price, etc info + BUILDIN_DEF(setiteminfo,"iii"), //[Lupus] set Items Buy / sell Price, etc info + BUILDIN_DEF(getequipcardid,"ii"), //[Lupus] returns CARD ID or other info from CARD slot N of equipped item + // [zBuffer] List of mathematics commands ---> + BUILDIN_DEF(sqrt,"i"), + BUILDIN_DEF(pow,"ii"), + BUILDIN_DEF(distance,"iiii"), + // <--- [zBuffer] List of mathematics commands + BUILDIN_DEF(md5,"s"), + // [zBuffer] List of dynamic var commands ---> + BUILDIN_DEF(getd,"s"), + BUILDIN_DEF(setd,"sv"), + // <--- [zBuffer] List of dynamic var commands + BUILDIN_DEF(petstat,"i"), + BUILDIN_DEF(callshop,"s?"), // [Skotlex] + BUILDIN_DEF(npcshopitem,"sii*"), // [Lance] + BUILDIN_DEF(npcshopadditem,"sii*"), + BUILDIN_DEF(npcshopdelitem,"si*"), + BUILDIN_DEF(npcshopattach,"s?"), + BUILDIN_DEF(equip,"i"), + BUILDIN_DEF(autoequip,"ii"), + BUILDIN_DEF(setbattleflag,"si"), + BUILDIN_DEF(getbattleflag,"s"), + BUILDIN_DEF(setitemscript,"is?"), //Set NEW item bonus script. Lupus + BUILDIN_DEF(disguise,"i"), //disguise player. Lupus + BUILDIN_DEF(undisguise,""), //undisguise player. Lupus + BUILDIN_DEF(getmonsterinfo,"ii"), //Lupus + BUILDIN_DEF(axtoi,"s"), + BUILDIN_DEF(query_sql,"s*"), + BUILDIN_DEF(query_logsql,"s*"), + BUILDIN_DEF(escape_sql,"v"), + BUILDIN_DEF(atoi,"s"), + // [zBuffer] List of player cont commands ---> + BUILDIN_DEF(rid2name,"i"), + BUILDIN_DEF(pcfollow,"ii"), + BUILDIN_DEF(pcstopfollow,"i"), + BUILDIN_DEF(pcblockmove,"ii"), + // <--- [zBuffer] List of player cont commands + // [zBuffer] List of mob control commands ---> + BUILDIN_DEF(unitwalk,"ii?"), + BUILDIN_DEF(unitkill,"i"), + BUILDIN_DEF(unitwarp,"isii"), + BUILDIN_DEF(unitattack,"iv?"), + BUILDIN_DEF(unitstop,"i"), + BUILDIN_DEF(unittalk,"is"), + BUILDIN_DEF(unitemote,"ii"), + BUILDIN_DEF(unitskilluseid,"ivi?"), // originally by Qamera [Celest] + BUILDIN_DEF(unitskillusepos,"iviii"), // [Celest] +// <--- [zBuffer] List of mob control commands + BUILDIN_DEF(sleep,"i"), + BUILDIN_DEF(sleep2,"i"), + BUILDIN_DEF(awake,"s"), + BUILDIN_DEF(getvariableofnpc,"rs"), + BUILDIN_DEF(warpportal,"iisii"), + BUILDIN_DEF2(homunculus_evolution,"homevolution",""), //[orn] + BUILDIN_DEF2(homunculus_mutate,"hommutate","?"), + BUILDIN_DEF2(homunculus_shuffle,"homshuffle",""), //[Zephyrus] + BUILDIN_DEF(eaclass,"?"), //[Skotlex] + BUILDIN_DEF(roclass,"i?"), //[Skotlex] + BUILDIN_DEF(checkvending,"?"), + BUILDIN_DEF(checkchatting,"?"), + BUILDIN_DEF(openmail,""), + BUILDIN_DEF(openauction,""), + BUILDIN_DEF(checkcell,"siii"), + BUILDIN_DEF(setcell,"siiiiii"), + BUILDIN_DEF(setwall,"siiiiis"), + BUILDIN_DEF(delwall,"s"), + BUILDIN_DEF(searchitem,"rs"), + BUILDIN_DEF(mercenary_create,"ii"), + BUILDIN_DEF(mercenary_heal,"ii"), + BUILDIN_DEF(mercenary_sc_start,"iii"), + BUILDIN_DEF(mercenary_get_calls,"i"), + BUILDIN_DEF(mercenary_get_faith,"i"), + BUILDIN_DEF(mercenary_set_calls,"ii"), + BUILDIN_DEF(mercenary_set_faith,"ii"), + BUILDIN_DEF(readbook,"ii"), + BUILDIN_DEF(setfont,"i"), + BUILDIN_DEF(areamobuseskill,"siiiiviiiii"), + BUILDIN_DEF(progressbar,"si"), + BUILDIN_DEF(pushpc,"ii"), + BUILDIN_DEF(buyingstore,"i"), + BUILDIN_DEF(searchstores,"ii"), + BUILDIN_DEF(showdigit,"i?"), + // WoE SE + BUILDIN_DEF(agitstart2,""), + BUILDIN_DEF(agitend2,""), + BUILDIN_DEF(agitcheck2,""), + // BattleGround + BUILDIN_DEF(waitingroom2bg,"siiss?"), + BUILDIN_DEF(waitingroom2bg_single,"isiis"), + BUILDIN_DEF(bg_team_setxy,"iii"), + BUILDIN_DEF(bg_warp,"isii"), + BUILDIN_DEF(bg_monster,"isiisi?"), + BUILDIN_DEF(bg_monster_set_team,"ii"), + BUILDIN_DEF(bg_leave,""), + BUILDIN_DEF(bg_destroy,"i"), + BUILDIN_DEF(areapercentheal,"siiiiii"), + BUILDIN_DEF(bg_get_data,"ii"), + BUILDIN_DEF(bg_getareausers,"isiiii"), + BUILDIN_DEF(bg_updatescore,"sii"), + + // Instancing + BUILDIN_DEF(instance_create,"si"), + BUILDIN_DEF(instance_destroy,"?"), + BUILDIN_DEF(instance_attachmap,"si?"), + BUILDIN_DEF(instance_detachmap,"s?"), + BUILDIN_DEF(instance_attach,"i"), + BUILDIN_DEF(instance_id,"?"), + BUILDIN_DEF(instance_set_timeout,"ii?"), + BUILDIN_DEF(instance_init,"i"), + BUILDIN_DEF(instance_announce,"isi?????"), + BUILDIN_DEF(instance_npcname,"s?"), + BUILDIN_DEF(has_instance,"s?"), + BUILDIN_DEF(instance_warpall,"sii?"), + BUILDIN_DEF(instance_check_party,"i???"), + /** + * 3rd-related + **/ + BUILDIN_DEF(makerune,"i"), + BUILDIN_DEF(checkdragon,""),//[Ind] + BUILDIN_DEF(setdragon,"?"),//[Ind] + BUILDIN_DEF(ismounting,""),//[Ind] + BUILDIN_DEF(setmounting,""),//[Ind] + BUILDIN_DEF(checkre,"i"), + /** + * rAthena and beyond! + **/ + BUILDIN_DEF(getargcount,""), + BUILDIN_DEF(getcharip,"?"), + BUILDIN_DEF(is_function,"s"), + BUILDIN_DEF(get_revision,""), + BUILDIN_DEF(freeloop,"i"), + BUILDIN_DEF(getrandgroupitem,"ii"), + BUILDIN_DEF(cleanmap,"s"), + BUILDIN_DEF2(cleanmap,"cleanarea","siiii"), + BUILDIN_DEF(npcskill,"viii"), + /** + * @commands (script based) + **/ + BUILDIN_DEF(bindatcmd, "ss??"), + BUILDIN_DEF(unbindatcmd, "s"), + BUILDIN_DEF(useatcmd, "s"), + + //Quest Log System [Inkfish] + BUILDIN_DEF(setquest, "i"), + BUILDIN_DEF(erasequest, "i"), + BUILDIN_DEF(completequest, "i"), + BUILDIN_DEF(checkquest, "i?"), + BUILDIN_DEF(changequest, "ii"), + BUILDIN_DEF(showevent, "ii"), + {NULL,NULL,NULL}, +}; diff --git a/src/map/script.h b/src/map/script.h new file mode 100644 index 000000000..ed56b8ebe --- /dev/null +++ b/src/map/script.h @@ -0,0 +1,197 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SCRIPT_H_ +#define _SCRIPT_H_ + +#define NUM_WHISPER_VAR 10 + +struct map_session_data; + +extern int potion_flag; //For use on Alchemist improved potions/Potion Pitcher. [Skotlex] +extern int potion_hp, potion_per_hp, potion_sp, potion_per_sp; +extern int potion_target; + +extern struct Script_Config { + unsigned warn_func_mismatch_argtypes : 1; + unsigned warn_func_mismatch_paramnum : 1; + int check_cmdcount; + int check_gotocount; + int input_min_value; + int input_max_value; + + const char *die_event_name; + const char *kill_pc_event_name; + const char *kill_mob_event_name; + const char *login_event_name; + const char *logout_event_name; + const char *loadmap_event_name; + const char *baselvup_event_name; + const char *joblvup_event_name; + + const char* ontouch_name; + const char* ontouch2_name; +} script_config; + +typedef enum c_op { + C_NOP, // end of script/no value (nil) + C_POS, + C_INT, // number + C_PARAM, // parameter variable (see pc_readparam/pc_setparam) + C_FUNC, // buildin function call + C_STR, // string (free'd automatically) + C_CONSTSTR, // string (not free'd) + C_ARG, // start of argument list + C_NAME, + C_EOL, // end of line (extra stack values are cleared) + C_RETINFO, + C_USERFUNC, // internal script function + C_USERFUNC_POS, // internal script function label + C_REF, // the next call to c_op2 should push back a ref to the left operand + + // operators + C_OP3, // a ? b : c + C_LOR, // a || b + C_LAND, // a && b + C_LE, // a <= b + C_LT, // a < b + C_GE, // a >= b + C_GT, // a > b + C_EQ, // a == b + C_NE, // a != b + C_XOR, // a ^ b + C_OR, // a | b + C_AND, // a & b + C_ADD, // a + b + C_SUB, // a - b + C_MUL, // a * b + C_DIV, // a / b + C_MOD, // a % b + C_NEG, // - a + C_LNOT, // ! a + C_NOT, // ~ a + C_R_SHIFT, // a >> b + C_L_SHIFT, // a << b + C_ADD_PP, // ++a + C_SUB_PP, // --a +} c_op; + +struct script_retinfo { + struct DBMap* var_function;// scope variables + struct script_code* script;// script code + int pos;// script location + int nargs;// argument count + int defsp;// default stack pointer +}; + +struct script_data { + enum c_op type; + union script_data_val { + int num; + char *str; + struct script_retinfo* ri; + } u; + struct DBMap** ref; +}; + +// Moved defsp from script_state to script_stack since +// it must be saved when script state is RERUNLINE. [Eoe / jA 1094] +struct script_code { + int script_size; + unsigned char* script_buf; + struct DBMap* script_vars; +}; + +struct script_stack { + int sp;// number of entries in the stack + int sp_max;// capacity of the stack + int defsp; + struct script_data *stack_data;// stack + struct DBMap* var_function;// scope variables +}; + + +// +// Script state +// +enum e_script_state { RUN,STOP,END,RERUNLINE,GOTO,RETFUNC }; + +struct script_state { + struct script_stack* stack; + int start,end; + int pos; + enum e_script_state state; + int rid,oid; + struct script_code *script, *scriptroot; + struct sleep_data { + int tick,timer,charid; + } sleep; + int instance_id; + //For backing up purposes + struct script_state *bk_st; + int bk_npcid; + unsigned freeloop : 1;// used by buildin_freeloop + unsigned op2ref : 1;// used by op_2 +}; + +struct script_reg { + int index; + int data; +}; + +struct script_regstr { + int index; + char* data; +}; + +enum script_parse_options { + SCRIPT_USE_LABEL_DB = 0x1,// records labels in scriptlabel_db + SCRIPT_IGNORE_EXTERNAL_BRACKETS = 0x2,// ignores the check for {} brackets around the script + SCRIPT_RETURN_EMPTY_SCRIPT = 0x4// returns the script object instead of NULL for empty scripts +}; + +const char* skip_space(const char* p); +void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos); + +struct script_code* parse_script(const char* src,const char* file,int line,int options); +void run_script_sub(struct script_code *rootscript,int pos,int rid,int oid, char* file, int lineno); +void run_script(struct script_code*,int,int,int); + +int set_var(struct map_session_data *sd, char *name, void *val); +int conv_num(struct script_state *st,struct script_data *data); +const char* conv_str(struct script_state *st,struct script_data *data); +int run_script_timer(int tid, unsigned int tick, int id, intptr_t data); +void run_script_main(struct script_state *st); + +void script_stop_sleeptimers(int id); +struct linkdb_node* script_erase_sleepdb(struct linkdb_node *n); +void script_free_code(struct script_code* code); +void script_free_vars(struct DBMap *storage); +struct script_state* script_alloc_state(struct script_code* script, int pos, int rid, int oid); +void script_free_state(struct script_state* st); + +struct DBMap* script_get_label_db(void); +struct DBMap* script_get_userfunc_db(void); +void script_run_autobonus(const char *autobonus,int id, int pos); + +bool script_get_constant(const char* name, int* value); +void script_set_constant(const char* name, int value, bool isparameter); + +void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value); +void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache); + +int script_config_read(char *cfgName); +int do_init_script(void); +int do_final_script(void); +int add_str(const char* p); +const char* get_str(int id); +int script_reload(void); + +// @commands (script based) +void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref); + +#ifdef BETA_THREAD_TEST +void queryThread_log(char * entry, int length); +#endif + +#endif /* _SCRIPT_H_ */ diff --git a/src/map/searchstore.c b/src/map/searchstore.c new file mode 100644 index 000000000..c59c13bed --- /dev/null +++ b/src/map/searchstore.c @@ -0,0 +1,405 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" // aMalloc, aRealloc, aFree +#include "../common/showmsg.h" // ShowError, ShowWarning +#include "../common/strlib.h" // safestrncpy +#include "battle.h" // battle_config.* +#include "clif.h" // clif_open_search_store_info, clif_search_store_info_* +#include "pc.h" // struct map_session_data +#include "searchstore.h" // struct s_search_store_info + + +/// failure constants for clif functions +enum e_searchstore_failure +{ + SSI_FAILED_NOTHING_SEARCH_ITEM = 0, // "No matching stores were found." + SSI_FAILED_OVER_MAXCOUNT = 1, // "There are too many results. Please enter more detailed search term." + SSI_FAILED_SEARCH_CNT = 2, // "You cannot search anymore." + SSI_FAILED_LIMIT_SEARCH_TIME = 3, // "You cannot search yet." + SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE = 4, // "No sale (purchase) information available." +}; + + +enum e_searchstore_searchtype +{ + SEARCHTYPE_VENDING = 0, + SEARCHTYPE_BUYING_STORE = 1, +}; + + +enum e_searchstore_effecttype +{ + EFFECTTYPE_NORMAL = 0, + EFFECTTYPE_CASH = 1, + EFFECTTYPE_MAX +}; + + +/// type for shop search function +typedef bool (*searchstore_search_t)(struct map_session_data* sd, unsigned short nameid); +typedef bool (*searchstore_searchall_t)(struct map_session_data* sd, const struct s_search_store_search* s); + + +/// retrieves search function by type +static searchstore_search_t searchstore_getsearchfunc(unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return &vending_search; + case SEARCHTYPE_BUYING_STORE: return &buyingstore_search; + } + return NULL; +} + + +/// retrieves search-all function by type +static searchstore_searchall_t searchstore_getsearchallfunc(unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return &vending_searchall; + case SEARCHTYPE_BUYING_STORE: return &buyingstore_searchall; + } + return NULL; +} + + +/// checks if the player has a store by type +static bool searchstore_hasstore(struct map_session_data* sd, unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return sd->state.vending; + case SEARCHTYPE_BUYING_STORE: return sd->state.buyingstore; + } + return false; +} + + +/// returns player's store id by type +static int searchstore_getstoreid(struct map_session_data* sd, unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return sd->vender_id; + case SEARCHTYPE_BUYING_STORE: return sd->buyer_id; + } + return 0; +} + + +bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect) +{ + if( !battle_config.feature_search_stores || sd->searchstore.open ) + { + return false; + } + + if( !uses || effect >= EFFECTTYPE_MAX ) + {// invalid input + return false; + } + + sd->searchstore.open = true; + sd->searchstore.uses = uses; + sd->searchstore.effect = effect; + + clif_open_search_store_info(sd); + + return true; +} + + +void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count) +{ + unsigned int i; + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + struct s_search_store_search s; + searchstore_searchall_t store_searchall; + time_t querytime; + + if( !battle_config.feature_search_stores ) + { + return; + } + + if( !sd->searchstore.open ) + { + return; + } + + if( ( store_searchall = searchstore_getsearchallfunc(type) ) == NULL ) + { + ShowError("searchstore_query: Unknown search type %u (account_id=%d).\n", (unsigned int)type, sd->bl.id); + return; + } + + time(&querytime); + + if( sd->searchstore.nextquerytime > querytime ) + { + clif_search_store_info_failed(sd, SSI_FAILED_LIMIT_SEARCH_TIME); + return; + } + + if( !sd->searchstore.uses ) + { + clif_search_store_info_failed(sd, SSI_FAILED_SEARCH_CNT); + return; + } + + // validate lists + for( i = 0; i < item_count; i++ ) + { + if( !itemdb_exists(itemlist[i]) ) + { + ShowWarning("searchstore_query: Client resolved item %hu is not known.\n", itemlist[i]); + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + return; + } + } + for( i = 0; i < card_count; i++ ) + { + if( !itemdb_exists(cardlist[i]) ) + { + ShowWarning("searchstore_query: Client resolved card %hu is not known.\n", cardlist[i]); + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + return; + } + } + + if( max_price < min_price ) + { + swap(min_price, max_price); + } + + sd->searchstore.uses--; + sd->searchstore.type = type; + sd->searchstore.nextquerytime = querytime+battle_config.searchstore_querydelay; + + // drop previous results + searchstore_clear(sd); + + // allocate max. amount of results + sd->searchstore.items = (struct s_search_store_info_item*)aMalloc(sizeof(struct s_search_store_info_item)*battle_config.searchstore_maxresults); + + // search + s.search_sd = sd; + s.itemlist = itemlist; + s.cardlist = cardlist; + s.item_count = item_count; + s.card_count = card_count; + s.min_price = min_price; + s.max_price = max_price; + iter = mapit_geteachpc(); + + for( pl_sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); pl_sd = (struct map_session_data*)mapit_next(iter) ) + { + if( sd == pl_sd ) + {// skip own shop, if any + continue; + } + + if( !store_searchall(pl_sd, &s) ) + {// exceeded result size + clif_search_store_info_failed(sd, SSI_FAILED_OVER_MAXCOUNT); + break; + } + } + + mapit_free(iter); + + if( sd->searchstore.count ) + { + // reclaim unused memory + sd->searchstore.items = (struct s_search_store_info_item*)aRealloc(sd->searchstore.items, sizeof(struct s_search_store_info_item)*sd->searchstore.count); + + // present results + clif_search_store_info_ack(sd); + + // one page displayed + sd->searchstore.pages++; + } + else + { + // cleanup + searchstore_clear(sd); + + // update uses + clif_search_store_info_ack(sd); + + // notify of failure + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + } +} + + +/// checks whether or not more results are available for the client +bool searchstore_querynext(struct map_session_data* sd) +{ + if( sd->searchstore.count && ( sd->searchstore.count-1 )/SEARCHSTORE_RESULTS_PER_PAGE < sd->searchstore.pages ) + { + return true; + } + + return false; +} + + +void searchstore_next(struct map_session_data* sd) +{ + if( !battle_config.feature_search_stores || !sd->searchstore.open || sd->searchstore.count <= sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE ) + {// nothing (more) to display + return; + } + + // present results + clif_search_store_info_ack(sd); + + // one more page displayed + sd->searchstore.pages++; +} + + +void searchstore_clear(struct map_session_data* sd) +{ + searchstore_clearremote(sd); + + if( sd->searchstore.items ) + {// release results + aFree(sd->searchstore.items); + sd->searchstore.items = NULL; + } + + sd->searchstore.count = 0; + sd->searchstore.pages = 0; +} + + +void searchstore_close(struct map_session_data* sd) +{ + if( sd->searchstore.open ) + { + searchstore_clear(sd); + + sd->searchstore.uses = 0; + sd->searchstore.open = false; + } +} + + +void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid) +{ + unsigned int i; + struct map_session_data* pl_sd; + searchstore_search_t store_search; + + if( !battle_config.feature_search_stores || !sd->searchstore.open || !sd->searchstore.count ) + { + return; + } + + searchstore_clearremote(sd); + + ARR_FIND( 0, sd->searchstore.count, i, sd->searchstore.items[i].store_id == store_id && sd->searchstore.items[i].account_id == account_id && sd->searchstore.items[i].nameid == nameid ); + if( i == sd->searchstore.count ) + {// no such result, crafted + ShowWarning("searchstore_click: Received request with item %hu of account %d, which is not part of current result set (account_id=%d, char_id=%d).\n", nameid, account_id, sd->bl.id, sd->status.char_id); + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL ) + {// no longer online + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + if( !searchstore_hasstore(pl_sd, sd->searchstore.type) || searchstore_getstoreid(pl_sd, sd->searchstore.type) != store_id ) + {// no longer vending/buying or not same shop + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + store_search = searchstore_getsearchfunc(sd->searchstore.type); + + if( !store_search(pl_sd, nameid) ) + {// item no longer being sold/bought + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + switch( sd->searchstore.effect ) + { + case EFFECTTYPE_NORMAL: + // display coords + + if( sd->bl.m != pl_sd->bl.m ) + {// not on same map, wipe previous marker + clif_search_store_info_click_ack(sd, -1, -1); + } + else + { + clif_search_store_info_click_ack(sd, pl_sd->bl.x, pl_sd->bl.y); + } + + break; + case EFFECTTYPE_CASH: + // open remotely + + // to bypass range checks + sd->searchstore.remote_id = account_id; + + switch( sd->searchstore.type ) + { + case SEARCHTYPE_VENDING: vending_vendinglistreq(sd, account_id); break; + case SEARCHTYPE_BUYING_STORE: buyingstore_open(sd, account_id); break; + } + + break; + default: + // unknown + ShowError("searchstore_click: Unknown search store effect %u (account_id=%d).\n", (unsigned int)sd->searchstore.effect, sd->bl.id); + } +} + + +/// checks whether or not sd has opened account_id's shop remotely +bool searchstore_queryremote(struct map_session_data* sd, int account_id) +{ + return (bool)( sd->searchstore.open && sd->searchstore.count && sd->searchstore.remote_id == account_id ); +} + + +/// removes range-check bypassing for remotely opened stores +void searchstore_clearremote(struct map_session_data* sd) +{ + sd->searchstore.remote_id = 0; +} + + +/// receives results from a store-specific callback +bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine) +{ + struct s_search_store_info_item* ssitem; + + if( sd->searchstore.count >= (unsigned int)battle_config.searchstore_maxresults ) + {// no more + return false; + } + + ssitem = &sd->searchstore.items[sd->searchstore.count++]; + ssitem->store_id = store_id; + ssitem->account_id = account_id; + safestrncpy(ssitem->store_name, store_name, sizeof(ssitem->store_name)); + ssitem->nameid = nameid; + ssitem->amount = amount; + ssitem->price = price; + memcpy(ssitem->card, card, sizeof(ssitem->card)); + ssitem->refine = refine; + + return true; +} diff --git a/src/map/searchstore.h b/src/map/searchstore.h new file mode 100644 index 000000000..ffa8e9784 --- /dev/null +++ b/src/map/searchstore.h @@ -0,0 +1,57 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SEARCHSTORE_H_ +#define _SEARCHSTORE_H_ + +#define SEARCHSTORE_RESULTS_PER_PAGE 10 + +/// information about the search being performed +struct s_search_store_search +{ + struct map_session_data* search_sd; // sd of the searching player + const unsigned short* itemlist; + const unsigned short* cardlist; + unsigned int item_count; + unsigned int card_count; + unsigned int min_price; + unsigned int max_price; +}; + +struct s_search_store_info_item +{ + int store_id; + int account_id; + char store_name[MESSAGE_SIZE]; + unsigned short nameid; + unsigned short amount; + unsigned int price; + short card[MAX_SLOTS]; + unsigned char refine; +}; + +struct s_search_store_info +{ + unsigned int count; + struct s_search_store_info_item* items; + unsigned int pages; // amount of pages already sent to client + unsigned int uses; + int remote_id; + time_t nextquerytime; + unsigned short effect; // 0 = Normal (display coords), 1 = Cash (remote open store) + unsigned char type; // 0 = Vending, 1 = Buying Store + bool open; +}; + +bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect); +void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count); +bool searchstore_querynext(struct map_session_data* sd); +void searchstore_next(struct map_session_data* sd); +void searchstore_clear(struct map_session_data* sd); +void searchstore_close(struct map_session_data* sd); +void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid); +bool searchstore_queryremote(struct map_session_data* sd, int account_id); +void searchstore_clearremote(struct map_session_data* sd); +bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine); + +#endif // _SEARCHSTORE_H_ diff --git a/src/map/skill.c b/src/map/skill.c new file mode 100644 index 000000000..757165107 --- /dev/null +++ b/src/map/skill.c @@ -0,0 +1,17983 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" + +#include "map.h" +#include "path.h" +#include "clif.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "pet.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "mob.h" +#include "npc.h" +#include "battle.h" +#include "battleground.h" +#include "party.h" +#include "itemdb.h" +#include "script.h" +#include "intif.h" +#include "log.h" +#include "chrif.h" +#include "guild.h" +#include "date.h" +#include "unit.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> + + +#define SKILLUNITTIMER_INTERVAL 100 + +// ranges reserved for mapping skill ids to skilldb offsets +#define HM_SKILLRANGEMIN 700 +#define HM_SKILLRANGEMAX HM_SKILLRANGEMIN + MAX_HOMUNSKILL +#define MC_SKILLRANGEMIN HM_SKILLRANGEMAX + 1 +#define MC_SKILLRANGEMAX MC_SKILLRANGEMIN + MAX_MERCSKILL +#define EL_SKILLRANGEMIN MC_SKILLRANGEMAX + 1 +#define EL_SKILLRANGEMAX EL_SKILLRANGEMIN + MAX_ELEMENTALSKILL +#define GD_SKILLRANGEMIN EL_SKILLRANGEMAX + 1 +#define GD_SKILLRANGEMAX GD_SKILLRANGEMIN + MAX_GUILDSKILL + +#if GD_SKILLRANGEMAX > 999 + #error GD_SKILLRANGEMAX is greater than 999 +#endif +static struct eri *skill_unit_ers = NULL; //For handling skill_unit's [Skotlex] +static struct eri *skill_timer_ers = NULL; //For handling skill_timerskills [Skotlex] + +DBMap* skillunit_db = NULL; // int id -> struct skill_unit* + +DBMap* skilldb_name2id = NULL; + +/** + * Skill Cool Down Delay Saving + * Struct skill_cd is not a member of struct map_session_data + * to keep cooldowns in memory between player log-ins. + * All cooldowns are reset when server is restarted. + **/ +DBMap* skillcd_db = NULL; // char_id -> struct skill_cd +struct skill_cd { + int duration[MAX_SKILL_TREE];//milliseconds + short skidx[MAX_SKILL_TREE];//the skill index entries belong to + short nameid[MAX_SKILL_TREE];//skill id + unsigned char cursor; +}; + +/** + * Skill Unit Persistency during endack routes (mostly for songs see bugreport:4574) + **/ +DBMap* skillusave_db = NULL; // char_id -> struct skill_usave +struct skill_usave { + uint16 skill_id, skill_lv; +}; + +struct s_skill_db skill_db[MAX_SKILL_DB]; +struct s_skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; +struct s_skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; +struct s_skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; +struct s_skill_improvise_db { + uint16 skill_id; + short per;//1-10000 +}; +struct s_skill_improvise_db skill_improvise_db[MAX_SKILL_IMPROVISE_DB]; +bool skill_reproduce_db[MAX_SKILL_DB]; +struct s_skill_changematerial_db { + int itemid; + short rate; + int qty[5]; + short qty_rate[5]; +}; +struct s_skill_changematerial_db skill_changematerial_db[MAX_SKILL_PRODUCE_DB]; + +//Warlock +struct s_skill_spellbook_db { + int nameid; + uint16 skill_id; + int point; +}; + +struct s_skill_spellbook_db skill_spellbook_db[MAX_SKILL_SPELLBOOK_DB]; +//Guillotine Cross +struct s_skill_magicmushroom_db skill_magicmushroom_db[MAX_SKILL_MAGICMUSHROOM_DB]; + +struct s_skill_unit_layout skill_unit_layout[MAX_SKILL_UNIT_LAYOUT]; +int firewall_unit_pos; +int icewall_unit_pos; +int earthstrain_unit_pos; +//early declaration +int skill_block_check(struct block_list *bl, enum sc_type type, uint16 skill_id); +static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); +static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); +static int skill_destroy_trap( struct block_list *bl, va_list ap ); +//Since only mob-casted splash skills can hit ice-walls +static inline int splash_target(struct block_list* bl) +{ +#ifndef RENEWAL + return ( bl->type == BL_MOB ) ? BL_SKILL|BL_CHAR : BL_CHAR; +#else // Some skills can now hit ground skills(traps, ice wall & etc.) + return BL_SKILL|BL_CHAR; +#endif +} + +/// Returns the id of the skill, or 0 if not found. +int skill_name2id(const char* name) +{ + if( name == NULL ) + return 0; + + return strdb_iget(skilldb_name2id, name); +} + +/// Maps skill ids to skill db offsets. +/// Returns the skill's array index, or 0 (Unknown Skill). +int skill_get_index( uint16 skill_id ) +{ + // avoid ranges reserved for mapping guild/homun/mercenary skills + if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) + || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) + || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) + || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) + return 0; + + // map skill id to skill db index + if( skill_id >= GD_SKILLBASE ) + skill_id = GD_SKILLRANGEMIN + skill_id - GD_SKILLBASE; + else if( skill_id >= EL_SKILLBASE ) + skill_id = EL_SKILLRANGEMIN + skill_id - EL_SKILLBASE; + else if( skill_id >= MC_SKILLBASE ) + skill_id = MC_SKILLRANGEMIN + skill_id - MC_SKILLBASE; + else if( skill_id >= HM_SKILLBASE ) + skill_id = HM_SKILLRANGEMIN + skill_id - HM_SKILLBASE; + + // validate result + if( !skill_id || skill_id >= MAX_SKILL_DB ) + return 0; + + return skill_id; +} + +const char* skill_get_name( uint16 skill_id ) +{ + return skill_db[skill_get_index(skill_id)].name; +} + +const char* skill_get_desc( uint16 skill_id ) +{ + return skill_db[skill_get_index(skill_id)].desc; +} + +// out of bounds error checking [celest] +static void skill_chk(int16* skill_id, uint16 skill_lv) +{ + *skill_id = skill_get_index(*skill_id); // checks/adjusts id + if( skill_lv > MAX_SKILL_LEVEL ) *skill_id = 0; +} + +#define skill_get(var,id,lv) { skill_chk(&id,lv); if(!id) return 0; return var; } + +// Skill DB +int skill_get_hit( uint16 skill_id ) { skill_get (skill_db[skill_id].hit, skill_id, 1); } +int skill_get_inf( uint16 skill_id ) { skill_get (skill_db[skill_id].inf, skill_id, 1); } +int skill_get_ele( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].element[skill_lv-1], skill_id, skill_lv); } +int skill_get_nk( uint16 skill_id ) { skill_get (skill_db[skill_id].nk, skill_id, 1); } +int skill_get_max( uint16 skill_id ) { skill_get (skill_db[skill_id].max, skill_id, 1); } +int skill_get_range( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].range[skill_lv-1], skill_id, skill_lv); } +int skill_get_splash( uint16 skill_id , uint16 skill_lv ) { skill_get ( (skill_db[skill_id].splash[skill_lv-1]>=0?skill_db[skill_id].splash[skill_lv-1]:AREA_SIZE), skill_id, skill_lv); } +int skill_get_hp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].hp[skill_lv-1], skill_id, skill_lv); } +int skill_get_sp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].sp[skill_lv-1], skill_id, skill_lv); } +int skill_get_hp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].hp_rate[skill_lv-1], skill_id, skill_lv); } +int skill_get_sp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].sp_rate[skill_lv-1], skill_id, skill_lv); } +int skill_get_state(uint16 skill_id) { skill_get (skill_db[skill_id].state, skill_id, 1); } +int skill_get_spiritball(uint16 skill_id, uint16 skill_lv) { skill_get (skill_db[skill_id].spiritball[skill_lv-1], skill_id, skill_lv); } +int skill_get_itemid(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].itemid[idx], skill_id, 1); } +int skill_get_itemqty(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].amount[idx], skill_id, 1); } +int skill_get_zeny( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].zeny[skill_lv-1], skill_id, skill_lv); } +int skill_get_num( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].num[skill_lv-1], skill_id, skill_lv); } +int skill_get_cast( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].cast[skill_lv-1], skill_id, skill_lv); } +int skill_get_delay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].delay[skill_lv-1], skill_id, skill_lv); } +int skill_get_walkdelay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].walkdelay[skill_lv-1], skill_id, skill_lv); } +int skill_get_time( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time[skill_lv-1], skill_id, skill_lv); } +int skill_get_time2( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time2[skill_lv-1], skill_id, skill_lv); } +int skill_get_castdef( uint16 skill_id ) { skill_get (skill_db[skill_id].cast_def_rate, skill_id, 1); } +int skill_get_weapontype( uint16 skill_id ) { skill_get (skill_db[skill_id].weapon, skill_id, 1); } +int skill_get_ammotype( uint16 skill_id ) { skill_get (skill_db[skill_id].ammo, skill_id, 1); } +int skill_get_ammo_qty( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].ammo_qty[skill_lv-1], skill_id, skill_lv); } +int skill_get_inf2( uint16 skill_id ) { skill_get (skill_db[skill_id].inf2, skill_id, 1); } +int skill_get_castcancel( uint16 skill_id ) { skill_get (skill_db[skill_id].castcancel, skill_id, 1); } +int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].maxcount[skill_lv-1], skill_id, skill_lv); } +int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].blewcount[skill_lv-1], skill_id, skill_lv); } +int skill_get_mhp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].mhp[skill_lv-1], skill_id, skill_lv); } +int skill_get_castnodex( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].castnodex[skill_lv-1], skill_id, skill_lv); } +int skill_get_delaynodex( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].delaynodex[skill_lv-1], skill_id, skill_lv); } +int skill_get_nocast ( uint16 skill_id ) { skill_get (skill_db[skill_id].nocast, skill_id, 1); } +int skill_get_type( uint16 skill_id ) { skill_get (skill_db[skill_id].skill_type, skill_id, 1); } +int skill_get_unit_id ( uint16 skill_id, int flag ){ skill_get (skill_db[skill_id].unit_id[flag], skill_id, 1); } +int skill_get_unit_interval( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_interval, skill_id, 1); } +int skill_get_unit_range( uint16 skill_id, uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_range[skill_lv-1], skill_id, skill_lv); } +int skill_get_unit_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BCT_ALL, skill_id, 1); } +int skill_get_unit_bl_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BL_ALL, skill_id, 1); } +int skill_get_unit_flag( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_flag, skill_id, 1); } +int skill_get_unit_layout_type( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_layout_type[skill_lv-1], skill_id, skill_lv); } +int skill_get_cooldown( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].cooldown[skill_lv-1], skill_id, skill_lv); } +#ifdef RENEWAL_CAST +int skill_get_fixed_cast( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].fixed_cast[skill_lv-1], skill_id, skill_lv); } +#endif +int skill_tree_get_max(uint16 skill_id, int b_class) +{ + int i; + b_class = pc_class2idx(b_class); + + ARR_FIND( 0, MAX_SKILL_TREE, i, skill_tree[b_class][i].id == 0 || skill_tree[b_class][i].id == skill_id ); + if( i < MAX_SKILL_TREE && skill_tree[b_class][i].id == skill_id ) + return skill_tree[b_class][i].max; + else + return skill_get_max(skill_id); +} + +int skill_frostjoke_scream(struct block_list *bl,va_list ap); +int skill_attack_area(struct block_list *bl,va_list ap); +struct skill_unit_group *skill_locate_element_field(struct block_list *bl); // [Skotlex] +int skill_graffitiremover(struct block_list *bl, va_list ap); // [Valaris] +int skill_greed(struct block_list *bl, va_list ap); +static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id); +static int skill_cell_overlap(struct block_list *bl, va_list ap); +static int skill_trap_splash(struct block_list *bl, va_list ap); +struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,struct skill_unit_group *sg,int tick); +static int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,unsigned int tick); +static int skill_unit_onleft(uint16 skill_id, struct block_list *bl,unsigned int tick); +static int skill_unit_effect(struct block_list *bl,va_list ap); + +int enchant_eff[5] = { 10, 14, 17, 19, 20 }; +int deluge_eff[5] = { 5, 9, 12, 14, 15 }; + +int skill_get_casttype (uint16 skill_id) +{ + int inf = skill_get_inf(skill_id); + if (inf&(INF_GROUND_SKILL)) + return CAST_GROUND; + if (inf&INF_SUPPORT_SKILL) + return CAST_NODAMAGE; + if (inf&INF_SELF_SKILL) { + if(skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF) + return CAST_DAMAGE; //Combo skill. + return CAST_NODAMAGE; + } + if (skill_get_nk(skill_id)&NK_NO_DAMAGE) + return CAST_NODAMAGE; + return CAST_DAMAGE; +} + +//Returns actual skill range taking into account attack range and AC_OWL [Skotlex] +int skill_get_range2 (struct block_list *bl, uint16 skill_id, uint16 skill_lv) +{ + int range; + if( bl->type == BL_MOB && battle_config.mob_ai&0x400 ) + return 9; //Mobs have a range of 9 regardless of skill used. + + range = skill_get_range(skill_id, skill_lv); + + if( range < 0 ) + { + if( battle_config.use_weapon_skill_range&bl->type ) + return status_get_range(bl); + range *=-1; + } + + //TODO: Find a way better than hardcoding the list of skills affected by AC_VULTURE + switch( skill_id ) + { + case AC_SHOWER: case MA_SHOWER: + case AC_DOUBLE: case MA_DOUBLE: + case HT_BLITZBEAT: + case AC_CHARGEARROW: + case MA_CHARGEARROW: + case SN_FALCONASSAULT: + case HT_POWER: + /** + * Ranger + **/ + case RA_ARROWSTORM: + case RA_AIMEDBOLT: + case RA_WUGBITE: + if( bl->type == BL_PC ) + range += pc_checkskill((TBL_PC*)bl, AC_VULTURE); + else + range += 10; //Assume level 10? + break; + // added to allow GS skills to be effected by the range of Snake Eyes [Reddozen] + case GS_RAPIDSHOWER: + case GS_PIERCINGSHOT: + case GS_FULLBUSTER: + case GS_SPREADATTACK: + case GS_GROUNDDRIFT: + if (bl->type == BL_PC) + range += pc_checkskill((TBL_PC*)bl, GS_SNAKEEYE); + else + range += 10; //Assume level 10? + break; + case NJ_KIRIKAGE: + if (bl->type == BL_PC) + range = skill_get_range(NJ_SHADOWJUMP,pc_checkskill((TBL_PC*)bl,NJ_SHADOWJUMP)); + break; + /** + * Warlock + **/ + case WL_WHITEIMPRISON: + case WL_SOULEXPANSION: + case WL_FROSTMISTY: + case WL_MARSHOFABYSS: + case WL_SIENNAEXECRATE: + case WL_DRAINLIFE: + case WL_CRIMSONROCK: + case WL_HELLINFERNO: + case WL_COMET: + case WL_CHAINLIGHTNING: + case WL_TETRAVORTEX: + case WL_RELEASE: + if( bl->type == BL_PC ) + range += pc_checkskill((TBL_PC*)bl, WL_RADIUS); + break; + /** + * Ranger Bonus + **/ + case HT_LANDMINE: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + if( bl->type == BL_PC ) + range += (1 + pc_checkskill((TBL_PC*)bl, RA_RESEARCHTRAP))/2; + } + + if( !range && bl->type != BL_PC ) + return 9; // Enable non players to use self skills on others. [Skotlex] + return range; +} + +int skill_calc_heal(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal) { + int skill, hp; + struct map_session_data *sd = BL_CAST(BL_PC, src); + struct map_session_data *tsd = BL_CAST(BL_PC, target); + struct status_change* sc; + + switch( skill_id ) { + case BA_APPLEIDUN: + #ifdef RENEWAL + hp = 100+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery + #else + hp = 30+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery + #endif + if( sd ) + hp += 5*pc_checkskill(sd,BA_MUSICALLESSON); + break; + case PR_SANCTUARY: + hp = (skill_lv>6)?777:skill_lv*100; + break; + case NPC_EVILLAND: + hp = (skill_lv>6)?666:skill_lv*100; + break; + default: + if (skill_lv >= battle_config.max_heal_lv) + return battle_config.max_heal; + #ifdef RENEWAL + /** + * Renewal Heal Formula + * Formula: ( [(Base Level + INT) / 5] テ 30 ) テ (Heal Level / 10) テ (Modifiers) + MATK + **/ + hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30 * skill_lv / 10; + #else + hp = ( status_get_lv(src) + status_get_int(src) ) / 8 * (4 + ( skill_id == AB_HIGHNESSHEAL ? ( sd ? pc_checkskill(sd,AL_HEAL) : 10 ) : skill_lv ) * 8); + #endif + if( sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0) ) + hp += hp * skill * 2 / 100; + else if( src->type == BL_HOM && (skill = merc_hom_checkskill(((TBL_HOM*)src), HLIF_BRAIN)) > 0 ) + hp += hp * skill * 2 / 100; + break; + } + + if( ( (target && target->type == BL_MER) || !heal ) && skill_id != NPC_EVILLAND ) + hp >>= 1; + + if( sd && (skill = pc_skillheal_bonus(sd, skill_id)) ) + hp += hp*skill/100; + + if( tsd && (skill = pc_skillheal2_bonus(tsd, skill_id)) ) + hp += hp*skill/100; + + sc = status_get_sc(target); + if( sc && sc->count ) { + if( sc->data[SC_CRITICALWOUND] && heal ) // Critical Wound has no effect on offensive heal. [Inkfish] + hp -= hp * sc->data[SC_CRITICALWOUND]->val2/100; + if( sc->data[SC_DEATHHURT] && heal ) + hp -= hp * 20/100; + if( sc->data[SC_INCHEALRATE] && skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN ) + hp += hp * sc->data[SC_INCHEALRATE]->val1/100; // Only affects Heal, Sanctuary and PotionPitcher.(like bHealPower) [Inkfish] + if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) + hp += hp / 10; + } + +#ifdef RENEWAL + // MATK part of the RE heal formula [malufett] + // Note: in this part matk bonuses from items or skills are not applied + switch( skill_id ) { + case BA_APPLEIDUN: case PR_SANCTUARY: + case NPC_EVILLAND: break; + default: + { + struct status_data *status = status_get_status_data(src); + int min, max, wMatk, variance; + + min = max = status_base_matk(status, status_get_lv(src)); + if( status->rhw.matk > 0 ){ + wMatk = status->rhw.matk; + variance = wMatk * status->rhw.wlv / 10; + min += wMatk - variance; + max += wMatk + variance; + } + + if( sc && sc->data[SC_RECOGNIZEDSPELL] ) + min = max; + + if( sd && sd->right_weapon.overrefine > 0 ){ + min++; + max += sd->right_weapon.overrefine - 1; + } + + if(max > min) + hp += min+rnd()%(max-min); + else + hp += min; + } + } +#endif + return hp; +} + +// Making plagiarize check its own function [Aru] +int can_copy (struct map_session_data *sd, uint16 skill_id, struct block_list* bl) +{ + // Never copy NPC/Wedding Skills + if (skill_get_inf2(skill_id)&(INF2_NPC_SKILL|INF2_WEDDING_SKILL)) + return 0; + + // High-class skills + if((skill_id >= LK_AURABLADE && skill_id <= ASC_CDP) || (skill_id >= ST_PRESERVE && skill_id <= CR_CULTIVATION)) + { + if(battle_config.copyskill_restrict == 2) + return 0; + else if(battle_config.copyskill_restrict) + return (sd->status.class_ == JOB_STALKER); + } + + //Added so plagarize can't copy agi/bless if you're undead since it damages you + if ((skill_id == AL_INCAGI || skill_id == AL_BLESSING || + skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || + skill_id == MER_INCAGI || skill_id == MER_BLESSING)) + return 0; + + // Couldn't preserve 3rd Class skills except only when using Reproduce skill. [Jobbie] + if( !(sd->sc.data[SC__REPRODUCE]) && (skill_id >= RK_ENCHANTBLADE && skill_id <= SR_RIDEINLIGHTNING) ) + return 0; + // Reproduce will only copy skills according on the list. [Jobbie] + else if( sd->sc.data[SC__REPRODUCE] && !skill_reproduce_db[skill_id] ) + return 0; + + return 1; +} + +// [MouseJstr] - skill ok to cast? and when? +int skillnotok (uint16 skill_id, struct map_session_data *sd) +{ + int16 idx,m; + nullpo_retr (1, sd); + m = sd->bl.m; + idx = skill_get_index(skill_id); + + if (idx == 0) + return 1; // invalid skill id + + if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) + return 0; // can do any damn thing they want + + if( skill_id == AL_TELEPORT && sd->skillitem == skill_id && sd->skillitemlv > 2 ) + return 0; // Teleport lv 3 bypasses this check.[Inkfish] + + // Epoque: + // This code will compare the player's attack motion value which is influenced by ASPD before + // allowing a skill to be cast. This is to prevent no-delay ACT files from spamming skills such as + // AC_DOUBLE which do not have a skill delay and are not regarded in terms of attack motion. + if( !sd->state.autocast && sd->skillitem != skill_id && sd->canskill_tick && + DIFF_TICK(gettick(), sd->canskill_tick) < (sd->battle_status.amotion * (battle_config.skill_amotion_leniency) / 100) ) + {// attempted to cast a skill before the attack motion has finished + return 1; + } + + if (sd->blockskill[idx] > 0){ + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return 1; + } + /** + * It has been confirmed on a official server (thanks to Yommy) that item-cast skills bypass all the restrictions above + * Also, without this check, an exploit where an item casting + healing (or any other kind buff) isn't deleted after used on a restricted map + **/ + if( sd->skillitem == skill_id ) + return 0; + // Check skill restrictions [Celest] + if( (!map_flag_vs(m) && skill_get_nocast (skill_id) & 1) || + (map[m].flag.pvp && skill_get_nocast (skill_id) & 2) || + (map_flag_gvg(m) && skill_get_nocast (skill_id) & 4) || + (map[m].flag.battleground && skill_get_nocast (skill_id) & 8) || + (map[m].flag.restricted && map[m].zone && skill_get_nocast (skill_id) & (8*map[m].zone)) ){ + clif_msg(sd, 0x536); // This skill cannot be used within this area + return 1; + } + + if( sd->sc.option&OPTION_MOUNTING ) + return 1;//You can't use skills while in the new mounts (The client doesn't let you, this is to make cheat-safe) + + switch (skill_id) { + case AL_WARP: + case RETURN_TO_ELDICASTES: + case ALL_GUARDIAN_RECALL: + if(map[m].flag.nowarp) { + clif_skill_teleportmessage(sd,0); + return 1; + } + return 0; + case AL_TELEPORT: + case SC_FATALMENACE: + case SC_DIMENSIONDOOR: + if(map[m].flag.noteleport) { + clif_skill_teleportmessage(sd,0); + return 1; + } + return 0; // gonna be checked in 'skill_castend_nodamage_id' + case WE_CALLPARTNER: + case WE_CALLPARENT: + case WE_CALLBABY: + if (map[m].flag.nomemo) { + clif_skill_teleportmessage(sd,1); + return 1; + } + break; + case MC_VENDING: + case MC_IDENTIFY: + case ALL_BUYING_STORE: + return 0; // always allowed + case WZ_ICEWALL: + // noicewall flag [Valaris] + if (map[m].flag.noicewall) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case GC_DARKILLUSION: + if( map_flag_gvg(m) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case GD_EMERGENCYCALL: + if ( + !(battle_config.emergency_call&((agit_flag || agit2_flag)?2:1)) || + !(battle_config.emergency_call&(map[m].flag.gvg || map[m].flag.gvg_castle?8:4)) || + (battle_config.emergency_call&16 && map[m].flag.nowarpto && !map[m].flag.gvg_castle) + ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case BS_GREED: + case WS_CARTBOOST: + case BS_HAMMERFALL: + case BS_ADRENALINE: + case MC_CARTREVOLUTION: + case MC_MAMMONITE: + case WS_MELTDOWN: + case MG_SIGHT: + case TF_HIDING: + /** + * These skills cannot be used while in mado gear (credits to Xantara) + **/ + if( pc_ismadogear(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + + case WM_SIRCLEOFNATURE: + case WM_SOUND_OF_DESTRUCTION: + case SC_MANHOLE: + case WM_LULLABY_DEEPSLEEP: + case WM_SATURDAY_NIGHT_FEVER: + if( !map_flag_vs(m) ) { + clif_skill_teleportmessage(sd,2); // This skill uses this msg instead of skill fails. + return 1; + } + break; + + } + return (map[m].flag.noskill); +} + +int skillnotok_hom(uint16 skill_id, struct homun_data *hd) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr(1,hd); + + if (idx == 0) + return 1; // invalid skill id + + if (hd->blockskill[idx] > 0) + return 1; + switch(skill_id){ + case MH_LIGHT_OF_REGENE: + if(hd->homunculus.intimacy <= 750) //if not cordial + return 1; + break; + case MH_OVERED_BOOST: + if(hd->homunculus.hunger <= 1) //if we starving + return 1; + case MH_GOLDENE_FERSE: //can be used with angriff + if(hd->sc.data[SC_ANGRIFFS_MODUS]) + return 1; + case MH_ANGRIFFS_MODUS: + if(hd->sc.data[SC_GOLDENE_FERSE]) + return 1; + break; + } + + //Use master's criteria. + return skillnotok(skill_id, hd->master); +} + +int skillnotok_mercenary(uint16 skill_id, struct mercenary_data *md) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr(1,md); + + if( idx == 0 ) + return 1; // Invalid Skill ID + if( md->blockskill[idx] > 0 ) + return 1; + + return skillnotok(skill_id, md->master); +} + +struct s_skill_unit_layout* skill_get_unit_layout (uint16 skill_id, uint16 skill_lv, struct block_list* src, int x, int y) +{ + int pos = skill_get_unit_layout_type(skill_id,skill_lv); + uint8 dir; + + if (pos < -1 || pos >= MAX_SKILL_UNIT_LAYOUT) { + ShowError("skill_get_unit_layout: unsupported layout type %d for skill %d (level %d)\n", pos, skill_id, skill_lv); + pos = cap_value(pos, 0, MAX_SQUARE_LAYOUT); // cap to nearest square layout + } + + if (pos != -1) // simple single-definition layout + return &skill_unit_layout[pos]; + + dir = (src->x == x && src->y == y) ? 6 : map_calc_dir(src,x,y); // 6 - default aegis direction + + if (skill_id == MG_FIREWALL) + return &skill_unit_layout [firewall_unit_pos + dir]; + else if (skill_id == WZ_ICEWALL) + return &skill_unit_layout [icewall_unit_pos + dir]; + else if( skill_id == WL_EARTHSTRAIN ) //Warlock + return &skill_unit_layout [earthstrain_unit_pos + dir]; + + ShowError("skill_get_unit_layout: unknown unit layout for skill %d (level %d)\n", skill_id, skill_lv); + return &skill_unit_layout[0]; // default 1x1 layout +} + +/*========================================== + * + *------------------------------------------*/ +int skill_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, int dmg_lv, unsigned int tick) +{ + struct map_session_data *sd, *dstsd; + struct mob_data *md, *dstmd; + struct status_data *sstatus, *tstatus; + struct status_change *sc, *tsc; + + enum sc_type status; + int skill; + int rate; + + nullpo_ret(src); + nullpo_ret(bl); + + if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest + + if( dmg_lv < ATK_BLOCK ) // Don't apply effect if miss. + return 0; + + sd = BL_CAST(BL_PC, src); + md = BL_CAST(BL_MOB, src); + dstsd = BL_CAST(BL_PC, bl); + dstmd = BL_CAST(BL_MOB, bl); + + sc = status_get_sc(src); + tsc = status_get_sc(bl); + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(bl); + if (!tsc) //skill additional effect is about adding effects to the target... + //So if the target can't be inflicted with statuses, this is pointless. + return 0; + + if( sd ) + { // These statuses would be applied anyway even if the damage was blocked by some skills. [Inkfish] + if( skill_id != WS_CARTTERMINATION && skill_id != AM_DEMONSTRATION && skill_id != CR_REFLECTSHIELD && skill_id != MS_REFLECTSHIELD && skill_id != ASC_BREAKER ) + { // Trigger status effects + enum sc_type type; + int i; + for( i = 0; i < ARRAYLENGTH(sd->addeff) && sd->addeff[i].flag; i++ ) + { + rate = sd->addeff[i].rate; + if( attack_type&BF_LONG ) // Any ranged physical attack takes status arrows into account (Grimtooth...) [DracoRPG] + rate += sd->addeff[i].arrow_rate; + if( !rate ) continue; + + if( (sd->addeff[i].flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC)) != (ATF_WEAPON|ATF_MAGIC|ATF_MISC) ) + { // Trigger has attack type consideration. + if( (sd->addeff[i].flag&ATF_WEAPON && attack_type&BF_WEAPON) || + (sd->addeff[i].flag&ATF_MAGIC && attack_type&BF_MAGIC) || + (sd->addeff[i].flag&ATF_MISC && attack_type&BF_MISC) ) ; + else + continue; + } + + if( (sd->addeff[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT) ) + { // Trigger has range consideration. + if((sd->addeff[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || + (sd->addeff[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) + continue; //Range Failed. + } + + type = sd->addeff[i].id; + skill = skill_get_time2(status_sc2skill(type),7); + + if (sd->addeff[i].flag&ATF_TARGET) + status_change_start(bl,type,rate,7,0,0,0,skill,0); + + if (sd->addeff[i].flag&ATF_SELF) + status_change_start(src,type,rate,7,0,0,0,skill,0); + } + } + + if( skill_id ) + { // Trigger status effects on skills + enum sc_type type; + int i; + for( i = 0; i < ARRAYLENGTH(sd->addeff3) && sd->addeff3[i].skill; i++ ) + { + if( skill_id != sd->addeff3[i].skill || !sd->addeff3[i].rate ) + continue; + type = sd->addeff3[i].id; + skill = skill_get_time2(status_sc2skill(type),7); + + if( sd->addeff3[i].target&ATF_TARGET ) + status_change_start(bl,type,sd->addeff3[i].rate,7,0,0,0,skill,0); + if( sd->addeff3[i].target&ATF_SELF ) + status_change_start(src,type,sd->addeff3[i].rate,7,0,0,0,skill,0); + } + } + } + + if( dmg_lv < ATK_DEF ) // no damage, return; + return 0; + + switch(skill_id) + { + case 0: // Normal attacks (no skill used) + { + if( attack_type&BF_SKILL ) + break; // If a normal attack is a skill, it's splash damage. [Inkfish] + if(sd) { + // Automatic trigger of Blitz Beat + if (pc_isfalcon(sd) && sd->status.weapon == W_BOW && (skill=pc_checkskill(sd,HT_BLITZBEAT))>0 && + rnd()%1000 <= sstatus->luk*10/3+1 ) { + rate=(sd->status.job_level+9)/10; + skill_castend_damage_id(src,bl,HT_BLITZBEAT,(skill<rate)?skill:rate,tick,SD_LEVEL); + } + // Automatic trigger of Warg Strike [Jobbie] + if( pc_iswug(sd) && (sd->status.weapon == W_BOW || sd->status.weapon == W_FIST) && (skill=pc_checkskill(sd,RA_WUGSTRIKE)) > 0 && rnd()%1000 <= sstatus->luk*10/3+1 ) + skill_castend_damage_id(src,bl,RA_WUGSTRIKE,skill,tick,0); + // Gank + if(dstmd && sd->status.weapon != W_BOW && + (skill=pc_checkskill(sd,RG_SNATCHER)) > 0 && + (skill*15 + 55) + pc_checkskill(sd,TF_STEAL)*10 > rnd()%1000) { + if(pc_steal_item(sd,bl,pc_checkskill(sd,TF_STEAL))) + clif_skill_nodamage(src,bl,TF_STEAL,skill,1); + else + clif_skill_fail(sd,RG_SNATCHER,USESKILL_FAIL_LEVEL,0); + } + // Chance to trigger Taekwon kicks [Dralnu] + if(sc && !sc->data[SC_COMBO]) { + if(sc->data[SC_READYSTORM] && + sc_start(src,SC_COMBO, 15, TK_STORMKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if(sc->data[SC_READYDOWN] && + sc_start(src,SC_COMBO, 15, TK_DOWNKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if(sc->data[SC_READYTURN] && + sc_start(src,SC_COMBO, 15, TK_TURNKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if (sc->data[SC_READYCOUNTER]) { //additional chance from SG_FRIEND [Komurka] + rate = 20; + if (sc->data[SC_SKILLRATE_UP] && sc->data[SC_SKILLRATE_UP]->val1 == TK_COUNTER) { + rate += rate*sc->data[SC_SKILLRATE_UP]->val2/100; + status_change_end(src, SC_SKILLRATE_UP, INVALID_TIMER); + } + sc_start2(src, SC_COMBO, rate, TK_COUNTER, bl->id, + (2000 - 4*sstatus->agi - 2*sstatus->dex)); + } + } + if(sc && sc->data[SC_PYROCLASTIC] && (rnd() % 1000 <= sstatus->luk * 10 / 3 + 1) ) + skill_castend_pos2(src, bl->x, bl->y, BS_HAMMERFALL,sc->data[SC_PYROCLASTIC]->val1, tick, 0); + } + + if (sc) { + struct status_change_entry *sce; + // Enchant Poison gives a chance to poison attacked enemies + if((sce=sc->data[SC_ENCPOISON])) //Don't use sc_start since chance comes in 1/10000 rate. + status_change_start(bl,SC_POISON,sce->val2, sce->val1,src->id,0,0, + skill_get_time2(AS_ENCHANTPOISON,sce->val1),0); + // Enchant Deadly Poison gives a chance to deadly poison attacked enemies + if((sce=sc->data[SC_EDP])) + sc_start4(bl,SC_DPOISON,sce->val2, sce->val1,src->id,0,0, + skill_get_time2(ASC_EDP,sce->val1)); + } + } + break; + + case SM_BASH: + if( sd && skill_lv > 5 && pc_checkskill(sd,SM_FATALBLOW)>0 ){ + //TODO: How much % per base level it actually is? + sc_start(bl,SC_STUN,(5*(skill_lv-5)+(int)sd->status.base_level/10), + skill_lv,skill_get_time2(SM_FATALBLOW,skill_lv)); + } + break; + + case MER_CRASH: + sc_start(bl,SC_STUN,(6*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case AS_VENOMKNIFE: + if (sd) //Poison chance must be that of Envenom. [Skotlex] + skill_lv = pc_checkskill(sd, TF_POISON); + case TF_POISON: + case AS_SPLASHER: + if(!sc_start2(bl,SC_POISON,(4*skill_lv+10),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)) + && sd && skill_id==TF_POISON + ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case AS_SONICBLOW: + sc_start(bl,SC_STUN,(2*skill_lv+10),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WZ_FIREPILLAR: + unit_set_walkdelay(bl, tick, skill_get_time2(skill_id, skill_lv), 1); + break; + + case MG_FROSTDIVER: +#ifndef RENEWAL + case WZ_FROSTNOVA: +#endif + sc_start(bl,SC_FREEZE,skill_lv*3+35,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + +#ifdef RENEWAL + case WZ_FROSTNOVA: + sc_start(bl,SC_FREEZE,skill_lv*5+33,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; +#endif + + case WZ_STORMGUST: + /** + * Storm Gust counter was dropped in renewal + **/ + #ifdef RENEWAL + sc_start(bl,SC_FREEZE,65-(5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + #else + //Tharis pointed out that this is normal freeze chance with a base of 300% + if(tsc->sg_counter >= 3 && + sc_start(bl,SC_FREEZE,300,skill_lv,skill_get_time2(skill_id,skill_lv))) + tsc->sg_counter = 0; + /** + * being it only resets on success it'd keep stacking and eventually overflowing on mvps, so we reset at a high value + **/ + else if( tsc->sg_counter > 250 ) + tsc->sg_counter = 0; + #endif + break; + + case WZ_METEOR: + sc_start(bl,SC_STUN,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WZ_VERMILION: + sc_start(bl,SC_BLIND,4*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + sc_start(bl,SC_FREEZE,(3*skill_lv+35),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_FLASHER: + sc_start(bl,SC_BLIND,(10*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_LANDMINE: + case MA_LANDMINE: + sc_start(bl,SC_STUN,(5*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_SHOCKWAVE: + status_percent_damage(src, bl, 0, 15*skill_lv+5, false); + break; + + case HT_SANDMAN: + case MA_SANDMAN: + sc_start(bl,SC_SLEEP,(10*skill_lv+40),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TF_SPRINKLESAND: + sc_start(bl,SC_BLIND,20,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TF_THROWSTONE: + sc_start(bl,SC_STUN,3,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,3,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case NPC_DARKCROSS: + case CR_HOLYCROSS: + sc_start(bl,SC_BLIND,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + //Chance to cause blind status vs demon and undead element, but not against players + if(!dstsd && (battle_check_undead(tstatus->race,tstatus->def_ele) || tstatus->race == RC_DEMON)) + sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + attack_type |= BF_WEAPON; + break; + + case AM_ACIDTERROR: + sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); + if (skill_break_equip(bl, EQP_ARMOR, 100*skill_get_time(skill_id,skill_lv), BCT_ENEMY)) + clif_emotion(bl,E_OMG); + break; + + case AM_DEMONSTRATION: + skill_break_equip(bl, EQP_WEAPON, 100*skill_lv, BCT_ENEMY); + break; + + case CR_SHIELDCHARGE: + sc_start(bl,SC_STUN,(15+skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case PA_PRESSURE: + status_percent_damage(src, bl, 0, 15+5*skill_lv, false); + break; + + case RG_RAID: + sc_start(bl,SC_STUN,(10+3*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,(10+3*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + +#ifdef RENEWAL + sc_start(bl,SC_RAID,100,7,5000); + break; + + case RG_BACKSTAP: + sc_start(bl,SC_STUN,(5+2*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); +#endif + break; + + case BA_FROSTJOKER: + sc_start(bl,SC_FREEZE,(15+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case DC_SCREAM: + sc_start(bl,SC_STUN,(25+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case BD_LULLABY: + sc_start(bl,SC_SLEEP,15,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case DC_UGLYDANCE: + rate = 5+5*skill_lv; + if(sd && (skill=pc_checkskill(sd,DC_DANCINGLESSON))) + rate += 5+skill; + status_zap(bl, 0, rate); + break; + case SL_STUN: + if (tstatus->size==SZ_MEDIUM) //Only stuns mid-sized mobs. + sc_start(bl,SC_STUN,(30+10*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case NPC_PETRIFYATTACK: + sc_start4(bl,status_skill2sc(skill_id),50+10*skill_lv, + skill_lv,0,0,skill_get_time(skill_id,skill_lv), + skill_get_time2(skill_id,skill_lv)); + break; + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case NPC_BLINDATTACK: + case NPC_POISON: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + case NPC_HELLPOWER: + sc_start(bl,status_skill2sc(skill_id),50+10*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_ACIDBREATH: + case NPC_ICEBREATH: + sc_start(bl,status_skill2sc(skill_id),70,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_BLEEDING: + sc_start(bl,SC_BLEEDING,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_MENTALBREAKER: + { //Based on observations by Tharis, Mental Breaker should do SP damage + //equal to Matk*skLevel. + rate = sstatus->matk_min; + if (rate < sstatus->matk_max) + rate += rnd()%(sstatus->matk_max - sstatus->matk_min); + rate*=skill_lv; + status_zap(bl, 0, rate); + break; + } + // Equipment breaking monster skills [Celest] + case NPC_WEAPONBRAKER: + skill_break_equip(bl, EQP_WEAPON, 150*skill_lv, BCT_ENEMY); + break; + case NPC_ARMORBRAKE: + skill_break_equip(bl, EQP_ARMOR, 150*skill_lv, BCT_ENEMY); + break; + case NPC_HELMBRAKE: + skill_break_equip(bl, EQP_HELM, 150*skill_lv, BCT_ENEMY); + break; + case NPC_SHIELDBRAKE: + skill_break_equip(bl, EQP_SHIELD, 150*skill_lv, BCT_ENEMY); + break; + + case CH_TIGERFIST: + sc_start(bl,SC_STOP,(10+skill_lv*10),0,skill_get_time2(skill_id,skill_lv)); + break; + + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + sc_start(bl,SC_STOP,(15+skill_lv*5),0,skill_get_time2(skill_id,skill_lv)); + break; + + case ST_REJECTSWORD: + sc_start(bl,SC_AUTOCOUNTER,(skill_lv*15),skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case PF_FOGWALL: + if (src != bl && !tsc->data[SC_DELUGE]) + sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case LK_HEADCRUSH: //Headcrush has chance of causing Bleeding status, except on demon and undead element + if (!(battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON)) + sc_start(bl, SC_BLEEDING,50, skill_lv, skill_get_time2(skill_id,skill_lv)); + break; + + case LK_JOINTBEAT: + status = status_skill2sc(skill_id); + if (tsc->jb_flag) { + sc_start2(bl,status,(5*skill_lv+5),skill_lv,tsc->jb_flag&BREAK_FLAGS,skill_get_time2(skill_id,skill_lv)); + tsc->jb_flag = 0; + } + break; + case ASC_METEORASSAULT: + //Any enemies hit by this skill will receive Stun, Darkness, or external bleeding status ailment with a 5%+5*skill_lv% chance. + switch(rnd()%3) { + case 0: + sc_start(bl,SC_BLIND,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,1)); + break; + case 1: + sc_start(bl,SC_STUN,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,2)); + break; + default: + sc_start(bl,SC_BLEEDING,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,3)); + } + break; + + case HW_NAPALMVULCAN: + sc_start(bl,SC_CURSE,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WS_CARTTERMINATION: // Cart termination + sc_start(bl,SC_STUN,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case CR_ACIDDEMONSTRATION: + skill_break_equip(bl, EQP_WEAPON|EQP_ARMOR, 100*skill_lv, BCT_ENEMY); + break; + + case TK_DOWNKICK: + sc_start(bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TK_JUMPKICK: + if( dstsd && dstsd->class_ != MAPID_SOUL_LINKER && !tsc->data[SC_PRESERVE] ) + {// debuff the following statuses + status_change_end(bl, SC_SPIRIT, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); + status_change_end(bl, SC_KAITE, INVALID_TIMER); + status_change_end(bl, SC_KAAHI, INVALID_TIMER); + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + } + break; + case TK_TURNKICK: + case MO_BALKYOUNG: //Note: attack_type is passed as BF_WEAPON for the actual target, BF_MISC for the splash-affected mobs. + if(attack_type&BF_MISC) //70% base stun chance... + sc_start(bl,SC_STUN,70,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_BULLSEYE: //0.1% coma rate. + if(tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN) + status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); + break; + case GS_PIERCINGSHOT: + sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NJ_HYOUSYOURAKU: + sc_start(bl,SC_FREEZE,(10+10*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_FLING: + sc_start(bl,SC_FLING,100, sd?sd->spiritball_old:5,skill_get_time(skill_id,skill_lv)); + break; + case GS_DISARM: + rate = 3*skill_lv; + if (sstatus->dex > tstatus->dex) + rate += (sstatus->dex - tstatus->dex)/5; //TODO: Made up formula + skill_strip_equip(bl, EQP_WEAPON, rate, skill_lv, skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case NPC_EVILLAND: + sc_start(bl,SC_BLIND,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_HELLJUDGEMENT: + sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_CRITICALWOUND: + sc_start(bl,SC_CRITICALWOUND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case RK_HUNDREDSPEAR: + if( !sd || pc_checkskill(sd,KN_SPEARBOOMERANG) == 0 ) + break; // Spear Boomerang auto cast chance only works if you have mastered Spear Boomerang. + rate = 10 + 3 * skill_lv; + if( rnd()%100 < rate ) + skill_castend_damage_id(src,bl,KN_SPEARBOOMERANG,1,tick,0); + break; + case RK_WINDCUTTER: + sc_start(bl,SC_FEAR,3+2*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case RK_DRAGONBREATH: + sc_start4(bl,SC_BURNING,5+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + break; + case AB_ADORAMUS: + if( tsc && !tsc->data[SC_DECREASEAGI] ) //Prevent duplicate agi-down effect. + sc_start(bl, SC_ADORAMUS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WL_CRIMSONROCK: + sc_start(bl, SC_STUN, 40, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WL_COMET: + sc_start4(bl,SC_BURNING,100,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + break; + case WL_EARTHSTRAIN: + { + int rate = 0, i; + const int pos[5] = { EQP_WEAPON, EQP_HELM, EQP_SHIELD, EQP_ARMOR, EQP_ACC }; + rate = 6 * skill_lv + sstatus->dex / 10 + (sd? sd->status.job_level / 4 : 0) - tstatus->dex /5;// The tstatus->dex / 5 part is unofficial, but players gotta have some kind of way to have resistance. [Rytech] + //rate -= rate * tstatus->dex / 200; // Disabled until official resistance is found. + + for( i = 0; i < skill_lv; i++ ) + skill_strip_equip(bl,pos[i],rate,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + case WL_JACKFROST: + sc_start(bl,SC_FREEZE,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case RA_WUGBITE: + sc_start(bl, SC_BITE, (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*2 : 0), skill_lv, (skill_get_time(skill_id,skill_lv) + (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*500 : 0)) ); + break; + case RA_SENSITIVEKEEN: + if( rnd()%100 < 8 * skill_lv ) + skill_castend_damage_id(src, bl, RA_WUGBITE, sd ? pc_checkskill(sd, RA_WUGBITE):skill_lv, tick, SD_ANIMATION); + break; + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + sc_start(bl, (skill_id == RA_FIRINGTRAP) ? SC_BURNING:SC_FREEZING, 40 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case NC_PILEBUNKER: + if( rnd()%100 < 5 + 15*skill_lv ) + { //Deactivatable Statuses: Kyrie Eleison, Auto Guard, Steel Body, Assumptio, and Millennium Shield + status_change_end(bl, SC_KYRIE, INVALID_TIMER); + status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); + status_change_end(bl, SC_STEELBODY, INVALID_TIMER); + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + status_change_end(bl, SC_MILLENNIUMSHIELD, INVALID_TIMER); + } + break; + case NC_FLAMELAUNCHER: + sc_start4(bl, SC_BURNING, 50 + 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id, skill_lv)); + break; + case NC_COLDSLOWER: + sc_start(bl, SC_FREEZE, 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + sc_start(bl, SC_FREEZING, 20 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case NC_POWERSWING: + sc_start(bl, SC_STUN, 5*skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + if( rnd()%100 < 5*skill_lv ) + skill_castend_damage_id(src, bl, NC_AXEBOOMERANG, pc_checkskill(sd, NC_AXEBOOMERANG), tick, 1); + break; + case GC_WEAPONCRUSH: + skill_castend_nodamage_id(src,bl,skill_id,skill_lv,tick,BCT_ENEMY); + break; + case LG_SHIELDPRESS: + sc_start(bl, SC_STUN, 30 + 8 * skill_lv, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case LG_PINPOINTATTACK: + rate = 30 + (((5 * (sd?pc_checkskill(sd,LG_PINPOINTATTACK):skill_lv)) + (sstatus->agi + status_get_lv(src))) / 10); + switch( skill_lv ) { + case 1: + sc_start(bl,SC_BLEEDING,rate,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case 2: + if( dstsd && dstsd->spiritball && rnd()%100 < rate ) + pc_delspiritball(dstsd, dstsd->spiritball, 0); + break; + default: + skill_break_equip(bl,(skill_lv == 3) ? EQP_SHIELD : (skill_lv == 4) ? EQP_ARMOR : EQP_WEAPON,rate * 100,BCT_ENEMY); + break; + } + break; + case LG_MOONSLASHER: + rate = 32 + 8 * skill_lv; + if( rnd()%100 < rate && dstsd ) // Uses skill_addtimerskill to avoid damage and setsit packet overlaping. Officially clif_setsit is received about 500 ms after damage packet. + skill_addtimerskill(src,tick+500,bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); + else if( dstmd && !is_boss(bl) ) + sc_start(bl,SC_STOP,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case LG_RAYOFGENESIS: // 50% chance to cause Blind on Undead and Demon monsters. + if ( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON ) + sc_start(bl, SC_BLIND,50, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case LG_EARTHDRIVE: + skill_break_equip(src, EQP_SHIELD, 500, BCT_SELF); + sc_start(bl, SC_EARTHDRIVE, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_DRAGONCOMBO: + sc_start(bl, SC_STUN, 1 + skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_FALLENEMPIRE: + sc_start(bl, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_WINDMILL: + if( dstsd ) + skill_addtimerskill(src,tick+status_get_amotion(src),bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); + else if( dstmd && !is_boss(bl) ) + sc_start(bl, SC_STUN, 100, skill_lv, 1000 + 1000 * (rnd() %3)); + break; + case SR_GENTLETOUCH_QUIET: // [(Skill Level x 5) + (Caster?s DEX + Caster?s Base Level) / 10] + sc_start(bl, SC_SILENCE, 5 * skill_lv + (sstatus->dex + status_get_lv(src)) / 10, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_EARTHSHAKER: + sc_start(bl,SC_STUN, 25 + 5 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case SR_HOWLINGOFLION: + sc_start(bl, SC_FEAR, 5 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WM_SOUND_OF_DESTRUCTION: + if( rnd()%100 < 5 + 5 * skill_lv ) { // Temporarly Check Until We Get the Official Formula + status_change_end(bl, SC_DANCING, INVALID_TIMER); + status_change_end(bl, SC_RICHMANKIM, INVALID_TIMER); + status_change_end(bl, SC_ETERNALCHAOS, INVALID_TIMER); + status_change_end(bl, SC_DRUMBATTLE, INVALID_TIMER); + status_change_end(bl, SC_NIBELUNGEN, INVALID_TIMER); + status_change_end(bl, SC_INTOABYSS, INVALID_TIMER); + status_change_end(bl, SC_SIEGFRIED, INVALID_TIMER); + status_change_end(bl, SC_WHISTLE, INVALID_TIMER); + status_change_end(bl, SC_ASSNCROS, INVALID_TIMER); + status_change_end(bl, SC_POEMBRAGI, INVALID_TIMER); + status_change_end(bl, SC_APPLEIDUN, INVALID_TIMER); + status_change_end(bl, SC_HUMMING, INVALID_TIMER); + status_change_end(bl, SC_FORTUNE, INVALID_TIMER); + status_change_end(bl, SC_SERVICE4U, INVALID_TIMER); + status_change_end(bl, SC_LONGING, INVALID_TIMER); + status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + status_change_end(bl, SC_WINKCHARM, INVALID_TIMER); + status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); + status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + } + break; + case SO_EARTHGRAVE: + sc_start(bl, SC_BLEEDING, 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); // Need official rate. [LimitLine] + break; + case SO_DIAMONDDUST: + rate = 5 + 5 * skill_lv; + if( sc && sc->data[SC_COOLER_OPTION] ) + rate += rate * sc->data[SC_COOLER_OPTION]->val2 / 100; + sc_start(bl, SC_CRYSTALIZE, rate, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case SO_VARETYR_SPEAR: + sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case GN_SLINGITEM_RANGEMELEEATK: + if( sd ) { + switch( sd->itemid ) { // Starting SCs here instead of do it in skill_additional_effect to simplify the code. + case 13261: + sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); + sc_start(bl, SC_BLEEDING, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); + break; + case 13262: + sc_start(bl, SC_MELON_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces ASPD and moviment speed + break; + case 13264: + sc_start(bl, SC_BANANA_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces LUK ??Needed confirm it, may be it's bugged in kRORE? + sc_start(bl, SC_BANANA_BOMB_SITDOWN, 75, skill_lv, skill_get_time(GN_SLINGITEM_RANGEMELEEATK,skill_lv)); // Sitdown for 3 seconds. + break; + } + sd->itemid = -1; + } + break; + case GN_HELLS_PLANT_ATK: + sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + sc_start(bl, SC_BLEEDING, 20 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case EL_WIND_SLASH: // Non confirmed rate. + sc_start(bl, SC_BLEEDING, 25, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case EL_STONE_HAMMER: + rate = 10 * skill_lv; + sc_start(bl, SC_STUN, rate, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case EL_ROCK_CRUSHER: + case EL_ROCK_CRUSHER_ATK: + sc_start(bl,status_skill2sc(skill_id),50,skill_lv,skill_get_time(EL_ROCK_CRUSHER,skill_lv)); + break; + case EL_TYPOON_MIS: + sc_start(bl,SC_SILENCE,10*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case KO_JYUMONJIKIRI: // needs more info + sc_start(bl,SC_JYUMONJIKIRI,25,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case KO_MAKIBISHI: + sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(skill_id,skill_lv)); + break; + case MH_LAVA_SLIDE: + if (tsc && !tsc->data[SC_BURNING]) sc_start4(bl, SC_BURNING, 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time(skill_id, skill_lv)); + break; + case MH_STAHL_HORN: + sc_start(bl, SC_STUN, (20 + 4 * (skill_lv-1)), skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case MH_NEEDLE_OF_PARALYZE: + sc_start(bl, SC_PARALYSIS, 40 + (5*skill_lv), skill_lv, skill_get_time(skill_id, skill_lv)); + break; + } + + if (md && battle_config.summons_trigger_autospells && md->master_id && md->special_state.ai) + { //Pass heritage to Master for status causing effects. [Skotlex] + sd = map_id2sd(md->master_id); + src = sd?&sd->bl:src; + } + + if( attack_type&BF_WEAPON ) + { // Coma, Breaking Equipment + if( sd && sd->special_state.bonus_coma ) + { + rate = sd->weapon_coma_ele[tstatus->def_ele]; + rate += sd->weapon_coma_race[tstatus->race]; + rate += sd->weapon_coma_race[tstatus->mode&MD_BOSS?RC_BOSS:RC_NONBOSS]; + if (rate) + status_change_start(bl, SC_COMA, rate, 0, 0, src->id, 0, 0, 0); + } + if( sd && battle_config.equip_self_break_rate ) + { // Self weapon breaking + rate = battle_config.equip_natural_break_rate; + if( sc ) + { + if(sc->data[SC_OVERTHRUST]) + rate += 10; + if(sc->data[SC_MAXOVERTHRUST]) + rate += 10; + } + if( rate ) + skill_break_equip(src, EQP_WEAPON, rate, BCT_SELF); + } + if( battle_config.equip_skill_break_rate && skill_id != WS_CARTTERMINATION && skill_id != ITM_TOMAHAWK ) + { // Cart Termination/Tomahawk won't trigger breaking data. Why? No idea, go ask Gravity. + // Target weapon breaking + rate = 0; + if( sd ) + rate += sd->bonus.break_weapon_rate; + if( sc && sc->data[SC_MELTDOWN] ) + rate += sc->data[SC_MELTDOWN]->val2; + if( rate ) + skill_break_equip(bl, EQP_WEAPON, rate, BCT_ENEMY); + + // Target armor breaking + rate = 0; + if( sd ) + rate += sd->bonus.break_armor_rate; + if( sc && sc->data[SC_MELTDOWN] ) + rate += sc->data[SC_MELTDOWN]->val3; + if( rate ) + skill_break_equip(bl, EQP_ARMOR, rate, BCT_ENEMY); + } + } + + if( sd && sd->ed && sc && !status_isdead(bl) && !skill_id ){ + struct unit_data *ud = unit_bl2ud(src); + + if( sc->data[SC_WILD_STORM_OPTION] ) + skill = sc->data[SC_WILD_STORM_OPTION]->val2; + else if( sc->data[SC_UPHEAVAL_OPTION] ) + skill = sc->data[SC_UPHEAVAL_OPTION]->val2; + else if( sc->data[SC_TROPIC_OPTION] ) + skill = sc->data[SC_TROPIC_OPTION]->val3; + else if( sc->data[SC_CHILLY_AIR_OPTION] ) + skill = sc->data[SC_CHILLY_AIR_OPTION]->val3; + else + skill = 0; + + if ( rnd()%100 < 25 && skill ){ + skill_castend_damage_id(src, bl, skill, 5, tick, 0); + + if (ud) { + rate = skill_delayfix(src, skill, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers ) + clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + // Autospell when attacking + if( sd && !status_isdead(bl) && sd->autospell[0].id ) + { + struct block_list *tbl; + struct unit_data *ud; + int i, skill_lv, type, notok; + + for (i = 0; i < ARRAYLENGTH(sd->autospell) && sd->autospell[i].id; i++) { + + if(!(sd->autospell[i].flag&attack_type&BF_WEAPONMASK && + sd->autospell[i].flag&attack_type&BF_RANGEMASK && + sd->autospell[i].flag&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + skill = (sd->autospell[i].id > 0) ? sd->autospell[i].id : -sd->autospell[i].id; + + sd->state.autocast = 1; + notok = skillnotok(skill, sd); + sd->state.autocast = 0; + + if ( notok ) + continue; + + skill_lv = sd->autospell[i].lv?sd->autospell[i].lv:1; + if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); + + rate = (!sd->state.arrow_atk) ? sd->autospell[i].rate : sd->autospell[i].rate / 2; + + if (rnd()%1000 >= rate) + continue; + + tbl = (sd->autospell[i].id < 0) ? src : bl; + + if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill)&UF_NOREITERATION && + skill_check_unit_range(src,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill)&UF_NOFOOTSET && + skill_check_unit_range2(src,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { + if(sd->ud.skillunit[v]->skill_id == skill) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + if( battle_config.autospell_check_range && + !battle_check_range(src, tbl, skill_get_range2(src, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) + continue; + + if (skill == AS_SONICBLOW) + pc_stop_attack(sd); //Special case, Sonic Blow autospell should stop the player attacking. + if (skill == PF_SPIDERWEB) //Special case, due to its nature of coding. + type = CAST_GROUND; + + sd->state.autocast = 1; + skill_consume_requirement(sd,skill,skill_lv,1); + skill_toggle_magicpower(src, skill); + switch (type) { + case CAST_GROUND: + skill_castend_pos2(src, tbl->x, tbl->y, skill, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, tbl, skill, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, tbl, skill, skill_lv, tick, 0); + break; + } + sd->state.autocast = 0; + //Set canact delay. [Skotlex] + ud = unit_bl2ud(src); + if (ud) { + rate = skill_delayfix(src, skill, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + //Autobonus when attacking + if( sd && sd->autobonus[0].rate ) + { + int i; + for( i = 0; i < ARRAYLENGTH(sd->autobonus); i++ ) + { + if( rnd()%1000 >= sd->autobonus[i].rate ) + continue; + if( sd->autobonus[i].active != INVALID_TIMER ) + continue; + if(!(sd->autobonus[i].atk_type&attack_type&BF_WEAPONMASK && + sd->autobonus[i].atk_type&attack_type&BF_RANGEMASK && + sd->autobonus[i].atk_type&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + pc_exeautobonus(sd,&sd->autobonus[i]); + } + } + + //Polymorph + if(sd && sd->bonus.classchange && attack_type&BF_WEAPON && + dstmd && !(tstatus->mode&MD_BOSS) && + (rnd()%10000 < sd->bonus.classchange)) + { + struct mob_db *mob; + int class_; + skill = 0; + do { + do { + class_ = rnd() % MAX_MOB_DB; + } while (!mobdb_checkid(class_)); + + rate = rnd() % 1000000; + mob = mob_db(class_); + } while ( + (mob->status.mode&(MD_BOSS|MD_PLANT) || mob->summonper[0] <= rate) && + (skill++) < 2000); + if (skill < 2000) + mob_class_change(dstmd,class_); + } + + return 0; +} + +int skill_onskillusage(struct map_session_data *sd, struct block_list *bl, uint16 skill_id, unsigned int tick) { + int skill, skill_lv, i, type, notok; + struct block_list *tbl; + + if( sd == NULL || !skill_id ) + return 0; + + for( i = 0; i < ARRAYLENGTH(sd->autospell3) && sd->autospell3[i].flag; i++ ) { + if( sd->autospell3[i].flag != skill_id ) + continue; + + if( sd->autospell3[i].lock ) + continue; // autospell already being executed + + skill = (sd->autospell3[i].id > 0) ? sd->autospell3[i].id : -sd->autospell3[i].id; + + sd->state.autocast = 1; + notok = skillnotok(skill, sd); + sd->state.autocast = 0; + + if ( notok ) + continue; + + skill_lv = sd->autospell3[i].lv ? sd->autospell3[i].lv : 1; + if( skill_lv < 0 ) skill_lv = 1 + rnd()%(-skill_lv); + + if( sd->autospell3[i].id >= 0 && bl == NULL ) + continue; // No target + if( rnd()%1000 >= sd->autospell3[i].rate ) + continue; + + tbl = (sd->autospell3[i].id < 0) ? &sd->bl : bl; + + if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill)&UF_NOREITERATION && + skill_check_unit_range(&sd->bl,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill)&UF_NOFOOTSET && + skill_check_unit_range2(&sd->bl,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { + if(sd->ud.skillunit[v]->skill_id == skill) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + if( battle_config.autospell_check_range && + !battle_check_range(&sd->bl, tbl, skill_get_range2(&sd->bl, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) + continue; + + sd->state.autocast = 1; + sd->autospell3[i].lock = true; + skill_consume_requirement(sd,skill,skill_lv,1); + switch( type ) + { + case CAST_GROUND: skill_castend_pos2(&sd->bl, tbl->x, tbl->y, skill, skill_lv, tick, 0); break; + case CAST_NODAMAGE: skill_castend_nodamage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; + case CAST_DAMAGE: skill_castend_damage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; + } + sd->autospell3[i].lock = false; + sd->state.autocast = 0; + } + + if( sd && sd->autobonus3[0].rate ) + { + for( i = 0; i < ARRAYLENGTH(sd->autobonus3); i++ ) + { + if( rnd()%1000 >= sd->autobonus3[i].rate ) + continue; + if( sd->autobonus3[i].active != INVALID_TIMER ) + continue; + if( sd->autobonus3[i].atk_type != skill_id ) + continue; + pc_exeautobonus(sd,&sd->autobonus3[i]); + } + } + + return 1; +} + +/* Splitted off from skill_additional_effect, which is never called when the + * attack skill kills the enemy. Place in this function counter status effects + * when using skills (eg: Asura's sp regen penalty, or counter-status effects + * from cards) that will take effect on the source, not the target. [Skotlex] + * Note: Currently this function only applies to Extremity Fist and BF_WEAPON + * type of skills, so not every instance of skill_additional_effect needs a call + * to this one. + */ +int skill_counter_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, unsigned int tick) +{ + int rate; + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + + nullpo_ret(src); + nullpo_ret(bl); + + if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest + + sd = BL_CAST(BL_PC, src); + dstsd = BL_CAST(BL_PC, bl); + + if(dstsd && attack_type&BF_WEAPON) + { //Counter effects. + enum sc_type type; + int i, time; + for(i=0; i < ARRAYLENGTH(dstsd->addeff2) && dstsd->addeff2[i].flag; i++) + { + rate = dstsd->addeff2[i].rate; + if (attack_type&BF_LONG) + rate+=dstsd->addeff2[i].arrow_rate; + if (!rate) continue; + + if ((dstsd->addeff2[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT)) + { //Trigger has range consideration. + if((dstsd->addeff2[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || + (dstsd->addeff2[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) + continue; //Range Failed. + } + type = dstsd->addeff2[i].id; + time = skill_get_time2(status_sc2skill(type),7); + + if (dstsd->addeff2[i].flag&ATF_TARGET) + status_change_start(src,type,rate,7,0,0,0,time,0); + + if (dstsd->addeff2[i].flag&ATF_SELF && !status_isdead(bl)) + status_change_start(bl,type,rate,7,0,0,0,time,0); + } + } + + switch(skill_id){ + case MO_EXTREMITYFIST: + sc_start(src,SC_EXTREMITYFIST,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_FULLBUSTER: + sc_start(src,SC_BLIND,2*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case HFLI_SBR44: //[orn] + case HVAN_EXPLOSION: + if(src->type == BL_HOM){ + TBL_HOM *hd = (TBL_HOM*)src; + hd->homunculus.intimacy = 200; + if (hd->master) + clif_send_homdata(hd->master,SP_INTIMATE,hd->homunculus.intimacy/100); + } + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + attack_type |= BF_WEAPON; + break; + } + + if(sd && (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && + rnd()%10000 < battle_config.sg_miracle_skill_ratio) //SG_MIRACLE [Komurka] + sc_start(src,SC_MIRACLE,100,1,battle_config.sg_miracle_skill_duration); + + if(sd && skill_id && attack_type&BF_MAGIC && status_isdead(bl) && + !(skill_get_inf(skill_id)&(INF_GROUND_SKILL|INF_SELF_SKILL)) && + (rate=pc_checkskill(sd,HW_SOULDRAIN))>0 + ){ //Soul Drain should only work on targetted spells [Skotlex] + if (pc_issit(sd)) pc_setstand(sd); //Character stuck in attacking animation while 'sitting' fix. [Skotlex] + clif_skill_nodamage(src,bl,HW_SOULDRAIN,rate,1); + status_heal(src, 0, status_get_lv(bl)*(95+15*rate)/100, 2); + } + + if( sd && status_isdead(bl) ) { + int sp = 0, hp = 0; + if( attack_type&BF_WEAPON ) { + sp += sd->bonus.sp_gain_value; + sp += sd->sp_gain_race[status_get_race(bl)]; + sp += sd->sp_gain_race[is_boss(bl)?RC_BOSS:RC_NONBOSS]; + hp += sd->bonus.hp_gain_value; + } + if( attack_type&BF_MAGIC ) { + sp += sd->bonus.magic_sp_gain_value; + hp += sd->bonus.magic_hp_gain_value; + if( skill_id == WZ_WATERBALL ) {//(bugreport:5303) + struct status_change *sc = NULL; + if( ( sc = status_get_sc(src) ) ) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == WZ_WATERBALL) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + } + } + } + if( hp || sp ) { // updated to force healing to allow healing through berserk + status_heal(src, hp, sp, battle_config.show_hp_sp_gain ? 3 : 1); + } + } + + // Trigger counter-spells to retaliate against damage causing skills. + if(dstsd && !status_isdead(bl) && dstsd->autospell2[0].id && + !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE)) + { + struct block_list *tbl; + struct unit_data *ud; + int i, skill_id, skill_lv, rate, type, notok; + + for (i = 0; i < ARRAYLENGTH(dstsd->autospell2) && dstsd->autospell2[i].id; i++) { + + if(!(dstsd->autospell2[i].flag&attack_type&BF_WEAPONMASK && + dstsd->autospell2[i].flag&attack_type&BF_RANGEMASK && + dstsd->autospell2[i].flag&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + skill_id = (dstsd->autospell2[i].id > 0) ? dstsd->autospell2[i].id : -dstsd->autospell2[i].id; + skill_lv = dstsd->autospell2[i].lv?dstsd->autospell2[i].lv:1; + if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); + + rate = dstsd->autospell2[i].rate; + if (attack_type&BF_LONG) + rate>>=1; + + dstsd->state.autocast = 1; + notok = skillnotok(skill_id, dstsd); + dstsd->state.autocast = 0; + + if ( notok ) + continue; + + if (rnd()%1000 >= rate) + continue; + + tbl = (dstsd->autospell2[i].id < 0) ? bl : src; + + if( (type = skill_get_casttype(skill_id)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill_id)&UF_NOREITERATION && + skill_check_unit_range(bl,tbl->x,tbl->y,skill_id,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill_id)&UF_NOFOOTSET && + skill_check_unit_range2(bl,tbl->x,tbl->y,skill_id,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill_id, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && dstsd->ud.skillunit[v] && maxcount;v++) { + if(dstsd->ud.skillunit[v]->skill_id == skill_id) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + + if( !battle_check_range(src, tbl, skill_get_range2(src, skill_id,skill_lv) + (skill_id == RG_CLOSECONFINE?0:1)) && battle_config.autospell_check_range ) + continue; + + dstsd->state.autocast = 1; + skill_consume_requirement(dstsd,skill_id,skill_lv,1); + switch (type) { + case CAST_GROUND: + skill_castend_pos2(bl, tbl->x, tbl->y, skill_id, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(bl, tbl, skill_id, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(bl, tbl, skill_id, skill_lv, tick, 0); + break; + } + dstsd->state.autocast = 0; + //Set canact delay. [Skotlex] + ud = unit_bl2ud(bl); + if (ud) { + rate = skill_delayfix(bl, skill_id, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers && dstsd ) + clif_status_change(bl, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + //Autobonus when attacked + if( dstsd && !status_isdead(bl) && dstsd->autobonus2[0].rate && !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE) ) + { + int i; + for( i = 0; i < ARRAYLENGTH(dstsd->autobonus2); i++ ) + { + if( rnd()%1000 >= dstsd->autobonus2[i].rate ) + continue; + if( dstsd->autobonus2[i].active != INVALID_TIMER ) + continue; + if(!(dstsd->autobonus2[i].atk_type&attack_type&BF_WEAPONMASK && + dstsd->autobonus2[i].atk_type&attack_type&BF_RANGEMASK && + dstsd->autobonus2[i].atk_type&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + pc_exeautobonus(dstsd,&dstsd->autobonus2[i]); + } + } + + return 0; +} +/*========================================================================= + Breaks equipment. On-non players causes the corresponding strip effect. + - rate goes from 0 to 10000 (100.00%) + - flag is a BCT_ flag to indicate which type of adjustment should be used + (BCT_ENEMY/BCT_PARTY/BCT_SELF) are the valid values. +--------------------------------------------------------------------------*/ +int skill_break_equip (struct block_list *bl, unsigned short where, int rate, int flag) +{ + const int where_list[4] = {EQP_WEAPON, EQP_ARMOR, EQP_SHIELD, EQP_HELM}; + const enum sc_type scatk[4] = {SC_STRIPWEAPON, SC_STRIPARMOR, SC_STRIPSHIELD, SC_STRIPHELM}; + const enum sc_type scdef[4] = {SC_CP_WEAPON, SC_CP_ARMOR, SC_CP_SHIELD, SC_CP_HELM}; + struct status_change *sc = status_get_sc(bl); + int i,j; + TBL_PC *sd; + sd = BL_CAST(BL_PC, bl); + if (sc && !sc->count) + sc = NULL; + + if (sd) { + if (sd->bonus.unbreakable_equip) + where &= ~sd->bonus.unbreakable_equip; + if (sd->bonus.unbreakable) + rate -= rate*sd->bonus.unbreakable/100; + if (where&EQP_WEAPON) { + switch (sd->status.weapon) { + case W_FIST: //Bare fists should not break :P + case W_1HAXE: + case W_2HAXE: + case W_MACE: // Axes and Maces can't be broken [DracoRPG] + case W_2HMACE: + case W_STAFF: + case W_2HSTAFF: + case W_BOOK: //Rods and Books can't be broken [Skotlex] + case W_HUUMA: + where &= ~EQP_WEAPON; + } + } + } + if (flag&BCT_ENEMY) { + if (battle_config.equip_skill_break_rate != 100) + rate = rate*battle_config.equip_skill_break_rate/100; + } else if (flag&(BCT_PARTY|BCT_SELF)) { + if (battle_config.equip_self_break_rate != 100) + rate = rate*battle_config.equip_self_break_rate/100; + } + + for (i = 0; i < 4; i++) { + if (where&where_list[i]) { + if (sc && sc->count && sc->data[scdef[i]]) + where&=~where_list[i]; + else if (rnd()%10000 >= rate) + where&=~where_list[i]; + else if (!sd && !(status_get_mode(bl)&MD_BOSS)) //Cause Strip effect. + sc_start(bl,scatk[i],100,0,skill_get_time(status_sc2skill(scatk[i]),1)); + } + } + if (!where) //Nothing to break. + return 0; + if (sd) { + for (i = 0; i < EQI_MAX; i++) { + j = sd->equip_index[i]; + if (j < 0 || sd->status.inventory[j].attribute == 1 || !sd->inventory_data[j]) + continue; + + switch(i) { + case EQI_HEAD_TOP: //Upper Head + flag = (where&EQP_HELM); + break; + case EQI_ARMOR: //Body + flag = (where&EQP_ARMOR); + break; + case EQI_HAND_R: //Left/Right hands + case EQI_HAND_L: + flag = ( + (where&EQP_WEAPON && sd->inventory_data[j]->type == IT_WEAPON) || + (where&EQP_SHIELD && sd->inventory_data[j]->type == IT_ARMOR)); + break; + case EQI_SHOES: + flag = (where&EQP_SHOES); + break; + case EQI_GARMENT: + flag = (where&EQP_GARMENT); + break; + default: + continue; + } + if (flag) { + sd->status.inventory[j].attribute = 1; + pc_unequipitem(sd, j, 3); + } + } + clif_equiplist(sd); + } + + return where; //Return list of pieces broken. +} + +int skill_strip_equip(struct block_list *bl, unsigned short where, int rate, int lv, int time) +{ + struct status_change *sc; + const int pos[5] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HELM, EQP_ACC}; + const enum sc_type sc_atk[5] = {SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC__STRIPACCESSORY}; + const enum sc_type sc_def[5] = {SC_CP_WEAPON, SC_CP_SHIELD, SC_CP_ARMOR, SC_CP_HELM, 0}; + int i; + + if (rnd()%100 >= rate) + return 0; + + sc = status_get_sc(bl); + if (!sc || sc->option&OPTION_MADOGEAR ) //Mado Gear cannot be divested [Ind] + return 0; + + for (i = 0; i < ARRAYLENGTH(pos); i++) { + if (where&pos[i] && sc->data[sc_def[i]]) + where&=~pos[i]; + } + if (!where) return 0; + + for (i = 0; i < ARRAYLENGTH(pos); i++) { + if (where&pos[i] && !sc_start(bl, sc_atk[i], 100, lv, time)) + where&=~pos[i]; + } + return where?1:0; +} +//Early declaration +static int skill_area_temp[8]; +/*========================================================================= + Used to knock back players, monsters, traps, etc + - 'count' is the number of squares to knock back + - 'direction' indicates the way OPPOSITE to the knockback direction (or -1 for default behavior) + - if 'flag&0x1', position update packets must not be sent. + - if 'flag&0x2', skill blown ignores players' special_state.no_knockback + -------------------------------------------------------------------------*/ +int skill_blown(struct block_list* src, struct block_list* target, int count, int8 dir, int flag) +{ + int dx = 0, dy = 0; + struct skill_unit* su = NULL; + + nullpo_ret(src); + + if (src != target && (map_flag_gvg(target->m) || map[target->m].flag.battleground)) + return 0; //No knocking back in WoE + if (count == 0) + return 0; //Actual knockback distance is 0. + + switch (target->type) { + case BL_MOB: { + struct mob_data* md = BL_CAST(BL_MOB, target); + if( md->class_ == MOBID_EMPERIUM ) + return 0; + if(src != target && is_boss(target)) //Bosses can't be knocked-back + return 0; + } + break; + case BL_PC: { + struct map_session_data *sd = BL_CAST(BL_PC, target); + if( sd->sc.data[SC_BASILICA] && sd->sc.data[SC_BASILICA]->val4 == sd->bl.id && !is_boss(src)) + return 0; // Basilica caster can't be knocked-back by normal monsters. + if( !(flag&0x2) && src != target && sd->special_state.no_knockback ) + return 0; + } + break; + case BL_SKILL: + su = (struct skill_unit *)target; + if( su && su->group && su->group->unit_id == UNT_ANKLESNARE ) + return 0; // ankle snare cannot be knocked back + break; + } + + if (dir == -1) // <optimized>: do the computation here instead of outside + dir = map_calc_dir(target, src->x, src->y); // direction from src to target, reversed + + if (dir >= 0 && dir < 8) + { // take the reversed 'direction' and reverse it + dx = -dirx[dir]; + dy = -diry[dir]; + } + + return unit_blown(target, dx, dy, count, flag); // send over the proper flag +} + + +//Checks if 'bl' should reflect back a spell cast by 'src'. +//type is the type of magic attack: 0: indirect (aoe), 1: direct (targetted) +static int skill_magic_reflect(struct block_list* src, struct block_list* bl, int type) +{ + struct status_change *sc = status_get_sc(bl); + struct map_session_data* sd = BL_CAST(BL_PC, bl); + + if( sc && sc->data[SC_KYOMU] ) // Nullify reflecting ability + return 0; + + // item-based reflection + if( sd && sd->bonus.magic_damage_return && type && rnd()%100 < sd->bonus.magic_damage_return ) + return 1; + + if( is_boss(src) ) + return 0; + + // status-based reflection + if( !sc || sc->count == 0 ) + return 0; + + if( sc->data[SC_MAGICMIRROR] && rnd()%100 < sc->data[SC_MAGICMIRROR]->val2 ) + return 1; + + if( sc->data[SC_KAITE] && (src->type == BL_PC || status_get_lv(src) <= 80) ) + {// Kaite only works against non-players if they are low-level. + clif_specialeffect(bl, 438, AREA); + if( --sc->data[SC_KAITE]->val2 <= 0 ) + status_change_end(bl, SC_KAITE, INVALID_TIMER); + return 2; + } + + return 0; +} + +/* + * ========================================================================= + * Does a skill attack with the given properties. + * src is the master behind the attack (player/mob/pet) + * dsrc is the actual originator of the damage, can be the same as src, or a BL_SKILL + * bl is the target to be attacked. + * flag can hold a bunch of information: + * flag&0xFFF is passed to the underlying battle_calc_attack for processing + * (usually holds number of targets, or just 1 for simple splash attacks) + * flag&0x1000 is used to tag that this is a splash-attack (so the damage + * packet shouldn't display a skill animation) + * flag&0x2000 is used to signal that the skill_lv should be passed as -1 to the + * client (causes player characters to not scream skill name) + *-------------------------------------------------------------------------*/ +int skill_attack (int attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct Damage dmg; + struct status_data *sstatus, *tstatus; + struct status_change *sc; + struct map_session_data *sd, *tsd; + int type,damage,rdamage=0; + int8 rmdamage=0;//magic reflected + + if(skill_id > 0 && !skill_lv) return 0; + + nullpo_ret(src); //Source is the master behind the attack (player/mob/pet) + nullpo_ret(dsrc); //dsrc is the actual originator of the damage, can be the same as src, or a skill casted by src. + nullpo_ret(bl); //Target to be attacked. + + if (src != dsrc) { + //When caster is not the src of attack, this is a ground skill, and as such, do the relevant target checking. [Skotlex] + if (!status_check_skilluse(battle_config.skill_caster_check?src:NULL, bl, skill_id, 2)) + return 0; + } else if ((flag&SD_ANIMATION) && skill_get_nk(skill_id)&NK_SPLASH) { + //Note that splash attacks often only check versus the targetted mob, those around the splash area normally don't get checked for being hidden/cloaked/etc. [Skotlex] + if (!status_check_skilluse(src, bl, skill_id, 2)) + return 0; + } + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, bl); + + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(bl); + sc= status_get_sc(bl); + if (sc && !sc->count) sc = NULL; //Don't need it. + + // Is this check really needed? FrostNova won't hurt you if you step right where the caster is? + if(skill_id == WZ_FROSTNOVA && dsrc->x == bl->x && dsrc->y == bl->y) + return 0; + //Trick Dead protects you from damage, but not from buffs and the like, hence it's placed here. + if (sc && sc->data[SC_TRICKDEAD] && !(sstatus->mode&MD_BOSS)) + return 0; + + dmg = battle_calc_attack(attack_type,src,bl,skill_id,skill_lv,flag&0xFFF); + + //Skotlex: Adjusted to the new system + if(src->type==BL_PET) + { // [Valaris] + struct pet_data *pd = (TBL_PET*)src; + if (pd->a_skill && pd->a_skill->div_ && pd->a_skill->id == skill_id) + { + int element = skill_get_ele(skill_id, skill_lv); + /*if (skill_id == -1) Does it ever worked? + element = sstatus->rhw.ele;*/ + if (element != ELE_NEUTRAL || !(battle_config.attack_attr_none&BL_PET)) + dmg.damage=battle_attr_fix(src, bl, skill_lv, element, tstatus->def_ele, tstatus->ele_lv); + else + dmg.damage= skill_lv; + dmg.damage2=0; + dmg.div_= pd->a_skill->div_; + } + } + + if( dmg.flag&BF_MAGIC && ( skill_id != NPC_EARTHQUAKE || (battle_config.eq_single_target_reflectable && (flag&0xFFF) == 1) ) ) + { // Earthquake on multiple targets is not counted as a target skill. [Inkfish] + if( (dmg.damage || dmg.damage2) && (type = skill_magic_reflect(src, bl, src==dsrc)) ) + { //Magic reflection, switch caster/target + struct block_list *tbl = bl; + rmdamage = 1; + bl = src; + src = tbl; + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, bl); + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; //Don't need it. + /* bugreport:2564 flag&2 disables double casting trigger */ + flag |= 2; + + //Spirit of Wizard blocks Kaite's reflection + if( type == 2 && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) + { //Consume one Fragment per hit of the casted skill? [Skotlex] + type = tsd?pc_search_inventory (tsd, 7321):0; + if (type >= 0) { + if ( tsd ) pc_delitem(tsd, type, 1, 0, 1, LOG_TYPE_CONSUME); + dmg.damage = dmg.damage2 = 0; + dmg.dmg_lv = ATK_MISS; + sc->data[SC_SPIRIT]->val3 = skill_id; + sc->data[SC_SPIRIT]->val4 = dsrc->id; + } + } + + /** + * Official Magic Reflection Behavior : damage reflected depends on gears caster wears, not target + **/ + #if MAGIC_REFLECTION_TYPE + if( dmg.dmg_lv != ATK_MISS )//Wiz SL cancelled and consumed fragment + dmg = battle_calc_attack(BF_MAGIC,bl,bl,skill_id,skill_lv,flag&0xFFF); + #endif + } + if(sc && sc->data[SC_MAGICROD] && src == dsrc) { + int sp = skill_get_sp(skill_id,skill_lv); + dmg.damage = dmg.damage2 = 0; + dmg.dmg_lv = ATK_MISS; //This will prevent skill additional effect from taking effect. [Skotlex] + sp = sp * sc->data[SC_MAGICROD]->val2 / 100; + if(skill_id == WZ_WATERBALL && skill_lv > 1) + sp = sp/((skill_lv|1)*(skill_lv|1)); //Estimate SP cost of a single water-ball + status_heal(bl, 0, sp, 2); + } + } + + damage = dmg.damage + dmg.damage2; + + if( (skill_id == AL_INCAGI || skill_id == AL_BLESSING || + skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || + skill_id == MER_INCAGI || skill_id == MER_BLESSING) && tsd->sc.data[SC_CHANGEUNDEAD] ) + damage = 1; + + if( damage > 0 && (( dmg.flag&BF_WEAPON && src != bl && ( src == dsrc || ( dsrc->type == BL_SKILL && ( skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM ) ) )) + || (sc && sc->data[SC_REFLECTDAMAGE])) ) + rdamage = battle_calc_return_damage(bl,src, &damage, dmg.flag, skill_id); + + if( damage && sc && sc->data[SC_GENSOU] && dmg.flag&BF_MAGIC ){ + struct block_list *nbl = NULL; + nbl = battle_getenemyarea(bl,bl->x,bl->y,2,BL_CHAR,bl->id); + if( nbl ){ // Only one target is chosen. + damage = damage / 2; // Deflect half of the damage to a target nearby + clif_skill_damage(bl, nbl, tick, status_get_amotion(src), 0, status_fix_damage(bl,nbl,damage,0), dmg.div_, OB_OBOROGENSOU_TRANSITION_ATK, -1, 6); + } + } + + //Skill hit type + type=(skill_id==0)?5:skill_get_hit(skill_id); + + if(damage < dmg.div_ + //Only skills that knockback even when they miss. [Skotlex] + && skill_id != CH_PALMSTRIKE) + dmg.blewcount = 0; + + if(skill_id == CR_GRANDCROSS||skill_id == NPC_GRANDDARKNESS) { + if(battle_config.gx_disptype) dsrc = src; + if(src == bl) type = 4; + else flag|=SD_ANIMATION; + } + if(skill_id == NJ_TATAMIGAESHI) { + dsrc = src; //For correct knockback. + flag|=SD_ANIMATION; + } + + if(sd) { + int flag = 0; //Used to signal if this skill can be combo'ed later on. + struct status_change_entry *sce; + if ((sce = sd->sc.data[SC_COMBO])) {//End combo state after skill is invoked. [Skotlex] + switch (skill_id) { + case TK_TURNKICK: + case TK_STORMKICK: + case TK_DOWNKICK: + case TK_COUNTER: + if (pc_famerank(sd->status.char_id,MAPID_TAEKWON)) {//Extend combo time. + sce->val1 = skill_id; //Update combo-skill + sce->val3 = skill_id; + if( sce->timer != INVALID_TIMER ) + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(tick+sce->val4, status_change_timer, src->id, SC_COMBO); + break; + } + unit_cancel_combo(src); // Cancel combo wait + break; + default: + if( src == dsrc ) // Ground skills are exceptions. [Inkfish] + status_change_end(src, SC_COMBO, INVALID_TIMER); + } + } + switch(skill_id) { + case MO_TRIPLEATTACK: + if (pc_checkskill(sd, MO_CHAINCOMBO) > 0 || pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + flag=1; + break; + case MO_CHAINCOMBO: + if(pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) + flag=1; + break; + case MO_COMBOFINISH: + if (sd->status.party_id>0) //bonus from SG_FRIEND [Komurka] + party_skill_check(sd, sd->status.party_id, MO_COMBOFINISH, skill_lv); + if (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) + flag=1; + case CH_TIGERFIST: + if (!flag && pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1) + flag=1; + case CH_CHAINCRUSH: + if (!flag && pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball > 0 && sd->sc.data[SC_EXPLOSIONSPIRITS]) + flag=1; + break; + case AC_DOUBLE: + if( (tstatus->race == RC_BRUTE || tstatus->race == RC_INSECT) && pc_checkskill(sd, HT_POWER)) + { + //TODO: This code was taken from Triple Blows, is this even how it should be? [Skotlex] + sc_start2(src,SC_COMBO,100,HT_POWER,bl->id,2000); + clif_combo_delay(src,2000); + } + break; + case TK_COUNTER: + { //bonus from SG_FRIEND [Komurka] + int level; + if(sd->status.party_id>0 && (level = pc_checkskill(sd,SG_FRIEND))) + party_skill_check(sd, sd->status.party_id, TK_COUNTER,level); + } + break; + case SL_STIN: + case SL_STUN: + if (skill_lv >= 7 && !sd->sc.data[SC_SMA]) + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA, skill_lv)); + break; + case GS_FULLBUSTER: + //Can't attack nor use items until skill's delay expires. [Skotlex] + sd->ud.attackabletime = sd->canuseitem_tick = sd->ud.canact_tick; + break; + case SR_DRAGONCOMBO: + if( pc_checkskill(sd, SR_FALLENEMPIRE) > 0 ) + flag = 1; + break; + case SR_FALLENEMPIRE: + if( pc_checkskill(sd, SR_TIGERCANNON) > 0 || pc_checkskill(sd, SR_GATEOFHELL) > 0 ) + flag = 1; + break; + } //Switch End + if (flag) { //Possible to chain + flag = DIFF_TICK(sd->ud.canact_tick, tick); + if (flag < 1) flag = 1; + sc_start2(src,SC_COMBO,100,skill_id,bl->id,flag); + clif_combo_delay(src, flag); + } + } + + //Display damage. + switch( skill_id ) + { + case PA_GOSPEL: //Should look like Holy Cross [Skotlex] + dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, CR_HOLYCROSS, -1, 5); + break; + //Skills that need be passed as a normal attack for the client to display correctly. + case HVAN_EXPLOSION: + case NPC_SELFDESTRUCTION: + if(src->type==BL_PC) + dmg.blewcount = 10; + dmg.amotion = 0; //Disable delay or attack will do no damage since source is dead by the time it takes effect. [Skotlex] + // fall through + case KN_AUTOCOUNTER: + case NPC_CRITICALSLASH: + case TF_DOUBLE: + case GS_CHAINACTION: + dmg.dmotion = clif_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,dmg.type,dmg.damage2); + break; + + case AS_SPLASHER: + if( flag&SD_ANIMATION ) // the surrounding targets + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, 5); // needs -1 as skill level + else // the central target doesn't display an animation + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, 5); // needs -2(!) as skill level + break; + case WL_HELLINFERNO: + case SR_EARTHSHAKER: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,skill_id,-2,6); + break; + case WL_SOULEXPANSION: + case WL_COMET: + case KO_MUCHANAGE: + case NJ_HUUMA: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,skill_lv,8); + break; + case WL_CHAINLIGHTNING_ATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,WL_CHAINLIGHTNING,-2,6); + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + case EL_FIRE_BOMB: + case EL_FIRE_BOMB_ATK: + case EL_FIRE_WAVE: + case EL_FIRE_WAVE_ATK: + case EL_FIRE_MANTLE: + case EL_CIRCLE_OF_FIRE: + case EL_FIRE_ARROW: + case EL_ICE_NEEDLE: + case EL_WATER_SCREW: + case EL_WATER_SCREW_ATK: + case EL_WIND_SLASH: + case EL_TIDAL_WEAPON: + case EL_ROCK_CRUSHER: + case EL_ROCK_CRUSHER_ATK: + case EL_HURRICANE: + case EL_HURRICANE_ATK: + case KO_BAKURETSU: + case GN_CRAZYWEED_ATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,5); + break; + case GN_SLINGITEM_RANGEMELEEATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,GN_SLINGITEM,-2,6); + break; + case EL_STONE_RAIN: + dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,(flag&1)?8:5); + break; + case WM_SEVERE_RAINSTORM_MELEE: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_SEVERE_RAINSTORM,skill_lv,5); + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_REVERBERATION,-2,6); + break; + case HT_CLAYMORETRAP: + case HT_BLASTMINE: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id,flag&SD_LEVEL?-1:skill_lv, 5); + if( dsrc != src ) // avoid damage display redundancy + break; + case HT_LANDMINE: + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, type); + break; + case WZ_SIGHTBLASTER: + dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); + break; + case AB_DUPLELIGHT_MELEE: + case AB_DUPLELIGHT_MAGIC: + dmg.amotion = 300;/* makes the damage value not overlap with previous damage (when displayed by the client) */ + default: + if( flag&SD_ANIMATION && dmg.div_ < 2 ) //Disabling skill animation doesn't works on multi-hit. + type = 5; + if( bl->type == BL_SKILL ){ + TBL_SKILL *su = (TBL_SKILL*)bl; + if( su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP )// show damage on trap targets + clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); + } + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, type); + break; + } + + map_freeblock_lock(); + + if(damage > 0 && dmg.flag&BF_SKILL && tsd + && pc_checkskill(tsd,RG_PLAGIARISM) + && (!sc || !sc->data[SC_PRESERVE]) + && damage < tsd->battle_status.hp) + { //Updated to not be able to copy skills if the blow will kill you. [Skotlex] + int copy_skill = skill_id; + /** + * Copy Referal: dummy skills should point to their source upon copying + **/ + switch( skill_id ) { + case AB_DUPLELIGHT_MELEE: + case AB_DUPLELIGHT_MAGIC: + copy_skill = AB_DUPLELIGHT; + break; + case WL_CHAINLIGHTNING_ATK: + copy_skill = WL_CHAINLIGHTNING; + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + copy_skill = WM_REVERBERATION; + break; + case WM_SEVERE_RAINSTORM_MELEE: + copy_skill = WM_SEVERE_RAINSTORM; + break; + case GN_CRAZYWEED_ATK: + copy_skill = GN_CRAZYWEED; + break; + case GN_HELLS_PLANT_ATK: + copy_skill = GN_HELLS_PLANT; + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + copy_skill = LG_OVERBRAND; + break; + } + + if ((tsd->status.skill[copy_skill].id == 0 || tsd->status.skill[copy_skill].flag == SKILL_FLAG_PLAGIARIZED) && + can_copy(tsd,copy_skill,bl)) // Split all the check into their own function [Aru] + { + int lv; + if( sc && sc->data[SC__REPRODUCE] && (lv = sc->data[SC__REPRODUCE]->val1) ) { + //Level dependent and limitation. + lv = min(lv,skill_get_max(copy_skill)); + if( tsd->reproduceskill_id && tsd->status.skill[tsd->reproduceskill_id].flag == SKILL_FLAG_PLAGIARIZED ) { + tsd->status.skill[tsd->reproduceskill_id].id = 0; + tsd->status.skill[tsd->reproduceskill_id].lv = 0; + tsd->status.skill[tsd->reproduceskill_id].flag = 0; + clif_deleteskill(tsd,tsd->reproduceskill_id); + } + + tsd->reproduceskill_id = copy_skill; + pc_setglobalreg(tsd, "REPRODUCE_SKILL", copy_skill); + pc_setglobalreg(tsd, "REPRODUCE_SKILL_LV", lv); + + tsd->status.skill[copy_skill].id = copy_skill; + tsd->status.skill[copy_skill].lv = lv; + tsd->status.skill[copy_skill].flag = SKILL_FLAG_PLAGIARIZED; + clif_addskill(tsd,copy_skill); + } else { + lv = skill_lv; + if (tsd->cloneskill_id && tsd->status.skill[tsd->cloneskill_id].flag == SKILL_FLAG_PLAGIARIZED){ + tsd->status.skill[tsd->cloneskill_id].id = 0; + tsd->status.skill[tsd->cloneskill_id].lv = 0; + tsd->status.skill[tsd->cloneskill_id].flag = 0; + clif_deleteskill(tsd,tsd->cloneskill_id); + } + + if ((type = pc_checkskill(tsd,RG_PLAGIARISM)) < lv) + lv = type; + + tsd->cloneskill_id = copy_skill; + pc_setglobalreg(tsd, "CLONE_SKILL", copy_skill); + pc_setglobalreg(tsd, "CLONE_SKILL_LV", lv); + + tsd->status.skill[skill_id].id = copy_skill; + tsd->status.skill[skill_id].lv = lv; + tsd->status.skill[skill_id].flag = SKILL_FLAG_PLAGIARIZED; + clif_addskill(tsd,skill_id); + } + } + } + + if (dmg.dmg_lv >= ATK_MISS && (type = skill_get_walkdelay(skill_id, skill_lv)) > 0) + { //Skills with can't walk delay also stop normal attacking for that + //duration when the attack connects. [Skotlex] + struct unit_data *ud = unit_bl2ud(src); + if (ud && DIFF_TICK(ud->attackabletime, tick + type) < 0) + ud->attackabletime = tick + type; + } + + if( !dmg.amotion ) + { //Instant damage + if( !sc || (!sc->data[SC_DEVOTION] && skill_id != CR_REFLECTSHIELD) ) + status_fix_damage(src,bl,damage,dmg.dmotion); //Deal damage before knockback to allow stuff like firewall+storm gust combo. + if( !status_isdead(bl) ) + skill_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,dmg.dmg_lv,tick); + if( damage > 0 ) //Counter status effects [Skotlex] + skill_counter_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,tick); + } + // Hell Inferno burning status only starts if Fire part hits. + if( skill_id == WL_HELLINFERNO && dmg.damage > 0 ) + sc_start4(bl,SC_BURNING,55+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + // Apply knock back chance in SC_TRIANGLESHOT skill. + else if( skill_id == SC_TRIANGLESHOT && rnd()%100 > (1 + skill_lv) ) + dmg.blewcount = 0; + + //Only knockback if it's still alive, otherwise a "ghost" is left behind. [Skotlex] + //Reflected spells do not bounce back (bl == dsrc since it only happens for direct skills) + if (dmg.blewcount > 0 && bl!=dsrc && !status_isdead(bl)) { + int8 dir = -1; // default + switch(skill_id) {//direction + case MG_FIREWALL: + case PR_SANCTUARY: + case SC_TRIANGLESHOT: + case LG_OVERBRAND: + case SR_KNUCKLEARROW: + case GN_WALLOFTHORN: + case EL_FIRE_MANTLE: + dir = unit_getdir(bl);// backwards + break; + // This ensures the storm randomly pushes instead of exactly a cell backwards per official mechanics. + case WZ_STORMGUST: + dir = rand()%8; + break; + case WL_CRIMSONROCK: + dir = map_calc_dir(bl,skill_area_temp[4],skill_area_temp[5]); + break; + + } + //blown-specific handling + switch( skill_id ) { + case LG_OVERBRAND: + if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) ) { + short dir_x, dir_y; + dir_x = dirx[(dir+4)%8]; + dir_y = diry[(dir+4)%8]; + if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) + skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); + } else + skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); + break; + case SR_KNUCKLEARROW: + if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) && !(flag&4) ) { + short dir_x, dir_y; + dir_x = dirx[(dir+4)%8]; + dir_y = diry[(dir+4)%8]; + if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) + skill_addtimerskill(src, tick + 300 * ((flag&2) ? 1 : 2), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|4); + } + break; + case GN_WALLOFTHORN: + unit_stop_walking(bl,1); + skill_blown(dsrc,bl,dmg.blewcount,dir, 0x2 ); + clif_fixpos(bl); + break; + default: + skill_blown(dsrc,bl,dmg.blewcount,dir, 0x0 ); + if ( !dmg.blewcount && bl->type == BL_SKILL && damage > 0 ){ + TBL_SKILL *su = (TBL_SKILL*)bl; + if( su->group && su->group->skill_id == HT_BLASTMINE) + skill_blown(src, bl, 3, -1, 0); + } + break; + } + } + + //Delayed damage must be dealt after the knockback (it needs to know actual position of target) + if (dmg.amotion) + battle_delay_damage(tick, dmg.amotion,src,bl,dmg.flag,skill_id,skill_lv,damage,dmg.dmg_lv,dmg.dmotion); + + if( sc && sc->data[SC_DEVOTION] && skill_id != PA_PRESSURE ) + { + struct status_change_entry *sce = sc->data[SC_DEVOTION]; + struct block_list *d_bl = map_id2bl(sce->val1); + + if( d_bl && ( + (d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == bl->id) || + (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == bl->id) + ) && check_distance_bl(bl, d_bl, sce->val3) ) + { + if(!rmdamage){ + clif_damage(d_bl,d_bl, gettick(), 0, 0, damage, 0, 0, 0); + status_fix_damage(NULL,d_bl, damage, 0); + } + else{//Reflected magics are done directly on the target not on paladin + //This check is only for magical skill. + //For BF_WEAPON skills types track var rdamage and function battle_calc_return_damage + clif_damage(bl,bl, gettick(), 0, 0, damage, 0, 0, 0); + status_fix_damage(bl,bl, damage, 0); + } + } + else { + status_change_end(bl, SC_DEVOTION, INVALID_TIMER); + if( !dmg.amotion ) + status_fix_damage(src,bl,damage,dmg.dmotion); + } + } + + if(damage > 0 && !(tstatus->mode&MD_BOSS)) { + if( skill_id == RG_INTIMIDATE ) { + int rate = 50 + skill_lv * 5; + rate = rate + (status_get_lv(src) - status_get_lv(bl)); + if(rnd()%100 < rate) + skill_addtimerskill(src,tick + 800,bl->id,0,0,skill_id,skill_lv,0,flag); + } else if( skill_id == SC_FATALMENACE ) + skill_addtimerskill(src,tick + 800,bl->id,skill_area_temp[4],skill_area_temp[5],skill_id,skill_lv,0,flag); + } + + if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS) + dmg.flag |= BF_WEAPON; + + if( sd && src != bl && damage > 0 && ( dmg.flag&BF_WEAPON || + (dmg.flag&BF_MISC && (skill_id == RA_CLUSTERBOMB || skill_id == RA_FIRINGTRAP || skill_id == RA_ICEBOUNDTRAP || skill_id == RK_DRAGONBREATH)) ) ) + { + if (battle_config.left_cardfix_to_right) + battle_drain(sd, bl, dmg.damage, dmg.damage, tstatus->race, tstatus->mode&MD_BOSS); + else + battle_drain(sd, bl, dmg.damage, dmg.damage2, tstatus->race, tstatus->mode&MD_BOSS); + } + + if( rdamage > 0 ) { + if( sc && sc->data[SC_REFLECTDAMAGE] ) { + if( src != bl )// Don't reflect your own damage (Grand Cross) + map_foreachinshootrange(battle_damage_area,bl,skill_get_splash(LG_REFLECTDAMAGE,1),BL_CHAR,tick,bl,dmg.amotion,sstatus->dmotion,rdamage,tstatus->race); + } else { + if( dmg.amotion ) + battle_delay_damage(tick, dmg.amotion,bl,src,0,CR_REFLECTSHIELD,0,rdamage,ATK_DEF,0); + else + status_fix_damage(bl,src,rdamage,0); + clif_damage(src,src,tick, dmg.amotion,0,rdamage,1,4,0); // in aegis damage reflected is shown in single hit. + //Use Reflect Shield to signal this kind of skill trigger. [Skotlex] + if( tsd && src != bl ) + battle_drain(tsd, src, rdamage, rdamage, sstatus->race, is_boss(src)); + skill_additional_effect(bl, src, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); + } + } + if( damage > 0 ) { + /** + * Post-damage effects + **/ + switch( skill_id ) { + case RK_CRUSHSTRIKE: + skill_break_equip(src,EQP_WEAPON,2000,BCT_SELF); // 20% chance to destroy the weapon. + break; + case GC_VENOMPRESSURE: { + struct status_change *ssc = status_get_sc(src); + if( ssc && ssc->data[SC_POISONINGWEAPON] && rnd()%100 < 70 + 5*skill_lv ) { + sc_start(bl,ssc->data[SC_POISONINGWEAPON]->val2,100,ssc->data[SC_POISONINGWEAPON]->val1,skill_get_time2(GC_POISONINGWEAPON, 1)); + status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + } + break; + case WM_METALICSOUND: + status_zap(bl, 0, damage*100/(100*(110-pc_checkskill(sd,WM_LESSON)*10))); + break; + case SR_TIGERCANNON: + status_zap(bl, 0, damage/10); // 10% of damage dealt + break; + } + if( sd ) + skill_onskillusage(sd, bl, skill_id, tick); + } + + if (!(flag&2) && + ( + skill_id == MG_COLDBOLT || skill_id == MG_FIREBOLT || skill_id == MG_LIGHTNINGBOLT + ) && + (sc = status_get_sc(src)) && + sc->data[SC_DOUBLECAST] && + rnd() % 100 < sc->data[SC_DOUBLECAST]->val2) + { +// skill_addtimerskill(src, tick + dmg.div_*dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); + skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); + } + + map_freeblock_unlock(); + + return damage; +} + +/*========================================== + * sub fonction for recursive skill call. + * Checking bl battle flag and display dammage + * then call func with source,target,skill_id,skill_lv,tick,flag + *------------------------------------------*/ +typedef int (*SkillFunc)(struct block_list *, struct block_list *, int, int, unsigned int, int); +int skill_area_sub (struct block_list *bl, va_list ap) +{ + struct block_list *src; + uint16 skill_id,skill_lv; + int flag; + unsigned int tick; + SkillFunc func; + + nullpo_ret(bl); + + src=va_arg(ap,struct block_list *); + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + func=va_arg(ap,SkillFunc); + + if(battle_check_target(src,bl,flag) > 0) + { + // several splash skills need this initial dummy packet to display correctly + if (flag&SD_PREAMBLE && skill_area_temp[2] == 0) + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + + if (flag&(SD_SPLASH|SD_PREAMBLE)) + skill_area_temp[2]++; + + return func(src,bl,skill_id,skill_lv,tick,flag); + } + return 0; +} + +static int skill_check_unit_range_sub (struct block_list *bl, va_list ap) +{ + struct skill_unit *unit; + uint16 skill_id,g_skill_id; + + unit = (struct skill_unit *)bl; + + if(bl->prev == NULL || bl->type != BL_SKILL) + return 0; + + if(!unit->alive) + return 0; + + skill_id = va_arg(ap,int); + g_skill_id = unit->group->skill_id; + + switch (skill_id) { + case MH_STEINWAND: + case MG_SAFETYWALL: + case AL_PNEUMA: + case SC_MAELSTROM: + if(g_skill_id != MH_STEINWAND && g_skill_id != MG_SAFETYWALL && g_skill_id != AL_PNEUMA && g_skill_id != SC_MAELSTROM) + return 0; + break; + case AL_WARP: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case HP_BASILICA: + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + case SC_DIMENSIONDOOR: + case SC_BLOODYLUST: + //Non stackable on themselves and traps (including venom dust which does not has the trap inf2 set) + if (skill_id != g_skill_id && !(skill_get_inf2(g_skill_id)&INF2_TRAP) && g_skill_id != AS_VENOMDUST && g_skill_id != MH_POISON_MIST) + return 0; + break; + default: //Avoid stacking with same kind of trap. [Skotlex] + if (g_skill_id != skill_id) + return 0; + break; + } + + return 1; +} + +static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) +{ + //Non players do not check for the skill's splash-trigger area. + int range = bl->type==BL_PC?skill_get_unit_range(skill_id, skill_lv):0; + int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); + if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { + ShowError("skill_check_unit_range: unsupported layout type %d for skill %d\n",layout_type,skill_id); + return 0; + } + + range += layout_type; + return map_foreachinarea(skill_check_unit_range_sub,bl->m,x-range,y-range,x+range,y+range,BL_SKILL,skill_id); +} + +static int skill_check_unit_range2_sub (struct block_list *bl, va_list ap) +{ + uint16 skill_id; + + if(bl->prev == NULL) + return 0; + + skill_id = va_arg(ap,int); + + if( status_isdead(bl) && skill_id != AL_WARP ) + return 0; + + if( skill_id == HP_BASILICA && bl->type == BL_PC ) + return 0; + + if( skill_id == AM_DEMONSTRATION && bl->type == BL_MOB && ((TBL_MOB*)bl)->class_ == MOBID_EMPERIUM ) + return 0; //Allow casting Bomb/Demonstration Right under emperium [Skotlex] + return 1; +} + +static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) +{ + int range, type; + + switch (skill_id) { // to be expanded later + case WZ_ICEWALL: + range = 2; + break; + default: + { + int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); + if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { + ShowError("skill_check_unit_range2: unsupported layout type %d for skill %d\n",layout_type,skill_id); + return 0; + } + range = skill_get_unit_range(skill_id,skill_lv) + layout_type; + } + break; + } + + // if the caster is a monster/NPC, only check for players + // otherwise just check characters + if (bl->type == BL_PC) + type = BL_CHAR; + else + type = BL_PC; + + return map_foreachinarea(skill_check_unit_range2_sub, bl->m, + x - range, y - range, x + range, y + range, + type, skill_id); +} + +int skill_guildaura_sub (struct map_session_data* sd, int id, int strvit, int agidex) +{ + if(id == sd->bl.id && battle_config.guild_aura&16) + return 0; // Do not affect guild leader + + if (sd->sc.data[SC_GUILDAURA]) { + struct status_change_entry *sce = sd->sc.data[SC_GUILDAURA]; + if( sce->val3 != strvit || sce->val4 != agidex ) { + sce->val3 = strvit; + sce->val4 = agidex; + status_calc_bl(&sd->bl, status_sc2scb_flag(SC_GUILDAURA)); + } + return 0; + } + sc_start4(&sd->bl, SC_GUILDAURA,100, 1, id, strvit, agidex, 1000); + return 1; +} + +/*========================================== + * Checks that you have the requirements for casting a skill for homunculus/mercenary. + * Flag: + * &1: finished casting the skill (invoke hp/sp/item consumption) + * &2: picked menu entry (Warp Portal, Teleport and other menu based skills) + *------------------------------------------*/ +static int skill_check_condition_mercenary(struct block_list *bl, int skill, int lv, int type) +{ + struct status_data *status; + struct map_session_data *sd = NULL; + int i, hp, sp, hp_rate, sp_rate, state, mhp; + uint16 idx; + int itemid[MAX_SKILL_ITEM_REQUIRE],amount[ARRAYLENGTH(itemid)],index[ARRAYLENGTH(itemid)]; + + if( lv < 1 || lv > MAX_SKILL_LEVEL ) + return 0; + nullpo_ret(bl); + + switch( bl->type ) + { + case BL_HOM: sd = ((TBL_HOM*)bl)->master; break; + case BL_MER: sd = ((TBL_MER*)bl)->master; break; + } + + status = status_get_status_data(bl); + if( (idx = skill_get_index(skill)) == 0 ) + return 0; + + // Requeriments + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + itemid[i] = skill_db[idx].itemid[i]; + amount[i] = skill_db[idx].amount[i]; + } + hp = skill_db[idx].hp[lv-1]; + sp = skill_db[idx].sp[lv-1]; + hp_rate = skill_db[idx].hp_rate[lv-1]; + sp_rate = skill_db[idx].sp_rate[lv-1]; + state = skill_db[idx].state; + if( (mhp = skill_db[idx].mhp[lv-1]) > 0 ) + hp += (status->max_hp * mhp) / 100; + if( hp_rate > 0 ) + hp += (status->hp * hp_rate) / 100; + else + hp += (status->max_hp * (-hp_rate)) / 100; + if( sp_rate > 0 ) + sp += (status->sp * sp_rate) / 100; + else + sp += (status->max_sp * (-sp_rate)) / 100; + + if( bl->type == BL_HOM ) + { // Intimacy Requeriments + struct homun_data *hd = BL_CAST(BL_HOM, bl); + switch( skill ) + { + case HFLI_SBR44: + if( hd->homunculus.intimacy <= 200 ) + return 0; + break; + case HVAN_EXPLOSION: + if( hd->homunculus.intimacy < (unsigned int)battle_config.hvan_explosion_intimate ) + return 0; + break; + } + } + + if( !(type&2) ) + { + if( hp > 0 && status->hp <= (unsigned int)hp ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_HP_INSUFFICIENT, 0); + return 0; + } + if( sp > 0 && status->sp <= (unsigned int)sp ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_SP_INSUFFICIENT, 0); + return 0; + } + } + + if( !type ) + switch( state ) + { + case ST_MOVE_ENABLE: + if( !unit_can_move(bl) ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); + return 0; + } + break; + } + if( !(type&1) ) + return 1; + + // Check item existences + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + index[i] = -1; + if( itemid[i] < 1 ) continue; // No item + index[i] = pc_search_inventory(sd, itemid[i]); + if( index[i] < 0 || sd->status.inventory[index[i]].amount < amount[i] ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + + // Consume items + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + if( index[i] >= 0 ) pc_delitem(sd, index[i], amount[i], 0, 1, LOG_TYPE_CONSUME); + } + + if( type&2 ) + return 1; + + if( sp || hp ) + status_zap(bl, hp, sp); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_area_sub_count (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *src = map_id2bl(id),*target; + struct unit_data *ud = unit_bl2ud(src); + struct skill_timerskill *skl = NULL; + int range; + + nullpo_ret(src); + nullpo_ret(ud); + skl = ud->skilltimerskill[data]; + nullpo_ret(skl); + ud->skilltimerskill[data] = NULL; + + do { + if(src->prev == NULL) + break; // Source not on Map + if(skl->target_id) { + target = map_id2bl(skl->target_id); + if( ( skl->skill_id == RG_INTIMIDATE || skl->skill_id == SC_FATALMENACE ) && (!target || target->prev == NULL || !check_distance_bl(src,target,AREA_SIZE)) ) + target = src; //Required since it has to warp. + if(target == NULL) + break; // Target offline? + if(target->prev == NULL) + break; // Target not on Map + if(src->m != target->m) + break; // Different Maps + if(status_isdead(src)) + break; // Caster is Dead + if(status_isdead(target) && skl->skill_id != RG_INTIMIDATE && skl->skill_id != WZ_WATERBALL) + break; + + switch(skl->skill_id) { + case RG_INTIMIDATE: + if (unit_warp(src,-1,-1,-1,CLR_TELEPORT) == 0) { + short x,y; + map_search_freecell(src, 0, &x, &y, 1, 1, 0); + if (target != src && !status_isdead(target)) + unit_warp(target, -1, x, y, CLR_TELEPORT); + } + break; + case BA_FROSTJOKER: + case DC_SCREAM: + range= skill_get_splash(skl->skill_id, skl->skill_lv); + map_foreachinarea(skill_frostjoke_scream,skl->map,skl->x-range,skl->y-range, + skl->x+range,skl->y+range,BL_CHAR,src,skl->skill_id,skl->skill_lv,tick); + break; + case NPC_EARTHQUAKE: + if( skl->type > 1 ) + skill_addtimerskill(src,tick+250,src->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); + skill_area_temp[0] = map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, src, skl->skill_id, skl->skill_lv, tick, BCT_ENEMY, skill_area_sub_count); + skill_area_temp[1] = src->id; + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), splash_target(src), src, skl->skill_id, skl->skill_lv, tick, skl->flag, skill_castend_damage_id); + break; + case WZ_WATERBALL: + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if (!status_isdead(target)) + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + if (skl->type>1 && !status_isdead(target) && !status_isdead(src)) { + skill_addtimerskill(src,tick+125,target->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); + } else { + struct status_change *sc = status_get_sc(src); + if(sc) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == skl->skill_id) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + } + } + break; + /** + * Warlock + **/ + case WL_CHAINLIGHTNING_ATK: + { + struct block_list *nbl = NULL; // Next Target of Chain + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); // Hit a Lightning on the current Target + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if( skl->type > 1 ) + { // Remaining Chains Hit + nbl = battle_getenemyarea(src,target->x,target->y,2,BL_CHAR|BL_SKILL,target->id); // Search for a new Target around current one... + if( nbl == NULL && skl->x > 1 ) + { + nbl = target; + skl->x--; + } + else skl->x = 3; + } + + if( nbl ) + skill_addtimerskill(src,tick+status_get_adelay(src),nbl->id,skl->x,0,WL_CHAINLIGHTNING_ATK,skl->skill_lv,skl->type-1,skl->flag); + } + break; + case WL_TETRAVORTEX_FIRE: + case WL_TETRAVORTEX_WATER: + case WL_TETRAVORTEX_WIND: + case WL_TETRAVORTEX_GROUND: + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag|SD_ANIMATION); + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if( skl->type >= 3 ) + { // Final Hit + if( !status_isdead(target) ) + { // Final Status Effect + int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN }, + applyeffects[4] = { 0, 0, 0, 0 }, + i, j = 0, k = 0; + for( i = 1; i <= 8; i = i + i ) + { + if( skl->x&i ) + { + applyeffects[j] = effects[k]; + j++; + } + k++; + } + if( j ) + { + i = applyeffects[rnd()%j]; + status_change_start(target, i, 10000, skl->skill_lv, + (i == SC_BURNING ? 1000 : 0), + (i == SC_BURNING ? src->id : 0), + 0, skill_get_time(WL_TETRAVORTEX,skl->skill_lv), 0); + } + } + } + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + skill_castend_damage_id(src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); // damage should split among targets + break; + case SC_FATALMENACE: + if( src == target ) // Casters Part + unit_warp(src, -1, skl->x, skl->y, 3); + else { // Target's Part + short x = skl->x, y = skl->y; + map_search_freecell(NULL, target->m, &x, &y, 2, 2, 1); + unit_warp(target,-1,x,y,3); + } + break; + case LG_MOONSLASHER: + case SR_WINDMILL: + if( target->type == BL_PC ) { + struct map_session_data *tsd = NULL; + if( (tsd = ((TBL_PC*)target)) && !pc_issit(tsd) ) { + pc_setsit(tsd); + skill_sit(tsd,1); + clif_sitting(&tsd->bl); + } + } + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + case SR_KNUCKLEARROW: + skill_attack(BF_WEAPON, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); + break; + case GN_SPORE_EXPLOSION: + map_foreachinrange(skill_area_sub, target, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, + src, skl->skill_id, skl->skill_lv, 0, skl->flag|1|BCT_ENEMY, skill_castend_damage_id); + break; + case CH_PALMSTRIKE: + { + struct status_change* tsc = status_get_sc(target); + struct status_change* sc = status_get_sc(src); + if( tsc && tsc->option&OPTION_HIDE || + sc && sc->option&OPTION_HIDE ){ + skill_blown(src,target,skill_get_blewcount(skl->skill_id, skl->skill_lv), -1, 0x0 ); + break; + } + } + default: + skill_attack(skl->type,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + break; + } + } + else { + if(src->m != skl->map) + break; + switch( skl->skill_id ) + { + case WZ_METEOR: + if( skl->type >= 0 ) + { + int x = skl->type>>16, y = skl->type&0xFFFF; + if( path_search_long(NULL, src->m, src->x, src->y, x, y, CELL_CHKWALL) ) + skill_unitsetting(src,skl->skill_id,skl->skill_lv,x,y,skl->flag); + if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) + clif_skill_poseffect(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,tick); + } + else if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,skl->flag); + break; + case GN_CRAZYWEED_ATK: + { + int dummy = 1, i = skill_get_unit_range(skl->skill_id,skl->skill_lv); + map_foreachinarea(skill_cell_overlap, src->m, skl->x-i, skl->y-i, skl->x+i, skl->y+i, BL_SKILL, skl->skill_id, &dummy, src); + } + case WL_EARTHSTRAIN: + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,(skl->type<<16)|skl->flag); + break; + + } + } + } while (0); + //Free skl now that it is no longer needed. + ers_free(skill_timer_ers, skl); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_addtimerskill (struct block_list *src, unsigned int tick, int target, int x,int y, uint16 skill_id, uint16 skill_lv, int type, int flag) +{ + int i; + struct unit_data *ud; + nullpo_retr(1, src); + if (src->prev == NULL) + return 0; + ud = unit_bl2ud(src); + nullpo_retr(1, ud); + + ARR_FIND( 0, MAX_SKILLTIMERSKILL, i, ud->skilltimerskill[i] == 0 ); + if( i == MAX_SKILLTIMERSKILL ) return 1; + + ud->skilltimerskill[i] = ers_alloc(skill_timer_ers, struct skill_timerskill); + ud->skilltimerskill[i]->timer = add_timer(tick, skill_timerskill, src->id, i); + ud->skilltimerskill[i]->src_id = src->id; + ud->skilltimerskill[i]->target_id = target; + ud->skilltimerskill[i]->skill_id = skill_id; + ud->skilltimerskill[i]->skill_lv = skill_lv; + ud->skilltimerskill[i]->map = src->m; + ud->skilltimerskill[i]->x = x; + ud->skilltimerskill[i]->y = y; + ud->skilltimerskill[i]->type = type; + ud->skilltimerskill[i]->flag = flag; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_cleartimerskill (struct block_list *src) +{ + int i; + struct unit_data *ud; + nullpo_ret(src); + ud = unit_bl2ud(src); + nullpo_ret(ud); + + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(ud->skilltimerskill[i]) { + delete_timer(ud->skilltimerskill[i]->timer, skill_timerskill); + ers_free(skill_timer_ers, ud->skilltimerskill[i]); + ud->skilltimerskill[i]=NULL; + } + } + return 1; +} +static int skill_ative_reverberation( struct block_list *bl, va_list ap) { + struct skill_unit *su = (TBL_SKILL*)bl; + struct skill_unit_group *sg; + if( bl->type != BL_SKILL ) + return 0; + if( su->alive && (sg = su->group) && sg->skill_id == WM_REVERBERATION ) { + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, bl, gettick()); + su->limit=DIFF_TICK(gettick(),sg->tick); + sg->unit_id = UNT_USED_TRAPS; + } + return 0; +} + +static int skill_reveal_trap (struct block_list *bl, va_list ap) +{ + TBL_SKILL *su = (TBL_SKILL*)bl; + if (su->alive && su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP) + { //Reveal trap. + //Change look is not good enough, the client ignores it as an actual trap still. [Skotlex] + //clif_changetraplook(bl, su->group->unit_id); + clif_skill_setunit(su); + return 1; + } + return 0; +} + +/*========================================== + * + * + *------------------------------------------*/ +int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data *sd = NULL; + struct status_data *tstatus; + struct status_change *sc; + + if (skill_id > 0 && !skill_lv) return 0; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if (src->m != bl->m) + return 1; + + if (bl->prev == NULL) + return 1; + + sd = BL_CAST(BL_PC, src); + + if (status_isdead(bl)) + return 1; + + if (skill_id && skill_get_type(skill_id) == BF_MAGIC && status_isimmune(bl) == 100) + { //GTB makes all targetted magic display miss with a single bolt. + sc_type sct = status_skill2sc(skill_id); + if(sct != SC_NONE) + status_change_end(bl, sct, INVALID_TIMER); + clif_skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill_get_hit(skill_id)); + return 1; + } + + sc = status_get_sc(src); + if (sc && !sc->count) + sc = NULL; //Unneeded + + tstatus = status_get_status_data(bl); + + map_freeblock_lock(); + + switch(skill_id) + { + case MER_CRASH: + case SM_BASH: + case MS_BASH: + case MC_MAMMONITE: + case TF_DOUBLE: + case AC_DOUBLE: + case MA_DOUBLE: + case AS_SONICBLOW: + case KN_PIERCE: + case ML_PIERCE: + case KN_SPEARBOOMERANG: + case TF_POISON: + case TF_SPRINKLESAND: + case AC_CHARGEARROW: + case MA_CHARGEARROW: + case RG_INTIMIDATE: + case AM_ACIDTERROR: + case BA_MUSICALSTRIKE: + case DC_THROWARROW: + case BA_DISSONANCE: + case CR_HOLYCROSS: + case NPC_DARKCROSS: + case CR_SHIELDCHARGE: + case CR_SHIELDBOOMERANG: + case NPC_PIERCINGATT: + case NPC_MENTALBREAKER: + case NPC_RANGEATTACK: + case NPC_CRITICALSLASH: + case NPC_COMBOATTACK: + case NPC_GUIDEDATTACK: + case NPC_POISON: + case NPC_RANDOMATTACK: + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + case NPC_UNDEADATTACK: + case NPC_ARMORBRAKE: + case NPC_WEAPONBRAKER: + case NPC_HELMBRAKE: + case NPC_SHIELDBRAKE: + case NPC_BLINDATTACK: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + case NPC_PETRIFYATTACK: + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case LK_AURABLADE: + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + case LK_HEADCRUSH: + case CG_ARROWVULCAN: + case HW_MAGICCRASHER: + case ITM_TOMAHAWK: + case MO_TRIPLEATTACK: + case CH_CHAINCRUSH: + case CH_TIGERFIST: + case PA_SHIELDCHAIN: // Shield Chain + case PA_SACRIFICE: + case WS_CARTTERMINATION: // Cart Termination + case AS_VENOMKNIFE: + case HT_PHANTASMIC: + case HT_POWER: + case TK_DOWNKICK: + case TK_COUNTER: + case GS_CHAINACTION: + case GS_TRIPLEACTION: + case GS_MAGICALBULLET: + case GS_TRACKING: + case GS_PIERCINGSHOT: + case GS_RAPIDSHOWER: + case GS_DUST: + case GS_DISARM: // Added disarm. [Reddozen] + case GS_FULLBUSTER: + case NJ_SYURIKEN: + case NJ_KUNAI: + case ASC_BREAKER: + case HFLI_MOON: //[orn] + case HFLI_SBR44: //[orn] + case NPC_BLEEDING: + case NPC_CRITICALWOUND: + case NPC_HELLPOWER: + case RK_SONICWAVE: + case RK_HUNDREDSPEAR: + case AB_DUPLELIGHT_MELEE: + case RA_AIMEDBOLT: + case NC_AXEBOOMERANG: + case NC_POWERSWING: + case GC_CROSSIMPACT: + case GC_VENOMPRESSURE: + case SC_TRIANGLESHOT: + case SC_FEINTBOMB: + case LG_BANISHINGPOINT: + case LG_SHIELDPRESS: + case LG_RAGEBURST: + case LG_RAYOFGENESIS: + case LG_HESPERUSLIT: + case SR_FALLENEMPIRE: + case SR_CRESCENTELBOW_AUTOSPELL: + case SR_GATEOFHELL: + case SR_GENTLETOUCH_QUIET: + case WM_SEVERE_RAINSTORM_MELEE: + case WM_GREAT_ECHO: + case GN_SLINGITEM_RANGEMELEEATK: + case KO_JYUMONJIKIRI: + case KO_SETSUDAN: + case KO_KAIHOU: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + /** + * Mechanic (MADO GEAR) + **/ + case NC_BOOSTKNUCKLE: + case NC_PILEBUNKER: + case NC_VULCANARM: + case NC_COLDSLOWER: + case NC_ARMSCANNON: + if (sd) pc_overheat(sd,1); + case RK_WINDCUTTER: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + break; + + case LK_JOINTBEAT: // decide the ailment first (affects attack damage and effect) + switch( rnd()%6 ){ + case 0: flag |= BREAK_ANKLE; break; + case 1: flag |= BREAK_WRIST; break; + case 2: flag |= BREAK_KNEE; break; + case 3: flag |= BREAK_SHOULDER; break; + case 4: flag |= BREAK_WAIST; break; + case 5: flag |= BREAK_NECK; break; + } + //TODO: is there really no cleaner way to do this? + sc = status_get_sc(bl); + if (sc) sc->jb_flag = flag; + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case MO_COMBOFINISH: + if (!(flag&1) && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) + { //Becomes a splash attack when Soul Linked. + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),splash_target(src), + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } else + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case TK_STORMKICK: // Taekwon kicks [Dralnu] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_area_temp[1] = 0; + map_foreachinrange(skill_attack_area, src, + skill_get_splash(skill_id, skill_lv), splash_target(src), + BF_WEAPON, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); + break; + + case KN_CHARGEATK: + { + bool path = path_search_long(NULL, src->m, src->x, src->y, bl->x, bl->y,CELL_CHKWALL); + unsigned int dist = distance_bl(src, bl); + uint8 dir = map_calc_dir(bl, src->x, src->y); + + // teleport to target (if not on WoE grounds) + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 0, 1) ) + clif_slide(src, bl->x, bl->y); + + // cause damage and knockback if the path to target was a straight one + if( path ) + { + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, dist); + skill_blown(src, bl, dist, dir, 0); + //HACK: since knockback officially defaults to the left, the client also turns to the left... therefore, + // make the caster look in the direction of the target + unit_setdir(src, (dir+4)%8); + } + + } + break; + + case NC_FLAMELAUNCHER: + if (sd) pc_overheat(sd,1); + case SN_SHARPSHOOTING: + case MA_SHARPSHOOTING: + case NJ_KAMAITACHI: + case LG_CANNONSPEAR: + //It won't shoot through walls since on castend there has to be a direct + //line of sight between caster and target. + skill_area_temp[1] = bl->id; + map_foreachinpath (skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, + skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), + skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + break; + + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_THUNDERBREATH: + skill_area_temp[1] = bl->id; + map_foreachinpath(skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, + skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), + skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + break; + + case MO_INVESTIGATE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case RG_BACKSTAP: + { + uint8 dir = map_calc_dir(src, bl->x, bl->y), t_dir = unit_getdir(bl); + if ((!check_distance_bl(src, bl, 0) && !map_check_dir(dir, t_dir)) || bl->type == BL_SKILL) { + status_change_end(src, SC_HIDING, INVALID_TIMER); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + dir = dir < 4 ? dir+4 : dir-4; // change direction [Celest] + unit_setdir(bl,dir); + } + else if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case MO_FINGEROFFENSIVE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + if (battle_config.finger_offensive_type && sd) { + int i; + for (i = 1; i < sd->spiritball_old; i++) + skill_addtimerskill(src, tick + i * 200, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); + } + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case MO_CHAINCOMBO: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case NJ_ISSEN: + status_change_end(src, SC_NEN, INVALID_TIMER); + status_change_end(src, SC_HIDING, INVALID_TIMER); + // fall through + case MO_EXTREMITYFIST: + { + short x, y, i = 2; // Move 2 cells for Issen(from target) + struct block_list *mbl = bl; + short dir = 0; + + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + + if( skill_id == MO_EXTREMITYFIST ) + { + mbl = src; + i = 3; // for Asura(from caster) + status_set_sp(src, 0, 0); + status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); +#ifdef RENEWAL + sc_start(src,SC_EXTREMITYFIST2,100,skill_lv,skill_get_time(skill_id,skill_lv)); +#endif + }else + status_set_hp(src, +#ifdef RENEWAL + max(status_get_max_hp(src)/100, 1) +#else + 1 +#endif + , 0); + + dir = map_calc_dir(src,bl->x,bl->y); + if( dir > 0 && dir < 4) x = -i; + else if( dir > 4 ) x = i; + else x = 0; + if( dir > 2 && dir < 6 ) y = -i; + else if( dir == 7 || dir < 2 ) y = i; + else y = 0; + if( (mbl == src || !map_flag_gvg(src->m) && !map[src->m].flag.battleground) && // only NJ_ISSEN don't have slide effect in GVG + unit_movepos(src, mbl->x+x, mbl->y+y, 1, 1) ) { + clif_slide(src, src->x, src->y); + //uncomment this if you want to remove MO_EXTREMITYFIST glitchy walking effect. [malufett] + //clif_fixpos(src); + } + } + break; + + //Splash attack skills. + case AS_GRIMTOOTH: + case MC_CARTREVOLUTION: + case NPC_SPLASHATTACK: + flag |= SD_PREAMBLE; // a fake packet will be sent for the first target to be hit + case AS_SPLASHER: + case SM_MAGNUM: + case MS_MAGNUM: + case HT_BLITZBEAT: + case AC_SHOWER: + case MA_SHOWER: + case MG_NAPALMBEAT: + case MG_FIREBALL: + case RG_RAID: + case HW_NAPALMVULCAN: + case NJ_HUUMA: + case NJ_BAKUENRYU: + case ASC_METEORASSAULT: + case GS_DESPERADO: + case GS_SPREADATTACK: + case NPC_EARTHQUAKE: + case NPC_PULSESTRIKE: + case NPC_HELLJUDGEMENT: + case NPC_VAMPIRE_GIFT: + case RK_IGNITIONBREAK: + case AB_JUDEX: + case WL_SOULEXPANSION: + case WL_CRIMSONROCK: + case WL_COMET: + case WL_JACKFROST: + case RA_ARROWSTORM: + case RA_WUGDASH: + case NC_SELFDESTRUCTION: + case NC_AXETORNADO: + case GC_ROLLINGCUTTER: + case GC_COUNTERSLASH: + case LG_MOONSLASHER: + case LG_EARTHDRIVE: + case SR_TIGERCANNON: + case SR_RAMPAGEBLASTER: + case SR_SKYNETBLOW: + case SR_WINDMILL: + case SR_RIDEINLIGHTNING: + case WM_SOUND_OF_DESTRUCTION: + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + case SO_VARETYR_SPEAR: + case GN_CART_TORNADO: + case GN_CARTCANNON: + case KO_HAPPOKUNAI: + case KO_HUUMARANKA: + case KO_MUCHANAGE: + case KO_BAKURETSU: + if( flag&1 ) {//Recursive invocation + // skill_area_temp[0] holds number of targets in area + // skill_area_temp[1] holds the id of the original target + // skill_area_temp[2] counts how many targets have already been processed + int sflag = skill_area_temp[0] & 0xFFF, heal; + if( flag&SD_LEVEL ) + sflag |= SD_LEVEL; // -1 will be used in packets instead of the skill level + if( skill_area_temp[1] != bl->id && !(skill_get_inf2(skill_id)&INF2_NPC_SKILL) ) + sflag |= SD_ANIMATION; // original target gets no animation (as well as all NPC skills) + + heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag); + if( skill_id == NPC_VAMPIRE_GIFT && heal > 0 ) { + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + status_heal(src,heal,0,0); + } + } else { + switch ( skill_id ) { + case NJ_BAKUENRYU: + case LG_EARTHDRIVE: + case GN_CARTCANNON: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case LG_MOONSLASHER: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + case NPC_EARTHQUAKE://FIXME: Isn't EarthQuake a ground skill after all? + skill_addtimerskill(src,tick+250,src->id,0,0,skill_id,skill_lv,2,flag|BCT_ENEMY|SD_SPLASH|1); + default: + break; + } + + skill_area_temp[0] = 0; + skill_area_temp[1] = bl->id; + skill_area_temp[2] = 0; + if( skill_id == WL_CRIMSONROCK ) { + skill_area_temp[4] = bl->x; + skill_area_temp[5] = bl->y; + } + if( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC ) + skill_area_temp[1] = 0; + // if skill damage should be split among targets, count them + //SD_LEVEL -> Forced splash damage for Auto Blitz-Beat -> count targets + //special case: Venom Splasher uses a different range for searching than for splashing + if( flag&SD_LEVEL || skill_get_nk(skill_id)&NK_SPLASHSPLIT ) + skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, (skill_id == AS_SPLASHER)?1:skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); + + // recursive invocation of skill_castend_damage_id() with flag|1 + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), ( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC )?BL_CHAR:splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + } + break; + + case KN_BRANDISHSPEAR: + case ML_BRANDISH: + //Coded apart for it needs the flag passed to the damage calculation. + if (skill_area_temp[1] != bl->id) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); + else + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case KN_BOWLINGBASH: + case MS_BOWLINGBASH: + if(flag&1){ + if(bl->id==skill_area_temp[1]) + break; + //two hits for 500% + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); + } else { + int i,c; + c = skill_get_blewcount(skill_id,skill_lv); + // keep moving target in the direction that src is looking, square by square + for(i=0;i<c;i++){ + if (!skill_blown(src,bl,1,(unit_getdir(src)+4)%8,0x1)) + break; //Can't knockback + skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_area_sub_count); + if( skill_area_temp[0] > 1 ) break; // collision + } + clif_blown(bl); //Update target pos. + if (i!=c) { //Splash + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + } + //Weirdo dual-hit property, two attacks for 500% + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); + } + break; + + case KN_SPEARSTAB: + if(flag&1) { + if (bl->id==skill_area_temp[1]) + break; + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION)) + skill_blown(src,bl,skill_area_temp[2],-1,0); + } else { + int x=bl->x,y=bl->y,i,dir; + dir = map_calc_dir(bl,src->x,src->y); + skill_area_temp[1] = bl->id; + skill_area_temp[2] = skill_get_blewcount(skill_id,skill_lv); + // all the enemies between the caster and the target are hit, as well as the target + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0)) + skill_blown(src,bl,skill_area_temp[2],-1,0); + for (i=0;i<4;i++) { + map_foreachincell(skill_area_sub,bl->m,x,y,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + x += dirx[dir]; + y += diry[dir]; + } + } + break; + + case TK_TURNKICK: + case MO_BALKYOUNG: //Active part of the attack. Skill-attack [Skotlex] + { + skill_area_temp[1] = bl->id; //NOTE: This is used in skill_castend_nodamage_id to avoid affecting the target. + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag)) + map_foreachinrange(skill_area_sub,bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1, + skill_castend_nodamage_id); + } + break; + case CH_PALMSTRIKE: // Palm Strike takes effect 1sec after casting. [Skotlex] + // clif_skill_nodamage(src,bl,skill_id,skill_lv,0); //Can't make this one display the correct attack animation delay :/ + clif_damage(src,bl,tick,status_get_amotion(src),0,-1,1,4,0); //Display an absorbed damage attack. + skill_addtimerskill(src, tick + (1000+status_get_amotion(src)), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); + break; + + case PR_TURNUNDEAD: + case ALL_RESURRECTION: + if (!battle_check_undead(tstatus->race, tstatus->def_ele)) + break; + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case MG_SOULSTRIKE: + case NPC_DARKSTRIKE: + case MG_COLDBOLT: + case MG_FIREBOLT: + case MG_LIGHTNINGBOLT: + case WZ_EARTHSPIKE: + case AL_HEAL: + case AL_HOLYLIGHT: + case WZ_JUPITEL: + case NPC_DARKTHUNDER: + case PR_ASPERSIO: + case MG_FROSTDIVER: + case WZ_SIGHTBLASTER: + case WZ_SIGHTRASHER: + case NJ_KOUENKA: + case NJ_HYOUSENSOU: + case NJ_HUUJIN: + case AB_ADORAMUS: + case AB_RENOVATIO: + case AB_HIGHNESSHEAL: + case AB_DUPLELIGHT_MAGIC: + case WM_METALICSOUND: + case MH_ERASER_CUTTER: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NPC_MAGICALATTACK: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + sc_start(src,status_skill2sc(skill_id),100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case HVAN_CAPRICE: //[blackhole89] + { + int ran=rnd()%4; + int sid = 0; + switch(ran) + { + case 0: sid=MG_COLDBOLT; break; + case 1: sid=MG_FIREBOLT; break; + case 2: sid=MG_LIGHTNINGBOLT; break; + case 3: sid=WZ_EARTHSPIKE; break; + } + skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL); + } + break; + case WZ_WATERBALL: + { + int range = skill_lv / 2; + int maxlv = skill_get_max(skill_id); // learnable level + int count = 0; + int x, y; + struct skill_unit* unit; + + if( skill_lv > maxlv ) + { + if( src->type == BL_MOB && skill_lv == 10 ) + range = 4; + else + range = maxlv / 2; + } + + for( y = src->y - range; y <= src->y + range; ++y ) + for( x = src->x - range; x <= src->x + range; ++x ) + { + if( !map_find_skill_unit_oncell(src,x,y,SA_LANDPROTECTOR,NULL,1) ) + { + if( src->type != BL_PC || map_getcell(src->m,x,y,CELL_CHKWATER) ) // non-players bypass the water requirement + count++; // natural water cell + else if( (unit = map_find_skill_unit_oncell(src,x,y,SA_DELUGE,NULL,1)) != NULL || (unit = map_find_skill_unit_oncell(src,x,y,NJ_SUITON,NULL,1)) != NULL ) + { + count++; // skill-induced water cell + skill_delunit(unit); // consume cell + } + } + } + + if( count > 1 ) // queue the remaining count - 1 timerskill Waterballs + skill_addtimerskill(src,tick+150,bl->id,0,0,skill_id,skill_lv,count-1,flag); + } + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case PR_BENEDICTIO: + //Should attack undead and demons. [Skotlex] + if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON) + skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case SL_SMA: + status_change_end(src, SC_SMA, INVALID_TIMER); + case SL_STIN: + case SL_STUN: + if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NPC_DARKBREATH: + clif_emotion(src,E_AG); + case SN_FALCONASSAULT: + case PA_PRESSURE: + case CR_ACIDDEMONSTRATION: + case TF_THROWSTONE: + case NPC_SMOKING: + case GS_FLING: + case NJ_ZENYNAGE: + case GN_THORNS_TRAP: + case GN_HELLS_PLANT_ATK: + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + /** + * Rune Knight + **/ + case RK_DRAGONBREATH: { + struct status_change *tsc = NULL; + if( (tsc = status_get_sc(bl)) && (tsc->data[SC_HIDING] )) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } else + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case NPC_SELFDESTRUCTION: { + struct status_change *tsc = NULL; + if( (tsc = status_get_sc(bl)) && tsc->data[SC_HIDING] ) + break; + } + case HVAN_EXPLOSION: + if (src != bl) + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + // Celest + case PF_SOULBURN: + if (rnd()%100 < (skill_lv < 5 ? 30 + skill_lv * 10 : 70)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (skill_lv == 5) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + status_percent_damage(src, bl, 0, 100, false); + } else { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + if (skill_lv == 5) + skill_attack(BF_MAGIC,src,src,src,skill_id,skill_lv,tick,flag); + status_percent_damage(src, src, 0, 100, false); + } + break; + + case NPC_BLOODDRAIN: + case NPC_ENERGYDRAIN: + { + int heal = skill_attack( (skill_id == NPC_BLOODDRAIN) ? BF_WEAPON : BF_MAGIC, + src, src, bl, skill_id, skill_lv, tick, flag); + if (heal > 0){ + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + status_heal(src, heal, 0, 0); + } + } + break; + + case GS_BULLSEYE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NJ_KASUMIKIRI: + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag) > 0) + sc_start(src,SC_HIDING,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case NJ_KIRIKAGE: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) + { //You don't move on GVG grounds. + short x, y; + map_search_freecell(bl, 0, &x, &y, 1, 1, 0); + if (unit_movepos(src, x, y, 0, 0)) + clif_slide(src,src->x,src->y); + } + status_change_end(src, SC_HIDING, INVALID_TIMER); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + case RK_PHANTOMTHRUST: + unit_setdir(src,map_calc_dir(src, bl->x, bl->y)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + skill_blown(src,bl,distance_bl(src,bl)-1,unit_getdir(src),0); + if( battle_check_target(src,bl,BCT_ENEMY) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case RK_STORMBLAST: + case RK_CRUSHSTRIKE: + if( sd ) { + if( pc_checkskill(sd,RK_RUNEMASTERY) >= ( skill_id == RK_CRUSHSTRIKE ? 7 : 3 ) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } else //non-sd support + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + case GC_DARKILLUSION: + { + short x, y; + short dir = map_calc_dir(src,bl->x,bl->y); + + if( dir > 0 && dir < 4) x = 2; + else if( dir > 4 ) x = -2; + else x = 0; + if( dir > 2 && dir < 6 ) y = 2; + else if( dir == 7 || dir < 2 ) y = -2; + else y = 0; + + if( unit_movepos(src, bl->x+x, bl->y+y, 1, 1) ) + { + clif_slide(src,bl->x+x,bl->y+y); + clif_fixpos(src); // the official server send these two packts. + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + if( rnd()%100 < 4 * skill_lv ) + skill_castend_damage_id(src,bl,GC_CROSSIMPACT,skill_lv,tick,flag); + } + + } + break; + + case GC_WEAPONCRUSH: + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_WEAPONBLOCKING,0); + break; + + case GC_CROSSRIPPERSLASHER: + if( sd && !(sc && sc->data[SC_ROLLINGCUTTER]) ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_CONDITION,0); + else + { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src,SC_ROLLINGCUTTER,INVALID_TIMER); + } + break; + + case GC_PHANTOMMENACE: + if( flag&1 ) + { // Only Hits Invisible Targets + struct status_change *tsc = status_get_sc(bl); + if(tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY]) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + case WL_CHAINLIGHTNING: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_addtimerskill(src,tick + 150,bl->id,3,0,WL_CHAINLIGHTNING_ATK,skill_lv,4+skill_lv,flag); + break; + case WL_DRAINLIFE: + { + int heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + int rate = 70 + 5 * skill_lv; + + heal = heal * (5 + 5 * skill_lv) / 100; + + if( bl->type == BL_SKILL ) + heal = 0; // Don't absorb heal from Ice Walls or other skill units. + + if( heal && rnd()%100 < rate ) + { + status_heal(src, heal, 0, 0); + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + } + } + break; + + case WL_TETRAVORTEX: + if( sd ) + { + int spheres[5] = { 0, 0, 0, 0, 0 }, + positions[5] = {-1,-1,-1,-1,-1 }, + i, j = 0, k, subskill = 0; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + if( sc && sc->data[i] ) + { + spheres[j] = i; + positions[j] = sc->data[i]->val2; + j++; // + } + + if( j < 4 ) + { // Need 4 spheres minimum + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + // Sphere Sort, this time from new to old + for( i = 0; i <= j - 2; i++ ) + for( k = i + 1; k <= j - 1; k++ ) + if( positions[i] < positions[k] ) + { + swap(positions[i],positions[k]); + swap(spheres[i],spheres[k]); + } + + k = 0; + for( i = 0; i < 4; i++ ) + { + switch( sc->data[spheres[i]]->val1 ) + { + case WLS_FIRE: subskill = WL_TETRAVORTEX_FIRE; k |= 1; break; + case WLS_WIND: subskill = WL_TETRAVORTEX_WIND; k |= 4; break; + case WLS_WATER: subskill = WL_TETRAVORTEX_WATER; k |= 2; break; + case WLS_STONE: subskill = WL_TETRAVORTEX_GROUND; k |= 8; break; + } + skill_addtimerskill(src, tick + i * 200, bl->id, k, 0, subskill, skill_lv, i, flag); + clif_skill_nodamage(src, bl, subskill, skill_lv, 1); + status_change_end(src, spheres[i], INVALID_TIMER); + } + } + break; + + case WL_RELEASE: + if( sd ) + { + int i; + // Priority is to release SpellBook + if( sc && sc->data[SC_READING_SB] ) + { // SpellBook + uint16 skill_id, skill_lv, point, s = 0; + int spell[SC_MAXSPELLBOOK-SC_SPELLBOOK1 + 1]; + + for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--) // List all available spell to be released + if( sc->data[i] ) spell[s++] = i; + + if ( s == 0 ) + break; + + i = spell[s==1?0:rand()%s];// Random select of spell to be released. + if( s && sc->data[i] ){// Now extract the data from the preserved spell + skill_id = sc->data[i]->val1; + skill_lv = sc->data[i]->val2; + point = sc->data[i]->val3; + status_change_end(src, (sc_type)i, INVALID_TIMER); + }else //something went wrong :( + break; + + if( sc->data[SC_READING_SB]->val2 > point ) + sc->data[SC_READING_SB]->val2 -= point; + else // Last spell to be released + status_change_end(src, SC_READING_SB, INVALID_TIMER); + + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + if( !skill_check_condition_castbegin(sd, skill_id, skill_lv) ) + break; + + switch( skill_get_casttype(skill_id) ) + { + case CAST_GROUND: + skill_castend_pos2(src, bl->x, bl->y, skill_id, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, bl, skill_id, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, bl, skill_id, skill_lv, tick, 0); + break; + } + + sd->ud.canact_tick = tick + skill_delayfix(src, skill_id, skill_lv); + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, skill_id, skill_lv), 0, 0, 0); + } + else + { // Summon Balls + int j = 0, k, skele; + int spheres[5] = { 0, 0, 0, 0, 0 }, + positions[5] = {-1,-1,-1,-1,-1 }; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + if( sc && sc->data[i] ) + { + spheres[j] = i; + positions[j] = sc->data[i]->val2; + sc->data[i]->val2--; // Prepares for next position + j++; + } + + if( j == 0 ) + { // No Spheres + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON_NONE,0); + break; + } + + // Sphere Sort + for( i = 0; i <= j - 2; i++ ) + for( k = i + 1; k <= j - 1; k++ ) + if( positions[i] > positions[k] ) + { + swap(positions[i],positions[k]); + swap(spheres[i],spheres[k]); + } + + if( skill_lv == 1 ) j = 1; // Limit only to one ball + for( i = 0; i < j; i++ ) + { + skele = WL_RELEASE - 5 + sc->data[spheres[i]]->val1 - WLS_FIRE; // Convert Ball Element into Skill ATK for balls + // WL_SUMMON_ATK_FIRE, WL_SUMMON_ATK_WIND, WL_SUMMON_ATK_WATER, WL_SUMMON_ATK_GROUND + skill_addtimerskill(src,tick+status_get_adelay(src)*i,bl->id,0,0,skele,sc->data[spheres[i]]->val3,BF_MAGIC,flag|SD_LEVEL); + status_change_end(src, spheres[i], INVALID_TIMER); // Eliminate ball + } + clif_skill_nodamage(src,bl,skill_id,0,1); + } + } + break; + case WL_FROSTMISTY: + // Causes Freezing status through walls. + sc_start(bl,status_skill2sc(skill_id),20+12*skill_lv+(sd ? sd->status.job_level : 50)/5,skill_lv,skill_get_time(skill_id,skill_lv)); + // Doesn't deal damage through non-shootable walls. + if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKWALL) ) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + break; + case WL_HELLINFERNO: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|ELE_DARK); + break; + case RA_WUGSTRIKE: + if( sd && pc_isridingwug(sd) ){ + short x[8]={0,-1,-1,-1,0,1,1,1}; + short y[8]={1,1,0,-1,-1,-1,0,1}; + uint8 dir = map_calc_dir(bl, src->x, src->y); + + if( unit_movepos(src, bl->x+x[dir], bl->y+y[dir], 1, 1) ) + { + clif_slide(src, bl->x+x[dir], bl->y+y[dir]); + clif_fixpos(src); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + } + break; + } + case RA_WUGBITE: + if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKNOREACH) ) { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + }else if( sd && skill_id == RA_WUGBITE ) // Only RA_WUGBITE has the skill fail message. + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + + break; + + case RA_SENSITIVEKEEN: + if( bl->type != BL_SKILL ) { // Only Hits Invisible Targets + struct status_change * tsc = status_get_sc(bl); + if( tsc && tsc->option&(OPTION_HIDE|OPTION_CLOAK) ){ + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + } + else + { + struct skill_unit *su = BL_CAST(BL_SKILL,bl); + struct skill_unit_group* sg; + + if( su && (sg=su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP ) + { + if( !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) + { + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = sg->item_id?sg->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + if( item_tmp.nameid ) + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); + } + skill_delunit(su); + } + } + break; + case NC_INFRAREDSCAN: + if( flag&1 ) + { //TODO: Need a confirmation if the other type of hidden status is included to be scanned. [Jobbie] + if( rnd()%100 < 50 ) + sc_start(bl, SC_INFRAREDSCAN, 10000, skill_lv, skill_get_time(skill_id, skill_lv)); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); // Need confirm it. + } + else + { + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( sd ) pc_overheat(sd,1); + } + break; + + case NC_MAGNETICFIELD: + sc_start2(bl,SC_MAGNETICFIELD,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)); + break; + case SC_FATALMENACE: + if( flag&1 ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else + { + short x, y; + map_search_freecell(src, 0, &x, &y, -1, -1, 0); + // Destination area + skill_area_temp[4] = x; + skill_area_temp[5] = y; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + skill_addtimerskill(src,tick + 800,src->id,x,y,skill_id,skill_lv,0,flag); // To teleport Self + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + } + break; + case LG_PINPOINTATTACK: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) + clif_slide(src,bl->x,bl->y); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case LG_SHIELDSPELL: + // flag&1: Phisycal Attack, flag&2: Magic Attack. + skill_attack((flag&1)?BF_WEAPON:BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case LG_OVERBRAND: + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); + break; + + case LG_OVERBRAND_BRANDISH: + skill_addtimerskill(src, tick + status_get_amotion(src)*8/10, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL); + break; + case SR_DRAGONCOMBO: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case SR_KNUCKLEARROW: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) { + clif_slide(src,bl->x,bl->y); + clif_fixpos(src); // Aegis send this packet too. + } + + if( flag&1 ) + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); + else + skill_addtimerskill(src, tick + 300, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL|2); + break; + + case SR_HOWLINGOFLION: + status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + status_change_end(bl, SC_SIRCLEOFNATURE, INVALID_TIMER); + status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); + break; + + case SR_EARTHSHAKER: + if( flag&1 ) { //by default cloaking skills are remove by aoe skills so no more checking/removing except hiding and cloaking exceed. + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } else{ + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + } + break; + + case WM_LULLABY_DEEPSLEEP: + if( bl != src && rnd()%100 < 88 + 2 * skill_lv ) + sc_start(bl,status_skill2sc(skill_id),100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case SO_POISON_BUSTER: { + struct status_change *tsc = status_get_sc(bl); + if( tsc && tsc->data[SC_POISON] ) { + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + status_change_end(bl, SC_POISON, INVALID_TIMER); + } + else if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + } + break; + + case GN_SPORE_EXPLOSION: + if( flag&1 ) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + else { + clif_skill_nodamage(src, bl, skill_id, 0, 1); + skill_addtimerskill(src, gettick() + skill_get_time(skill_id, skill_lv) - 1000, bl->id, 0, 0, skill_id, skill_lv, 0, 0); + } + break; + + case EL_FIRE_BOMB: + case EL_FIRE_WAVE: + case EL_WATER_SCREW: + case EL_HURRICANE: + case EL_TYPOON_MIS: + if( flag&1 ) + skill_attack(skill_get_type(skill_id+1),src,src,bl,skill_id+1,skill_lv,tick,flag); + else { + int i = skill_get_splash(skill_id,skill_lv); + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 30 ) + map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case EL_ROCK_CRUSHER: + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 50 ) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + else + skill_attack(BF_WEAPON,src,src,bl,EL_ROCK_CRUSHER_ATK,skill_lv,tick,flag); + break; + + case EL_STONE_RAIN: + if( flag&1 ) + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + else { + int i = skill_get_splash(skill_id,skill_lv); + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 30 ) + map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case EL_FIRE_ARROW: + case EL_ICE_NEEDLE: + case EL_WIND_SLASH: + case EL_STONE_HAMMER: + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case EL_TIDAL_WEAPON: + if( src->type == BL_ELEM ) { + struct elemental_data *ele = BL_CAST(BL_ELEM,src); + struct status_change *sc = status_get_sc(&ele->bl); + struct status_change *tsc = status_get_sc(bl); + sc_type type = status_skill2sc(skill_id), type2; + type2 = type-1; + + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } + if( rnd()%100 < 50 ) + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + else { + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(battle_get_master(src),type,100,ele->bl.id,skill_get_time(skill_id,skill_lv)); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } + break; + + + //recursive homon skill + case MH_MAGMA_FLOW: + case MH_XENO_SLASHER: + case MH_HEILIGE_STANGE: + if(flag & 1) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + else { + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); + } + break; + + case MH_STAHL_HORN: + case MH_NEEDLE_OF_PARALYZE: + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + break; + case MH_TINDER_BREAKER: + if (unit_movepos(src, bl->x, bl->y, 1, 1)) { +#if PACKETVER >= 20111005 + clif_snap(src, bl->x, bl->y); +#else + clif_skill_poseffect(src,skill_id,skill_lv,bl->x,bl->y,tick); +#endif + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,SC_CLOSECONFINE2,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case 0:/* no skill - basic/normal attack */ + if(sd) { + if (flag & 3){ + if (bl->id != skill_area_temp[1]) + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, SD_LEVEL|flag); + } else { + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub, bl, + sd->bonus.splash_range, BL_CHAR, + src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, + skill_castend_damage_id); + flag|=1; //Set flag to 1 so ammo is not double-consumed. [Skotlex] + } + } + break; + + default: + ShowWarning("skill_castend_damage_id: Unknown skill used:%d\n",skill_id); + clif_skill_damage(src, bl, tick, status_get_amotion(src), tstatus->dmotion, + 0, abs(skill_get_num(skill_id, skill_lv)), + skill_id, skill_lv, skill_get_hit(skill_id)); + map_freeblock_unlock(); + return 1; + } + + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + + map_freeblock_unlock(); + + if( sd && !(flag&1) ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk ) + {// consume arrow on last invocation to this skill. + battle_consume_ammo(sd, skill_id, skill_lv); + } + + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data *sd, *dstsd; + struct mob_data *md, *dstmd; + struct homun_data *hd; + struct mercenary_data *mer; + struct status_data *sstatus, *tstatus; + struct status_change *tsc; + struct status_change_entry *tsce; + + int i = 0; + enum sc_type type; + + if(skill_id > 0 && !skill_lv) return 0; // celest + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if (src->m != bl->m) + return 1; + + sd = BL_CAST(BL_PC, src); + hd = BL_CAST(BL_HOM, src); + md = BL_CAST(BL_MOB, src); + mer = BL_CAST(BL_MER, src); + + dstsd = BL_CAST(BL_PC, bl); + dstmd = BL_CAST(BL_MOB, bl); + + if(bl->prev == NULL) + return 1; + if(status_isdead(src)) + return 1; + + if( src != bl && status_isdead(bl) ) { + /** + * Skills that may be cast on dead targets + **/ + switch( skill_id ) { + case NPC_WIDESOULDRAIN: + case PR_REDEMPTIO: + case ALL_RESURRECTION: + case WM_DEADHILLHERE: + break; + default: + return 1; + } + } + + tstatus = status_get_status_data(bl); + sstatus = status_get_status_data(src); + + //Check for undead skills that convert a no-damage skill into a damage one. [Skotlex] + switch (skill_id) { + case HLIF_HEAL: //[orn] + if (bl->type != BL_HOM) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0) ; + break ; + } + case AL_HEAL: + case ALL_RESURRECTION: + case PR_ASPERSIO: + /** + * Arch Bishop + **/ + case AB_RENOVATIO: + case AB_HIGHNESSHEAL: + //Apparently only player casted skills can be offensive like this. + if (sd && battle_check_undead(tstatus->race,tstatus->def_ele)) { + if (battle_check_target(src, bl, BCT_ENEMY) < 1) { + //Offensive heal does not works on non-enemies. [Skotlex] + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); + } + break; + case NPC_SMOKING: //Since it is a self skill, this one ends here rather than in damage_id. [Skotlex] + return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); + case MH_STEINWAND: { + struct block_list *s_src = battle_get_master(src); + short ret = 0; + if(!skill_check_unit_range(src, src->x, src->y, skill_id, skill_lv)) //prevent reiteration + ret = skill_castend_pos2(src,src->x,src->y,skill_id,skill_lv,tick,flag); //cast on homon + if(s_src && !skill_check_unit_range(s_src, s_src->x, s_src->y, skill_id, skill_lv)) + ret |= skill_castend_pos2(s_src,s_src->x,s_src->y,skill_id,skill_lv,tick,flag); //cast on master + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + return ret; + } + break; + default: + //Skill is actually ground placed. + if (src == bl && skill_get_unit_id(skill_id,0)) + return skill_castend_pos2(src,bl->x,bl->y,skill_id,skill_lv,tick,0); + } + + type = status_skill2sc(skill_id); + tsc = status_get_sc(bl); + tsce = (tsc && type != -1)?tsc->data[type]:NULL; + + if (src!=bl && type > -1 && + (i = skill_get_ele(skill_id, skill_lv)) > ELE_NEUTRAL && + skill_get_inf(skill_id) != INF_SUPPORT_SKILL && + battle_attr_fix(NULL, NULL, 100, i, tstatus->def_ele, tstatus->ele_lv) <= 0) + return 1; //Skills that cause an status should be blocked if the target element blocks its element. + + map_freeblock_lock(); + switch(skill_id) + { + case HLIF_HEAL: //[orn] + case AL_HEAL: + /** + * Arch Bishop + **/ + case AB_HIGHNESSHEAL: + { + int heal = skill_calc_heal(src, bl, (skill_id == AB_HIGHNESSHEAL)?AL_HEAL:skill_id, (skill_id == AB_HIGHNESSHEAL)?10:skill_lv, true); + int heal_get_jobexp; + //Highness Heal: starts at 1.5 boost + 0.5 for each level + if( skill_id == AB_HIGHNESSHEAL ) { + heal = heal * ( 15 + 5 * skill_lv ) / 10; + } + if( status_isimmune(bl) || + (dstmd && (dstmd->class_ == MOBID_EMPERIUM || mob_is_battleground(dstmd))) || + (dstsd && pc_ismadogear(dstsd)) )//Mado is immune to heal + heal=0; + + if( sd && dstsd && sd->status.partner_id == dstsd->status.char_id && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.sex == 0 ) + heal = heal*2; + + if( tsc && tsc->count ) + { + if( tsc->data[SC_KAITE] && !(sstatus->mode&MD_BOSS) ) + { //Bounce back heal + if (--tsc->data[SC_KAITE]->val2 <= 0) + status_change_end(bl, SC_KAITE, INVALID_TIMER); + if (src == bl) + heal=0; //When you try to heal yourself under Kaite, the heal is voided. + else { + bl = src; + dstsd = sd; + } + } + else if (tsc->data[SC_BERSERK] || tsc->data[SC_SATURDAYNIGHTFEVER] || tsc->data[SC__BLOODYLUST]) + heal = 0; //Needed so that it actually displays 0 when healing. + } + clif_skill_nodamage (src, bl, skill_id, heal, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && heal && skill_id != HLIF_HEAL ) + heal = ~heal + 1; + heal_get_jobexp = status_heal(bl,heal,0,0); + + if(sd && dstsd && heal > 0 && sd != dstsd && battle_config.heal_exp > 0){ + heal_get_jobexp = heal_get_jobexp * battle_config.heal_exp / 100; + if (heal_get_jobexp <= 0) + heal_get_jobexp = 1; + pc_gainexp (sd, bl, 0, heal_get_jobexp, false); + } + } + break; + + case PR_REDEMPTIO: + if (sd && !(flag&1)) { + if (sd->status.party_id == 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_area_temp[0] = 0; + party_foreachsamemap(skill_area_sub, + sd,skill_get_splash(skill_id, skill_lv), + src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + if (skill_area_temp[0] == 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_area_temp[0] = 5 - skill_area_temp[0]; // The actual penalty... + if (skill_area_temp[0] > 0 && !map[src->m].flag.noexppenalty) { //Apply penalty + sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * skill_area_temp[0] * 2/1000); //0.2% penalty per each. + sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * skill_area_temp[0] * 2/1000); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + } + status_set_hp(src, 1, 0); + status_set_sp(src, 0, 0); + break; + } else if (status_isdead(bl) && flag&1) { //Revive + skill_area_temp[0]++; //Count it in, then fall-through to the Resurrection code. + skill_lv = 3; //Resurrection level 3 is used + } else //Invalid target, skip resurrection. + break; + + case ALL_RESURRECTION: + if(sd && (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)) + { //No reviving in WoE grounds! + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (!status_isdead(bl)) + break; + { + int per = 0, sper = 0; + if (tsc && tsc->data[SC_HELLPOWER]) + break; + + if (map[bl->m].flag.pvp && dstsd && dstsd->pvp_point < 0) + break; + + switch(skill_lv){ + case 1: per=10; break; + case 2: per=30; break; + case 3: per=50; break; + case 4: per=80; break; + } + if(dstsd && dstsd->special_state.restart_full_recover) + per = sper = 100; + if (status_revive(bl, per, sper)) + { + clif_skill_nodamage(src,bl,ALL_RESURRECTION,skill_lv,1); //Both Redemptio and Res show this skill-animation. + if(sd && dstsd && battle_config.resurrection_exp > 0) + { + int exp = 0,jexp = 0; + int lv = dstsd->status.base_level - sd->status.base_level, jlv = dstsd->status.job_level - sd->status.job_level; + if(lv > 0 && pc_nextbaseexp(dstsd)) { + exp = (int)((double)dstsd->status.base_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if (exp < 1) exp = 1; + } + if(jlv > 0 && pc_nextjobexp(dstsd)) { + jexp = (int)((double)dstsd->status.job_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if (jexp < 1) jexp = 1; + } + if(exp > 0 || jexp > 0) + pc_gainexp (sd, bl, exp, jexp, false); + } + } + } + break; + + case AL_DECAGI: + case MER_DECAGI: + clif_skill_nodamage (src, bl, skill_id, skill_lv, + sc_start(bl, type, (40 + skill_lv * 2 + (status_get_lv(src) + sstatus->int_)/5), skill_lv, skill_get_time(skill_id,skill_lv))); + break; + + case AL_CRUCIS: + if (flag&1) + sc_start(bl,type, 23+skill_lv*4 +status_get_lv(src) -status_get_lv(bl), skill_lv,skill_get_time(skill_id,skill_lv)); + else { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case PR_LEXDIVINA: + case MER_LEXDIVINA: + if( tsce ) + status_change_end(bl,type, INVALID_TIMER); + else + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + break; + + case SA_ABRACADABRA: + { + int abra_skill_id = 0, abra_skill_lv; + do { + i = rnd() % MAX_SKILL_ABRA_DB; + abra_skill_id = skill_abra_db[i].skill_id; + } while (abra_skill_id == 0 || + skill_abra_db[i].req_lv > skill_lv || //Required lv for it to appear + rnd()%10000 >= skill_abra_db[i].per + ); + abra_skill_lv = min(skill_lv, skill_get_max(abra_skill_id)); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + + if( sd ) + {// player-casted + sd->state.abra_flag = 1; + sd->skillitem = abra_skill_id; + sd->skillitemlv = abra_skill_lv; + clif_item_skill(sd, abra_skill_id, abra_skill_lv); + } + else + {// mob-casted + struct unit_data *ud = unit_bl2ud(src); + int inf = skill_get_inf(abra_skill_id); + int target_id = 0; + if (!ud) break; + if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { + if (src->type == BL_PET) + bl = (struct block_list*)((TBL_PET*)src)->msd; + if (!bl) bl = src; + unit_skilluse_id(src, bl->id, abra_skill_id, abra_skill_lv); + } else { //Assume offensive skills + if (ud->target) + target_id = ud->target; + else switch (src->type) { + case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; + case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; + } + if (!target_id) + break; + if (skill_get_casttype(abra_skill_id) == CAST_GROUND) { + bl = map_id2bl(target_id); + if (!bl) bl = src; + unit_skilluse_pos(src, bl->x, bl->y, abra_skill_id, abra_skill_lv); + } else + unit_skilluse_id(src, target_id, abra_skill_id, abra_skill_lv); + } + } + } + break; + + case SA_COMA: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + case SA_FULLRECOVERY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (status_isimmune(bl)) + break; + status_percent_heal(bl, 100, 100); + break; + case NPC_ALLHEAL: + { + int heal; + if( status_isimmune(bl) ) + break; + heal = status_percent_heal(bl, 100, 0); + clif_skill_nodamage(NULL, bl, AL_HEAL, heal, 1); + if( dstmd ) + { // Reset Damage Logs + memset(dstmd->dmglog, 0, sizeof(dstmd->dmglog)); + dstmd->tdmg = 0; + } + } + break; + case SA_SUMMONMONSTER: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd) mob_once_spawn(sd, src->m, src->x, src->y," --ja--", -1, 1, "", SZ_SMALL, AI_NONE); + break; + case SA_LEVELUP: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd, NULL, pc_nextbaseexp(sd) * 10 / 100, 0, false); + break; + case SA_INSTANTDEATH: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_set_hp(bl,1,0); + break; + case SA_QUESTION: + case SA_GRAVITY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case SA_CLASSCHANGE: + case SA_MONOCELL: + if (dstmd) + { + int class_; + if ( sd && dstmd->status.mode&MD_BOSS ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + class_ = skill_id==SA_MONOCELL?1002:mob_get_random_id(4, 1, 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + mob_class_change(dstmd,class_); + if( tsc && dstmd->status.mode&MD_BOSS ) + { + const enum sc_type scs[] = { SC_QUAGMIRE, SC_PROVOKE, SC_ROKISWEIL, SC_GRAVITATION, SC_SUITON, SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC_BLADESTOP }; + for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) + if (tsc->data[i]) status_change_end(bl, (sc_type)i, INVALID_TIMER); + for (i = 0; i < ARRAYLENGTH(scs); i++) + if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); + } + } + break; + case SA_DEATH: + if ( sd && dstmd && dstmd->status.mode&MD_BOSS ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_kill(bl); + break; + case SA_REVERSEORCISH: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); + break; + case SA_FORTUNE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(sd) pc_getzeny(sd,status_get_lv(bl)*100,LOG_TYPE_STEAL,NULL); + break; + case SA_TAMINGMONSTER: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd && dstmd) { + ARR_FIND( 0, MAX_PET_DB, i, dstmd->class_ == pet_db[i].class_ ); + if( i < MAX_PET_DB ) + pet_catch_process1(sd, dstmd->class_); + } + break; + + case CR_PROVIDENCE: + if(sd && dstsd){ //Check they are not another crusader [Skotlex] + if ((dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case CG_MARIONETTE: + { + struct status_change* sc = status_get_sc(src); + + if( sd && dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && dstsd->status.sex == sd->status.sex ) + {// Cannot cast on another bard/dancer-type class of the same gender as caster + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + + if( sc && tsc ) + { + if( !sc->data[SC_MARIONETTE] && !tsc->data[SC_MARIONETTE2] ) + { + sc_start(src,SC_MARIONETTE,100,bl->id,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_MARIONETTE2,100,src->id,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + else + if( sc->data[SC_MARIONETTE ] && sc->data[SC_MARIONETTE ]->val1 == bl->id && + tsc->data[SC_MARIONETTE2] && tsc->data[SC_MARIONETTE2]->val1 == src->id ) + { + status_change_end(src, SC_MARIONETTE, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE2, INVALID_TIMER); + } + else + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + + map_freeblock_unlock(); + return 1; + } + } + } + break; + + case RG_CLOSECONFINE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); + break; + case SA_FLAMELAUNCHER: // added failure chance and chance to break weapon if turned on [Valaris] + case SA_FROSTWEAPON: + case SA_LIGHTNINGLOADER: + case SA_SEISMICWEAPON: + if (dstsd) { + if(dstsd->status.weapon == W_FIST || + (dstsd->sc.count && !dstsd->sc.data[type] && + ( //Allow re-enchanting to lenghten time. [Skotlex] + dstsd->sc.data[SC_FIREWEAPON] || + dstsd->sc.data[SC_WATERWEAPON] || + dstsd->sc.data[SC_WINDWEAPON] || + dstsd->sc.data[SC_EARTHWEAPON] || + dstsd->sc.data[SC_SHADOWWEAPON] || + dstsd->sc.data[SC_GHOSTWEAPON] || + dstsd->sc.data[SC_ENCPOISON] + )) + ) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + } + // 100% success rate at lv4 & 5, but lasts longer at lv5 + if(!clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,type,(60+skill_lv*10),skill_lv, skill_get_time(skill_id,skill_lv)))) { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + if (skill_break_equip(bl, EQP_WEAPON, 10000, BCT_PARTY) && sd && sd != dstsd) + clif_displaymessage(sd->fd, msg_txt(669)); + } + break; + + case PR_ASPERSIO: + if (sd && dstmd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case ITEM_ENCHANTARMS: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv, + skill_get_ele(skill_id,skill_lv), skill_get_time(skill_id,skill_lv))); + break; + + case TK_SEVENWIND: + switch(skill_get_ele(skill_id,skill_lv)) { + case ELE_EARTH : type = SC_EARTHWEAPON; break; + case ELE_WIND : type = SC_WINDWEAPON; break; + case ELE_WATER : type = SC_WATERWEAPON; break; + case ELE_FIRE : type = SC_FIREWEAPON; break; + case ELE_GHOST : type = SC_GHOSTWEAPON; break; + case ELE_DARK : type = SC_SHADOWWEAPON; break; + case ELE_HOLY : type = SC_ASPERSIO; break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + + sc_start(bl,SC_SEVENWIND,100,skill_lv,skill_get_time(skill_id,skill_lv)); + + break; + + case PR_KYRIE: + case MER_KYRIE: + clif_skill_nodamage(bl,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + //Passive Magnum, should had been casted on yourself. + case SM_MAGNUM: + case MS_MAGNUM: + skill_area_temp[1] = 0; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_SKILL|BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + clif_skill_nodamage (src,src,skill_id,skill_lv,1); + // Initiate 10% of your damage becomes fire element. + sc_start4(src,SC_WATK_ELEMENT,100,3,20,0,0,skill_get_time2(skill_id, skill_lv)); + if( sd ) + skill_blockpc_start(sd, skill_id, skill_get_time(skill_id, skill_lv)); + else if( bl->type == BL_MER ) + skill_blockmerc_start((TBL_MER*)bl, skill_id, skill_get_time(skill_id, skill_lv)); + break; + + case TK_JUMPKICK: + /* Check if the target is an enemy; if not, skill should fail so the character doesn't unit_movepos (exploitable) */ + if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) + { + if( unit_movepos(src, bl->x, bl->y, 1, 1) ) + { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + clif_slide(src,bl->x,bl->y); + } + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + break; + + case AL_INCAGI: + case AL_BLESSING: + case MER_INCAGI: + case MER_BLESSING: + if (dstsd != NULL && tsc->data[SC_CHANGEUNDEAD]) { + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + } + case PR_SLOWPOISON: + case PR_IMPOSITIO: + case PR_LEXAETERNA: + case PR_SUFFRAGIUM: + case PR_BENEDICTIO: + case LK_BERSERK: + case MS_BERSERK: + case KN_AUTOCOUNTER: + case KN_TWOHANDQUICKEN: + case KN_ONEHAND: + case MER_QUICKEN: + case CR_SPEARQUICKEN: + case CR_REFLECTSHIELD: + case MS_REFLECTSHIELD: + case AS_POISONREACT: + case MC_LOUD: + case MG_ENERGYCOAT: + case MO_EXPLOSIONSPIRITS: + case MO_STEELBODY: + case MO_BLADESTOP: + case LK_AURABLADE: + case LK_PARRYING: + case MS_PARRYING: + case LK_CONCENTRATION: + case WS_CARTBOOST: + case SN_SIGHT: + case WS_MELTDOWN: + case WS_OVERTHRUSTMAX: + case ST_REJECTSWORD: + case HW_MAGICPOWER: + case PF_MEMORIZE: + case PA_SACRIFICE: + case ASC_EDP: + case PF_DOUBLECASTING: + case SG_SUN_COMFORT: + case SG_MOON_COMFORT: + case SG_STAR_COMFORT: + case NPC_HALLUCINATION: + case GS_MADNESSCANCEL: + case GS_ADJUSTMENT: + case GS_INCREASING: + case NJ_KASUMIKIRI: + case NJ_UTSUSEMI: + case NJ_NEN: + case NPC_DEFENDER: + case NPC_MAGICMIRROR: + case ST_PRESERVE: + case NPC_INVINCIBLE: + case NPC_INVINCIBLEOFF: + case RK_DEATHBOUND: + case AB_RENOVATIO: + case AB_EXPIATIO: + case AB_DUPLELIGHT: + case AB_SECRAMENT: + case NC_ACCELERATION: + case NC_HOVERING: + case NC_SHAPESHIFT: + case WL_RECOGNIZEDSPELL: + case GC_VENOMIMPRESS: + case SC_DEADLYINFECT: + case LG_EXEEDBREAK: + case LG_PRESTIGE: + case SR_CRESCENTELBOW: + case SR_LIGHTNINGWALK: + case SR_GENTLETOUCH_ENERGYGAIN: + case GN_CARTBOOST: + case KO_MEIKYOUSISUI: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case SO_STRIKING: + if (sd) { + int bonus = 25 + 10 * skill_lv; + bonus += (pc_checkskill(sd, SA_FLAMELAUNCHER)+pc_checkskill(sd, SA_FROSTWEAPON)+pc_checkskill(sd, SA_LIGHTNINGLOADER)+pc_checkskill(sd, SA_SEISMICWEAPON))*5; + clif_skill_nodamage( src, bl, skill_id, skill_lv, + battle_check_target(src,bl,BCT_PARTY) ? + sc_start2(bl, type, 100, skill_lv, bonus, skill_get_time(skill_id,skill_lv)) : + 0 + ); + } + break; + + case NPC_STOP: + if( clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)) ) ) + sc_start2(src,type,100,skill_lv,bl->id,skill_get_time(skill_id,skill_lv)); + break; + case HP_ASSUMPTIO: + if( sd && dstmd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + else + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case MG_SIGHT: + case MER_SIGHT: + case AL_RUWACH: + case WZ_SIGHTBLASTER: + case NPC_WIDESIGHT: + case NPC_STONESKIN: + case NPC_ANTIMAGIC: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,skill_id,skill_get_time(skill_id,skill_lv))); + break; + case HLIF_AVOID: + case HAMI_DEFENCE: + i = skill_get_time(skill_id,skill_lv); + clif_skill_nodamage(bl,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,i)); // Master + clif_skill_nodamage(src,src,skill_id,skill_lv,sc_start(src,type,100,skill_lv,i)); // Homunc + break; + case NJ_BUNSINJYUTSU: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + status_change_end(bl, SC_NEN, INVALID_TIMER); + break; +/* Was modified to only affect targetted char. [Skotlex] + case HP_ASSUMPTIO: + if (flag&1) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), BL_PC, + src, skill_id, skill_lv, tick, flag|BCT_ALL|1, + skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; +*/ + case SM_ENDURE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (sd) + skill_blockpc_start (sd, skill_id, skill_get_time2(skill_id,skill_lv)); + break; + + case AS_ENCHANTPOISON: // Prevent spamming [Valaris] + if (sd && dstsd && dstsd->sc.count) { + if (dstsd->sc.data[SC_FIREWEAPON] || + dstsd->sc.data[SC_WATERWEAPON] || + dstsd->sc.data[SC_WINDWEAPON] || + dstsd->sc.data[SC_EARTHWEAPON] || + dstsd->sc.data[SC_SHADOWWEAPON] || + dstsd->sc.data[SC_GHOSTWEAPON] + // dstsd->sc.data[SC_ENCPOISON] //People say you should be able to recast to lengthen the timer. [Skotlex] + ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case LK_TENSIONRELAX: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,0,0,skill_get_time2(skill_id,skill_lv), + skill_get_time(skill_id,skill_lv))); + break; + + case MC_CHANGECART: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case TK_MISSION: + if (sd) { + int id; + if (sd->mission_mobid && (sd->mission_count || rnd()%100)) { //Cannot change target when already have one + clif_mission_info(sd, sd->mission_mobid, sd->mission_count); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + id = mob_get_random_id(0,0xF, sd->status.base_level); + if (!id) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + sd->mission_mobid = id; + sd->mission_count = 0; + pc_setglobalreg(sd,"TK_MISSION_ID", id); + clif_mission_info(sd, id, 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case AC_CONCENTRATION: + { + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + map_foreachinrange( status_change_timer_sub, src, + skill_get_splash(skill_id, skill_lv), BL_CHAR, + src,NULL,type,tick); + } + break; + + case SM_PROVOKE: + case SM_SELFPROVOKE: + case MER_PROVOKE: + if( (tstatus->mode&MD_BOSS) || battle_check_undead(tstatus->race,tstatus->def_ele) ) + { + map_freeblock_unlock(); + return 1; + } + //TODO: How much does base level affects? Dummy value of 1% per level difference used. [Skotlex] + clif_skill_nodamage(src,bl,skill_id == SM_SELFPROVOKE ? SM_PROVOKE : skill_id,skill_lv, + (i = sc_start(bl,type, skill_id == SM_SELFPROVOKE ? 100:( 50 + 3*skill_lv + status_get_lv(src) - status_get_lv(bl)), skill_lv, skill_get_time(skill_id,skill_lv)))); + if( !i ) + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + unit_skillcastcancel(bl, 2); + + if( tsc && tsc->count ) + { + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + if( tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE ) + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_TRICKDEAD, INVALID_TIMER); + } + + if( dstmd ) + { + dstmd->state.provoke_flag = src->id; + mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); + } + break; + + case ML_DEVOTION: + case CR_DEVOTION: + { + int count, lv; + if( !dstsd || (!sd && !mer) ) + { // Only players can be devoted + if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + + if( (lv = status_get_lv(src) - dstsd->status.base_level) < 0 ) + lv = -lv; + if( lv > battle_config.devotion_level_difference || // Level difference requeriments + (dstsd->sc.data[type] && dstsd->sc.data[type]->val1 != src->id) || // Cannot Devote a player devoted from another source + (skill_id == ML_DEVOTION && (!mer || mer != dstsd->md)) || // Mercenary only can devote owner + (dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER || // Crusader Cannot be devoted + (dstsd->sc.data[SC_HELLPOWER])) // Players affected by SC_HELLPOWERR cannot be devoted. + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + + i = 0; + count = (sd)? min(skill_lv,5) : 1; // Mercenary only can Devote owner + if( sd ) + { // Player Devoting Player + ARR_FIND(0, count, i, sd->devotion[i] == bl->id ); + if( i == count ) + { + ARR_FIND(0, count, i, sd->devotion[i] == 0 ); + if( i == count ) + { // No free slots, skill Fail + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + map_freeblock_unlock(); + return 1; + } + } + + sd->devotion[i] = bl->id; + } + else + mer->devotion_flag = 1; // Mercenary Devoting Owner + + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start4(bl, type, 100, src->id, i, skill_get_range2(src,skill_id,skill_lv),0, skill_get_time2(skill_id, skill_lv))); + clif_devotion(src, NULL); + } + break; + + case MO_CALLSPIRITS: + if(sd) { + int limit = skill_lv; + if( sd->sc.data[SC_RAISINGDRAGON] ) + limit += sd->sc.data[SC_RAISINGDRAGON]->val1; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); + } + break; + + case CH_SOULCOLLECT: + if(sd) { + int limit = 5; + if( sd->sc.data[SC_RAISINGDRAGON] ) + limit += sd->sc.data[SC_RAISINGDRAGON]->val1; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + for (i = 0; i < limit; i++) + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); + } + break; + + case MO_KITRANSLATION: + if(dstsd && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) { + pc_addspiritball(dstsd,skill_get_time(skill_id,skill_lv),5); + } + break; + + case TK_TURNKICK: + case MO_BALKYOUNG: //Passive part of the attack. Splash knock-back+stun. [Skotlex] + if (skill_area_temp[1] != bl->id) { + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),-1,0); + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); //Use Misc rather than weapon to signal passive pushback + } + break; + + case MO_ABSORBSPIRITS: + i = 0; + if (dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) + { // split the if for readability, and included gunslingers in the check so that their coins cannot be removed [Reddozen] + i = dstsd->spiritball * 7; + pc_delspiritball(dstsd,dstsd->spiritball,0); + } else if (dstmd && !(tstatus->mode&MD_BOSS) && rnd() % 100 < 20) + { // check if target is a monster and not a Boss, for the 20% chance to absorb 2 SP per monster's level [Reddozen] + i = 2 * dstmd->level; + mob_target(dstmd,src,0); + } + if (i) status_heal(src, 0, i, 3); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i?1:0); + break; + + case AC_MAKINGARROW: + if(sd) { + clif_arrow_create_list(sd); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case AM_PHARMACY: + if(sd) { + clif_skill_produce_mix_list(sd,skill_id,22); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SA_CREATECON: + if(sd) { + clif_elementalconverter_list(sd); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case BS_HAMMERFALL: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,SC_STUN,(20 + 10 * skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + case RG_RAID: + skill_area_temp[1] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), splash_target(src), + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + status_change_end(src, SC_HIDING, INVALID_TIMER); + break; + + case ASC_METEORASSAULT: + case GS_SPREADATTACK: + case RK_STORMBLAST: + case NC_AXETORNADO: + case GC_COUNTERSLASH: + case SR_SKYNETBLOW: + case SR_RAMPAGEBLASTER: + case SR_HOWLINGOFLION: + case KO_HAPPOKUNAI: + skill_area_temp[1] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + i = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + if( !i && ( skill_id == NC_AXETORNADO || skill_id == SR_SKYNETBLOW || skill_id == KO_HAPPOKUNAI ) ) + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + + case NC_EMERGENCYCOOL: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(src,SC_OVERHEAT_LIMITPOINT,INVALID_TIMER); + status_change_end(src,SC_OVERHEAT,INVALID_TIMER); + break; + case SR_WINDMILL: + case GN_CART_TORNADO: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + case SR_EARTHSHAKER: + case NC_INFRAREDSCAN: + case NPC_EARTHQUAKE: + case NPC_VAMPIRE_GIFT: + case NPC_HELLJUDGEMENT: + case NPC_PULSESTRIKE: + case LG_MOONSLASHER: + skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); + break; + + case KN_BRANDISHSPEAR: + case ML_BRANDISH: + skill_brandishspear(src, bl, skill_id, skill_lv, tick, flag); + break; + + case WZ_SIGHTRASHER: + //Passive side of the attack. + status_change_end(src, SC_SIGHT, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,src, + skill_get_splash(skill_id, skill_lv),BL_CHAR|BL_SKILL, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case NJ_HYOUSYOURAKU: + case NJ_RAIGEKISAI: + case WZ_FROSTNOVA: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_area_temp[1] = 0; + map_foreachinrange(skill_attack_area, src, + skill_get_splash(skill_id, skill_lv), splash_target(src), + BF_MAGIC, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); + break; + + case HVAN_EXPLOSION: //[orn] + case NPC_SELFDESTRUCTION: + //Self Destruction hits everyone in range (allies+enemies) + //Except for Summoned Marine spheres on non-versus maps, where it's just enemy. + i = ((!md || md->special_state.ai == 2) && !map_flag_vs(src->m))? + BCT_ENEMY:BCT_ALL; + clif_skill_nodamage(src, src, skill_id, -1, 1); + map_delblock(src); //Required to prevent chain-self-destructions hitting back. + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), splash_target(src), + src, skill_id, skill_lv, tick, flag|i, + skill_castend_damage_id); + map_addblock(src); + status_damage(src, src, sstatus->max_hp,0,0,1); + break; + + case AL_ANGELUS: + case PR_MAGNIFICAT: + case PR_GLORIA: + case SN_WINDWALK: + case CASH_BLESSING: + case CASH_INCAGI: + case CASH_ASSUMPTIO: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + case MER_MAGNIFICAT: + if( mer != NULL ) + { + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if( mer->master && mer->master->status.party_id != 0 && !(flag&1) ) + party_foreachsamemap(skill_area_sub, mer->master, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + else if( mer->master && !(flag&1) ) + clif_skill_nodamage(src, &mer->master->bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + + case BS_ADRENALINE: + case BS_ADRENALINE2: + case BS_WEAPONPERFECT: + case BS_OVERTHRUST: + if (sd == NULL || sd->status.party_id == 0 || (flag & 1)) { + clif_skill_nodamage(bl,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,(src == bl)? 1:0,skill_get_time(skill_id,skill_lv))); + } else if (sd) { + party_foreachsamemap(skill_area_sub, + sd,skill_get_splash(skill_id, skill_lv), + src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + + case BS_MAXIMIZE: + case NV_TRICKDEAD: + case CR_DEFENDER: + case ML_DEFENDER: + case CR_AUTOGUARD: + case ML_AUTOGUARD: + case TK_READYSTORM: + case TK_READYDOWN: + case TK_READYTURN: + case TK_READYCOUNTER: + case TK_DODGE: + case CR_SHRINK: + case SG_FUSION: + case GS_GATLINGFEVER: + if( tsce ) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case SL_KAITE: + case SL_KAAHI: + case SL_KAIZEL: + case SL_KAUPE: + if (sd) { + if (!dstsd || !( + (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SOULLINKER) || + (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER || + dstsd->status.char_id == sd->status.char_id || + dstsd->status.char_id == sd->status.partner_id || + dstsd->status.char_id == sd->status.child + )) { + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,8); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); + break; + case SM_AUTOBERSERK: + case MER_AUTOBERSERK: + if( tsce ) + i = status_change_end(bl, type, INVALID_TIMER); + else + i = sc_start(bl,type,100,skill_lv,60000); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + break; + case TF_HIDING: + case ST_CHASEWALK: + case KO_YAMIKUMO: + if (tsce) + { + clif_skill_nodamage(src,bl,skill_id,-1,status_change_end(bl, type, INVALID_TIMER)); //Hide skill-scream animation. + map_freeblock_unlock(); + return 0; + } else if( tsc && tsc->option&OPTION_MADOGEAR ) { + //Mado Gear cannot hide + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,-1,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case TK_RUN: + if (tsce) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,0)); + if (sd) // If the client receives a skill-use packet inmediately before a walkok packet, it will discard the walk packet! [Skotlex] + clif_walkok(sd); // So aegis has to resend the walk ok. + break; + case AS_CLOAKING: + case GC_CLOAKINGEXCEED: + case LG_FORCEOFVANGUARD: + case SC_REPRODUCE: + case SC_INVISIBILITY: + if (tsce) { + i = status_change_end(bl, type, INVALID_TIMER); + if( i ) + clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + case RA_CAMOUFLAGE: + i = sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + if( i ) + clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case BD_ADAPTATION: + if(tsc && tsc->data[SC_DANCING]){ + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(bl, SC_DANCING, INVALID_TIMER); + } + break; + + case BA_FROSTJOKER: + case DC_SCREAM: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_addtimerskill(src,tick+2000,bl->id,src->x,src->y,skill_id,skill_lv,0,flag); + + if (md) { + // custom hack to make the mob display the skill, because these skills don't show the skill use text themselves + //NOTE: mobs don't have the sprite animation that is used when performing this skill (will cause glitches) + char temp[70]; + snprintf(temp, sizeof(temp), "%s : %s !!",md->name,skill_db[skill_id].desc); + clif_message(&md->bl,temp); + } + break; + + case BA_PANGVOICE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,50,7,skill_get_time(skill_id,skill_lv))); + break; + + case DC_WINKCHARM: + if( dstsd ) + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,30,7,skill_get_time2(skill_id,skill_lv))); + else + if( dstmd ) + { + if( status_get_lv(src) > status_get_lv(bl) + && (tstatus->race == RC_DEMON || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL) + && !(tstatus->mode&MD_BOSS) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(bl,type,70,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); + else + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + } + break; + + case TF_STEAL: + if(sd) { + if(pc_steal_item(sd,bl,skill_lv)) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + } + break; + + case RG_STEALCOIN: + if(sd) { + if(pc_steal_coin(sd,bl)) + { + dstmd->state.provoke_flag = src->id; + mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case MG_STONECURSE: + { + int brate = 0; + if (tstatus->mode&MD_BOSS) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc) + break; + + if (sd && sd->sc.data[SC_PETROLOGY_OPTION]) + brate = sd->sc.data[SC_PETROLOGY_OPTION]->val3; + + if (tsc->data[SC_STONE]) { + status_change_end(bl, SC_STONE, INVALID_TIMER); + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (sc_start4(bl,SC_STONE,(skill_lv*4+20)+brate, + skill_lv, 0, 0, skill_get_time(skill_id, skill_lv), + skill_get_time2(skill_id,skill_lv))) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else if(sd) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + // Level 6-10 doesn't consume a red gem if it fails [celest] + if (skill_lv > 5) + { // not to consume items + map_freeblock_unlock(); + return 0; + } + } + } + break; + + case NV_FIRSTAID: + clif_skill_nodamage(src,bl,skill_id,5,1); + status_heal(bl,5,0,0); + break; + + case AL_CURE: + if(status_isimmune(bl)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_CONFUSION, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case TF_DETOXIFY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_DPOISON, INVALID_TIMER); + break; + + case PR_STRECOVERY: + if(status_isimmune(bl)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + if (tsc && tsc->opt1) { + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_WHITEIMPRISON, INVALID_TIMER); + } + //Is this equation really right? It looks so... special. + if(battle_check_undead(tstatus->race,tstatus->def_ele)) + { + status_change_start(bl, SC_BLIND, + 100*(100-(tstatus->int_/2+tstatus->vit/3+tstatus->luk/10)), + 1,0,0,0, + skill_get_time2(skill_id, skill_lv) * (100-(tstatus->int_+tstatus->vit)/2)/100,0); + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(dstmd) + mob_unlocktarget(dstmd,tick); + break; + + // Mercenary Supportive Skills + case MER_BENEDICTION: + status_change_end(bl, SC_CURSE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_COMPRESS: + status_change_end(bl, SC_BLEEDING, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_MENTALCURE: + status_change_end(bl, SC_CONFUSION, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_RECUPERATE: + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_REGAIN: + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_TENDER: + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case MER_SCAPEGOAT: + if( mer && mer->master ) + { + status_heal(&mer->master->bl, mer->battle_status.hp, 0, 2); + status_damage(src, src, mer->battle_status.max_hp, 0, 0, 1); + } + break; + + case MER_ESTIMATION: + if( !mer ) + break; + sd = mer->master; + case WZ_ESTIMATION: + if( sd == NULL ) + break; + if( dstsd ) + { // Fail on Players + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) + break; // Cannot be Used on Emperium + + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + clif_skill_estimation(sd, bl); + if( skill_id == MER_ESTIMATION ) + sd = NULL; + break; + + case BS_REPAIRWEAPON: + if(sd && dstsd) + clif_item_repair_list(sd,dstsd,skill_lv); + break; + + case MC_IDENTIFY: + if(sd) + clif_item_identify_list(sd); + break; + + // Weapon Refining [Celest] + case WS_WEAPONREFINE: + if(sd) + clif_item_refine_list(sd); + break; + + case MC_VENDING: + if(sd) + { //Prevent vending of GMs with unnecessary Level to trade/drop. [Skotlex] + if ( !pc_can_give_items(sd) ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + else { + sd->state.prevend = 1; + clif_openvendingreq(sd,2+skill_lv); + } + } + break; + + case AL_TELEPORT: + if(sd) + { + if (map[bl->m].flag.noteleport && skill_lv <= 2) { + clif_skill_teleportmessage(sd,0); + break; + } + if(!battle_config.duel_allow_teleport && sd->duel_group && skill_lv <= 2) { // duel restriction [LuzZza] + char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_TELEPORT)); + clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." + break; + } + + if( sd->state.autocast || ( (sd->skillitem == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1 ) || skill_lv == 3 ) + { + if( skill_lv == 1 ) + pc_randomwarp(sd,CLR_TELEPORT); + else + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( skill_lv == 1 ) + clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,0,0,0); + else + clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,sd->status.save_point.map,0,0); + } else + unit_warp(bl,-1,-1,-1,CLR_TELEPORT); + break; + + case NPC_EXPULSION: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_warp(bl,-1,-1,-1,CLR_TELEPORT); + break; + + case AL_HOLYWATER: + if(sd) { + if (skill_produce_mix(sd, skill_id, 523, 0, 0, 0, 1)) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case TF_PICKSTONE: + if(sd) { + int eflag; + struct item item_tmp; + struct block_list tbl; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + memset(&tbl,0,sizeof(tbl)); // [MouseJstr] + item_tmp.nameid = ITEMID_STONE; + item_tmp.identify = 1; + tbl.id = 0; + clif_takeitem(&sd->bl,&tbl); + eflag = pc_additem(sd,&item_tmp,1,LOG_TYPE_PRODUCE); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + break; + case ASC_CDP: + if(sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, 678, 0, 0, 0, 1); //Produce a Deadly Poison Bottle. + } + break; + + case RG_STRIPWEAPON: + case RG_STRIPSHIELD: + case RG_STRIPARMOR: + case RG_STRIPHELM: + case ST_FULLSTRIP: + case GC_WEAPONCRUSH: + case SC_STRIPACCESSARY: { + unsigned short location = 0; + int d = 0; + + //Rate in percent + if ( skill_id == ST_FULLSTRIP ) { + i = 5 + 2*skill_lv + (sstatus->dex - tstatus->dex)/5; + } else if( skill_id == SC_STRIPACCESSARY ) { + i = 12 + 2 * skill_lv + (sstatus->dex - tstatus->dex)/5; + } else { + i = 5 + 5*skill_lv + (sstatus->dex - tstatus->dex)/5; + } + + if (i < 5) i = 5; //Minimum rate 5% + + //Duration in ms + if( skill_id == GC_WEAPONCRUSH){ + d = skill_get_time(skill_id,skill_lv); + if(bl->type == BL_PC) + d += skill_lv * 15 + (sstatus->dex - tstatus->dex); + else + d += skill_lv * 30 + (sstatus->dex - tstatus->dex) / 2; + }else + d = skill_get_time(skill_id,skill_lv) + (sstatus->dex - tstatus->dex)*500; + + if (d < 0) d = 0; //Minimum duration 0ms + + switch (skill_id) { + case RG_STRIPWEAPON: + case GC_WEAPONCRUSH: + location = EQP_WEAPON; + break; + case RG_STRIPSHIELD: + location = EQP_SHIELD; + break; + case RG_STRIPARMOR: + location = EQP_ARMOR; + break; + case RG_STRIPHELM: + location = EQP_HELM; + break; + case ST_FULLSTRIP: + location = EQP_WEAPON|EQP_SHIELD|EQP_ARMOR|EQP_HELM; + break; + case SC_STRIPACCESSARY: + location = EQP_ACC; + break; + } + + //Special message when trying to use strip on FCP [Jobbie] + if( sd && skill_id == ST_FULLSTRIP && tsc && tsc->data[SC_CP_WEAPON] && tsc->data[SC_CP_HELM] && tsc->data[SC_CP_ARMOR] && tsc->data[SC_CP_SHIELD]) + { + clif_gospel_info(sd, 0x28); + break; + } + + //Attempts to strip at rate i and duration d + if( (i = skill_strip_equip(bl, location, i, skill_lv, d)) || (skill_id != ST_FULLSTRIP && skill_id != GC_WEAPONCRUSH ) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + + //Nothing stripped. + if( sd && !i ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + case AM_BERSERKPITCHER: + case AM_POTIONPITCHER: { + int i,x,hp = 0,sp = 0,bonus=100; + if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) { + map_freeblock_unlock(); + return 1; + } + if( sd ) { + x = skill_lv%11 - 1; + i = pc_search_inventory(sd,skill_db[skill_id].itemid[x]); + if( i < 0 || skill_db[skill_id].itemid[x] <= 0 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + if(sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < skill_db[skill_id].amount[x]) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + if( skill_id == AM_BERSERKPITCHER ) { + if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[i]->elv ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + } + potion_flag = 1; + potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0; + potion_target = bl->id; + run_script(sd->inventory_data[i]->script,0,sd->bl.id,0); + potion_flag = potion_target = 0; + if( sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ALCHEMIST ) + bonus += sd->status.base_level; + if( potion_per_hp > 0 || potion_per_sp > 0 ) { + hp = tstatus->max_hp * potion_per_hp / 100; + hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + if( dstsd ) { + sp = dstsd->status.max_sp * potion_per_sp / 100; + sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + } + } else { + if( potion_hp > 0 ) { + hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + hp = hp * (100 + (tstatus->vit<<1)) / 100; + if( dstsd ) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if( potion_sp > 0 ) { + sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + sp = sp * (100 + (tstatus->int_<<1)) / 100; + if( dstsd ) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; + } + } + + if (sd->itemgrouphealrate[IG_POTION]>0) { + hp += hp * sd->itemgrouphealrate[IG_POTION] / 100; + sp += sp * sd->itemgrouphealrate[IG_POTION] / 100; + } + + if( (i = pc_skillheal_bonus(sd, skill_id)) ) { + hp += hp * i / 100; + sp += sp * i / 100; + } + } else { + hp = (1 + rnd()%400) * (100 + skill_lv*10) / 100; + hp = hp * (100 + (tstatus->vit<<1)) / 100; + if( dstsd ) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if( dstsd && (i = pc_skillheal2_bonus(dstsd, skill_id)) ) { + hp += hp * i / 100; + sp += sp * i / 100; + } + if( tsc && tsc->count ) { + if( tsc->data[SC_CRITICALWOUND] ) { + hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + } + if( tsc->data[SC_DEATHHURT] ) { + hp -= hp * 20 / 100; + sp -= sp * 20 / 100; + } + if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2 ) { + hp += hp / 10; + sp += sp / 10; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( hp > 0 || (skill_id == AM_POTIONPITCHER && sp <= 0) ) + clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); + if( sp > 0 ) + clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); +#ifdef RENEWAL + if( tsc && tsc->data[SC_EXTREMITYFIST2] ) + sp = 0; +#endif + status_heal(bl,hp,sp,0); + } + break; + case AM_CP_WEAPON: + case AM_CP_SHIELD: + case AM_CP_ARMOR: + case AM_CP_HELM: + { + unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; + + if( sd && ( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[skill_id - AM_CP_WEAPON]) < 0 ) ) ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); // Don't consume item requirements + return 0; + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + case AM_TWILIGHT1: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + //Prepare 200 White Potions. + if (!skill_produce_mix(sd, skill_id, 504, 0, 0, 0, 200)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case AM_TWILIGHT2: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + //Prepare 200 Slim White Potions. + if (!skill_produce_mix(sd, skill_id, 547, 0, 0, 0, 200)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case AM_TWILIGHT3: + if (sd) { + int ebottle = pc_search_inventory(sd,713); + if( ebottle >= 0 ) + ebottle = sd->status.inventory[ebottle].amount; + //check if you can produce all three, if not, then fail: + if (!skill_can_produce_mix(sd,970,-1, 100) //100 Alcohol + || !skill_can_produce_mix(sd,7136,-1, 50) //50 Acid Bottle + || !skill_can_produce_mix(sd,7135,-1, 50) //50 Flame Bottle + || ebottle < 200 //200 empty bottle are required at total. + ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, 970, 0, 0, 0, 100); + skill_produce_mix(sd, skill_id, 7136, 0, 0, 0, 50); + skill_produce_mix(sd, skill_id, 7135, 0, 0, 0, 50); + } + break; + case SA_DISPELL: + if (flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) + || (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_ROGUE) //Rogue's spirit defends againt dispel. + || rnd()%100 >= 50+10*skill_lv + || ( tsc && tsc->option&OPTION_MADOGEAR ) )//Mado Gear is immune to dispell according to bug report 49 [Ind] + { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc || !tsc->count) + break; + for(i=0;i<SC_MAX;i++) + { + if (!tsc->data[i]) + continue; + switch (i) { + case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: + case SC_STRIPWEAPON: case SC_STRIPSHIELD: case SC_STRIPARMOR: + case SC_STRIPHELM: case SC_CP_WEAPON: case SC_CP_SHIELD: + case SC_CP_ARMOR: case SC_CP_HELM: case SC_COMBO: + case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: + case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: + case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: + case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: + case SC_EDP: case SC_AUTOBERSERK: + case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: + case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: + case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: + case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: + case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: + case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: + case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: + case SC_READYCOUNTER: case SC_DODGE: case SC_WARM: + case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: + case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: + case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: + case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: + case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: + case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: + case SC_INTOABYSS: case SC_SIEGFRIED: case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: case SC_FOOD_LUK_CASH: case SC_SEVENWIND: + case SC_MIRACLE: case SC_S_LIFEPOTION: case SC_L_LIFEPOTION: + case SC_INCHEALRATE: case SC_ELECTRICSHOCKER: case SC__STRIPACCESSORY: + //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: case SC_MINOR_BBQ: + //case SC_SIROMA_ICE_TEA: case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: + case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: case SC_STEALTHFIELD_MASTER: + case SC_STEALTHFIELD: case SC_GIANTGROWTH: case SC_MILLENNIUMSHIELD: + case SC_REFRESH: case SC_STONEHARDSKIN: case SC_VITALITYACTIVATION: + case SC_FIGHTINGSPIRIT: case SC_ABUNDANCE: case SC__SHADOWFORM: + case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: + case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: + case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: + case SC_GT_REVITALIZE: case SC_REFLECTDAMAGE: case SC_INSPIRATION: + case SC_EXEEDBREAK: case SC_FORCEOFVANGUARD: case SC_BANDING: + case SC_DUPLELIGHT: case SC_EXPIATIO: case SC_LAUDAAGNUS: + case SC_LAUDARAMUS: case SC_GATLINGFEVER: case SC_INCREASING: + case SC_ADJUSTMENT: case SC_MADNESSCANCEL: +#ifdef RENEWAL + case SC_EXTREMITYFIST2: +#endif + continue; + /** + * bugreport:4888 these songs may only be dispelled if you're not in their song area anymore + **/ + case SC_WHISTLE: + case SC_ASSNCROS: + case SC_POEMBRAGI: + case SC_APPLEIDUN: + case SC_HUMMING: + case SC_DONTFORGETME: + case SC_FORTUNE: + case SC_SERVICE4U: + if( tsc->data[i]->val4 ) //val4 = out-of-song-area + continue; + break; + case SC_ASSUMPTIO: + if( bl->type == BL_MOB ) + continue; + break; + } + if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. + status_change_end(bl, (sc_type)i, INVALID_TIMER); + } + break; + } + //Affect all targets on splash area. + map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|1, + skill_castend_damage_id); + break; + + case TF_BACKSLIDING: //This is the correct implementation as per packet logging information. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); + break; + + case TK_HIGHJUMP: + { + int x,y, dir = unit_getdir(src); + + //Fails on noteleport maps, except for GvG and BG maps [Skotlex] + if( map[src->m].flag.noteleport && + !(map[src->m].flag.battleground || map_flag_gvg2(src->m) ) + ) { + x = src->x; + y = src->y; + } else { + x = src->x + dirx[dir]*skill_lv*2; + y = src->y + diry[dir]*skill_lv*2; + } + + clif_skill_nodamage(src,bl,TK_HIGHJUMP,skill_lv,1); + if(!map_count_oncell(src->m,x,y,BL_PC|BL_NPC|BL_MOB) && map_getcell(src->m,x,y,CELL_CHKREACH)) { + clif_slide(src,x,y); + unit_movepos(src, x, y, 1, 0); + } + } + break; + + case SA_CASTCANCEL: + case SO_SPELLFIST: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_skillcastcancel(src,1); + if(sd) { + int sp = skill_get_sp(sd->skill_id_old,sd->skill_lv_old); + if( skill_id == SO_SPELLFIST ){ + sc_start4(src,type,100,skill_lv+1,skill_lv,sd->skill_id_old,sd->skill_lv_old,skill_get_time(skill_id,skill_lv)); + sd->skill_id_old = sd->skill_lv_old = 0; + break; + } + sp = sp * (90 - (skill_lv-1)*20) / 100; + if(sp < 0) sp = 0; + status_zap(src, 0, sp); + } + break; + case SA_SPELLBREAKER: + { + int sp; + if(tsc && tsc->data[SC_MAGICROD]) { + sp = skill_get_sp(skill_id,skill_lv); + sp = sp * tsc->data[SC_MAGICROD]->val2 / 100; + if(sp < 1) sp = 1; + status_heal(bl,0,sp,2); + status_percent_damage(bl, src, 0, -20, false); //20% max SP damage. + } else { + struct unit_data *ud = unit_bl2ud(bl); + int bl_skill_id=0,bl_skill_lv=0,hp = 0; + if (!ud || ud->skilltimer == INVALID_TIMER) + break; //Nothing to cancel. + bl_skill_id = ud->skill_id; + bl_skill_lv = ud->skill_lv; + if (tstatus->mode & MD_BOSS) + { //Only 10% success chance against bosses. [Skotlex] + if (rnd()%100 < 90) + { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } else if (!dstsd || map_flag_vs(bl->m)) //HP damage only on pvp-maps when against players. + hp = tstatus->max_hp/50; //Recover 2% HP [Skotlex] + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_skillcastcancel(bl,0); + sp = skill_get_sp(bl_skill_id,bl_skill_lv); + status_zap(bl, hp, sp); + + if (hp && skill_lv >= 5) + hp>>=1; //Recover half damaged HP at level 5 [Skotlex] + else + hp = 0; + + if (sp) //Recover some of the SP used + sp = sp*(25*(skill_lv-1))/100; + + if(hp || sp) + status_heal(src, hp, sp, 2); + } + } + break; + case SA_MAGICROD: + clif_skill_nodamage(src,src,SA_MAGICROD,skill_lv,1); + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case SA_AUTOSPELL: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(sd) + clif_autospell(sd,skill_lv); + else { + int maxlv=1,spellid=0; + static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; + if(skill_lv >= 10) { + spellid = MG_FROSTDIVER; +// if (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SA_SAGE) +// maxlv = 10; +// else + maxlv = skill_lv - 9; + } + else if(skill_lv >=8) { + spellid = MG_FIREBALL; + maxlv = skill_lv - 7; + } + else if(skill_lv >=5) { + spellid = MG_SOULSTRIKE; + maxlv = skill_lv - 4; + } + else if(skill_lv >=2) { + int i = rnd()%3; + spellid = spellarray[i]; + maxlv = skill_lv - 1; + } + else if(skill_lv > 0) { + spellid = MG_NAPALMBEAT; + maxlv = 3; + } + if(spellid > 0) + sc_start4(src,SC_AUTOSPELL,100,skill_lv,spellid,maxlv,0, + skill_get_time(SA_AUTOSPELL,skill_lv)); + } + break; + + case BS_GREED: + if(sd){ + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_greed,bl, + skill_get_splash(skill_id, skill_lv),BL_ITEM,bl); + } + break; + + case SA_ELEMENTWATER: + case SA_ELEMENTFIRE: + case SA_ELEMENTGROUND: + case SA_ELEMENTWIND: + if(sd && !dstmd) //Only works on monsters. + break; + if(tstatus->mode&MD_BOSS) + break; + case NPC_ATTRICHANGE: + case NPC_CHANGEWATER: + case NPC_CHANGEGROUND: + case NPC_CHANGEFIRE: + case NPC_CHANGEWIND: + case NPC_CHANGEPOISON: + case NPC_CHANGEHOLY: + case NPC_CHANGEDARKNESS: + case NPC_CHANGETELEKINESIS: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), + skill_get_time(skill_id, skill_lv))); + break; + case NPC_CHANGEUNDEAD: + //This skill should fail if target is wearing bathory/evil druid card [Brainstorm] + //TO-DO This is ugly, fix it + if(tstatus->def_ele==ELE_UNDEAD || tstatus->def_ele==ELE_DARK) break; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), + skill_get_time(skill_id, skill_lv))); + break; + + case NPC_PROVOCATION: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (md) mob_unlocktarget(md, tick); + break; + + case NPC_KEEPING: + case NPC_BARRIER: + { + int skill_time = skill_get_time(skill_id,skill_lv); + struct unit_data *ud = unit_bl2ud(bl); + if (clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_time)) + && ud) { //Disable attacking/acting/moving for skill's duration. + ud->attackabletime = + ud->canact_tick = + ud->canmove_tick = tick + skill_time; + } + } + break; + + case NPC_REBIRTH: + if( md && md->state.rebirth ) + break; // only works once + sc_start(bl,type,100,skill_lv,-1); + break; + + case NPC_DARKBLESSING: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,(50+skill_lv*5),skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + + case NPC_LICK: + status_zap(bl, 0, 100); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,(skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + + case NPC_SUICIDE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_kill(src); //When suiciding, neither exp nor drops is given. + break; + + case NPC_SUMMONSLAVE: + case NPC_SUMMONMONSTER: + if(md && md->skill_idx >= 0) + mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv,skill_id); + break; + + case NPC_CALLSLAVE: + mob_warpslave(src,MOB_SLAVEDISTANCE); + break; + + case NPC_RANDOMMOVE: + if (md) { + md->next_walktime = tick - 1; + mob_randomwalk(md,tick); + } + break; + + case NPC_SPEEDUP: + { + // or does it increase casting rate? just a guess xD + int i = SC_ASPDPOTION0 + skill_lv - 1; + if (i > SC_ASPDPOTION3) + i = SC_ASPDPOTION3; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,(sc_type)i,100,skill_lv,skill_lv * 60000)); + } + break; + + case NPC_REVENGE: + // not really needed... but adding here anyway ^^ + if (md && md->master_id > 0) { + struct block_list *mbl, *tbl; + if ((mbl = map_id2bl(md->master_id)) == NULL || + (tbl = battle_gettargeted(mbl)) == NULL) + break; + md->state.provoke_flag = tbl->id; + mob_target(md, tbl, sstatus->rhw.range); + } + break; + + case NPC_RUN: + { + const int mask[8][2] = {{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}}; + uint8 dir = (bl == src)?unit_getdir(src):map_calc_dir(src,bl->x,bl->y); //If cast on self, run forward, else run away. + unit_stop_attack(src); + //Run skillv tiles overriding the can-move check. + if (unit_walktoxy(src, src->x + skill_lv * mask[dir][0], src->y + skill_lv * mask[dir][1], 2) && md) + md->state.skillstate = MSS_WALK; //Otherwise it isn't updated in the ai. + } + break; + + case NPC_TRANSFORMATION: + case NPC_METAMORPHOSIS: + if(md && md->skill_idx >= 0) { + int class_ = mob_random_class (md->db->skill[md->skill_idx].val,0); + if (skill_lv > 1) //Multiply the rest of mobs. [Skotlex] + mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv-1,skill_id); + if (class_) mob_class_change(md, class_); + } + break; + + case NPC_EMOTION_ON: + case NPC_EMOTION: + //va[0] is the emotion to use. + //NPC_EMOTION & NPC_EMOTION_ON can change a mob's mode 'permanently' [Skotlex] + //val[1] 'sets' the mode + //val[2] adds to the current mode + //val[3] removes from the current mode + //val[4] if set, asks to delete the previous mode change. + if(md && md->skill_idx >= 0 && tsc) + { + clif_emotion(bl, md->db->skill[md->skill_idx].val[0]); + if(md->db->skill[md->skill_idx].val[4] && tsce) + status_change_end(bl, type, INVALID_TIMER); + + if(md->db->skill[md->skill_idx].val[1] || md->db->skill[md->skill_idx].val[2]) + sc_start4(src, type, 100, skill_lv, + md->db->skill[md->skill_idx].val[1], + md->db->skill[md->skill_idx].val[2], + md->db->skill[md->skill_idx].val[3], + skill_get_time(skill_id, skill_lv)); + } + break; + + case NPC_POWERUP: + sc_start(bl,SC_INCATKRATE,100,200,skill_get_time(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); + break; + + case NPC_AGIUP: + sc_start(bl,SC_SPEEDUP1,100,skill_lv,skill_get_time(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); + break; + + case NPC_INVISIBLE: + //Have val4 passed as 6 is for "infinite cloak" (do not end on attack/skill use). + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,0,0,6,skill_get_time(skill_id,skill_lv))); + break; + + case NPC_SIEGEMODE: + // not sure what it does + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case WE_MALE: + { + int hp_rate=(!skill_lv)? 0:skill_db[skill_id].hp_rate[skill_lv-1]; + int gain_hp= tstatus->max_hp*abs(hp_rate)/100; // The earned is the same % of the target HP than it costed the caster. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,status_heal(bl, gain_hp, 0, 0),1); + } + break; + case WE_FEMALE: + { + int sp_rate=(!skill_lv)? 0:skill_db[skill_id].sp_rate[skill_lv-1]; + int gain_sp=tstatus->max_sp*abs(sp_rate)/100;// The earned is the same % of the target SP than it costed the caster. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,status_heal(bl, 0, gain_sp, 0),1); + } + break; + + // parent-baby skills + case WE_BABY: + if(sd){ + struct map_session_data *f_sd = pc_get_father(sd); + struct map_session_data *m_sd = pc_get_mother(sd); + // if neither was found + if(!f_sd && !m_sd){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + status_change_start(bl,SC_STUN,10000,skill_lv,0,0,0,skill_get_time2(skill_id,skill_lv),8); + if (f_sd) sc_start(&f_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + if (m_sd) sc_start(&m_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + } + break; + + case PF_HPCONVERSION: + { + int hp, sp; + hp = sstatus->max_hp/10; + sp = hp * 10 * skill_lv / 100; + if (!status_charge(src,hp,0)) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + status_heal(bl,0,sp,2); + } + break; + + case MA_REMOVETRAP: + case HT_REMOVETRAP: + { + struct skill_unit* su; + struct skill_unit_group* sg; + su = BL_CAST(BL_SKILL, bl); + + // Mercenaries can remove any trap + // Players can only remove their own traps or traps on Vs maps. + if( su && (sg = su->group) && (src->type == BL_MER || sg->src_id == src->id || map_flag_vs(bl->m)) && (skill_get_inf2(sg->skill_id)&INF2_TRAP) ) + { + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + if( sd && !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) + { // prevent picking up expired traps + if( battle_config.skill_removetrap_type ) + { // get back all items used to deploy the trap + for( i = 0; i < 10; i++ ) + { + if( skill_db[su->group->skill_id].itemid[i] > 0 ) + { + int flag; + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = skill_db[su->group->skill_id].itemid[i]; + item_tmp.identify = 1; + if( item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,skill_db[su->group->skill_id].amount[i],LOG_TYPE_OTHER)) ) + { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,skill_db[su->group->skill_id].amount[i],sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + } + else + { // get back 1 trap + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = su->group->item_id?su->group->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + if( item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_OTHER)) ) + { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + skill_delunit(su); + }else if(sd) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + + } + break; + case HT_SPRINGTRAP: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ + switch(su->group->unit_id){ + case UNT_ANKLESNARE: // ankle snare + if (su->group->val2 != 0) + // if it is already trapping something don't spring it, + // remove trap should be used instead + break; + // otherwise fallthrough to below + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_CLAYMORETRAP: + case UNT_TALKIEBOX: + su->group->unit_id = UNT_USED_TRAPS; + clif_changetraplook(bl, UNT_USED_TRAPS); + su->group->limit=DIFF_TICK(tick+1500,su->group->tick); + su->limit=DIFF_TICK(tick+1500,su->group->tick); + } + } + } + break; + case BD_ENCORE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(sd) + unit_skilluse_id(src,src->id,sd->skill_id_dance,sd->skill_lv_dance); + break; + + case AS_SPLASHER: + if(tstatus->mode&MD_BOSS + /** + * Renewal dropped the 3/4 hp requirement + **/ + #ifndef RENEWAL + || tstatus-> hp > tstatus->max_hp*3/4 + #endif + ) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,skill_id,src->id,skill_get_time(skill_id,skill_lv),1000)); +#ifndef RENEWAL + if (sd) skill_blockpc_start (sd, skill_id, skill_get_time(skill_id, skill_lv)+3000); +#endif + break; + + case PF_MINDBREAKER: + { + if(tstatus->mode&MD_BOSS || battle_check_undead(tstatus->race,tstatus->def_ele)) + { + map_freeblock_unlock(); + return 1; + } + + if (tsce) + { //HelloKitty2 (?) explained that this silently fails when target is + //already inflicted. [Skotlex] + map_freeblock_unlock(); + return 1; + } + + //Has a 55% + skill_lv*5% success chance. + if (!clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,55+5*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)))) + { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + + unit_skillcastcancel(bl,0); + + if(tsc && tsc->count){ + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + if(tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE) + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + } + + if(dstmd) + mob_target(dstmd,src,skill_get_range2(src,skill_id,skill_lv)); + } + break; + + case PF_SOULCHANGE: + { + unsigned int sp1 = 0, sp2 = 0; + if (dstmd) { + if (dstmd->state.soul_change_flag) { + if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + dstmd->state.soul_change_flag = 1; + sp2 = sstatus->max_sp * 3 /100; + status_heal(src, 0, sp2, 2); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + } + sp1 = sstatus->sp; + sp2 = tstatus->sp; + #ifdef RENEWAL + sp1 = sp1 / 2; + sp2 = sp2 / 2; + if( tsc && tsc->data[SC_EXTREMITYFIST2] ) + sp1 = tstatus->sp; + #endif + status_set_sp(src, sp2, 3); + status_set_sp(bl, sp1, 3); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + // Slim Pitcher + case CR_SLIMPITCHER: + // Updated to block Slim Pitcher from working on barricades and guardian stones. + if( dstmd && (dstmd->class_ == MOBID_EMPERIUM || (dstmd->class_ >= MOBID_BARRICADE1 && dstmd->class_ <= MOBID_GUARIDAN_STONE2)) ) + break; + if (potion_hp || potion_sp) { + int hp = potion_hp, sp = potion_sp; + hp = hp * (100 + (tstatus->vit<<1))/100; + sp = sp * (100 + (tstatus->int_<<1))/100; + if (dstsd) { + if (hp) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; + if (sp) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; + } + if( tsc && tsc->count ) { + if (tsc->data[SC_CRITICALWOUND]) { + hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + } + if (tsc->data[SC_DEATHHURT]) { + hp -= hp * 20 / 100; + sp -= sp * 20 / 100; + } + if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2) { + hp += hp / 10; + sp += sp / 10; + } + } + if(hp > 0) + clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); + if(sp > 0) + clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); + status_heal(bl,hp,sp,0); + } + break; + // Full Chemical Protection + case CR_FULLPROTECTION: + { + unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; + int i, s = 0, skilltime = skill_get_time(skill_id,skill_lv); + + for (i=0 ; i<4; i++) { + if( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[i]) < 0 ) ) + continue; + sc_start(bl,(sc_type)(SC_CP_WEAPON + i),100,skill_lv,skilltime); + s++; + } + if( sd && !s ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); // Don't consume item requirements + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RG_CLEANER: //AppleGirl + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case CG_LONGINGFREEDOM: + { + if (tsc && !tsce && (tsce=tsc->data[SC_DANCING]) && tsce->val4 + && (tsce->val1&0xFFFF) != CG_MOONLIT) //Can't use Longing for Freedom while under Moonlight Petals. [Skotlex] + { + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + } + break; + + case CG_TAROTCARD: + { + int eff, count = -1; + if( rnd() % 100 > skill_lv * 8 || (dstmd && ((dstmd->guardian_data && dstmd->class_ == MOBID_EMPERIUM) || mob_is_battleground(dstmd))) ) + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + + map_freeblock_unlock(); + return 0; + } + status_zap(src,0,skill_db[skill_get_index(skill_id)].sp[skill_lv]); // consume sp only if succeeded [Inkfish] + do { + eff = rnd() % 14; + clif_specialeffect(bl, 523 + eff, AREA); + switch (eff) + { + case 0: // heals SP to 0 + status_percent_damage(src, bl, 0, 100, false); + break; + case 1: // matk halved + sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + break; + case 2: // all buffs removed + status_change_clear_buffs(bl,1); + break; + case 3: // 1000 damage, random armor destroyed + { + int where[] = { EQP_ARMOR, EQP_SHIELD, EQP_HELM, EQP_SHOES, EQP_GARMENT }; + status_fix_damage(src, bl, 1000, 0); + clif_damage(src,bl,tick,0,0,1000,0,0,0); + if( !status_isdead(bl) ) + skill_break_equip(bl, where[rnd()%5], 10000, BCT_ENEMY); + } + break; + case 4: // atk halved + sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + break; + case 5: // 2000HP heal, random teleported + status_heal(src, 2000, 0, 0); + if( !map_flag_vs(bl->m) ) + unit_warp(bl, -1,-1,-1, CLR_TELEPORT); + break; + case 6: // random 2 other effects + if (count == -1) + count = 3; + else + count++; //Should not retrigger this one. + break; + case 7: // stop freeze or stoned + { + enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONE }; + sc_start(bl,sc[rnd()%3],100,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + case 8: // curse coma and poison + sc_start(bl,SC_COMA,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_POISON,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case 9: // confusion + sc_start(bl,SC_CONFUSION,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case 10: // 6666 damage, atk matk halved, cursed + status_fix_damage(src, bl, 6666, 0); + clif_damage(src,bl,tick,0,0,6666,0,0,0); + sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_CURSE,skill_lv,100,skill_get_time2(skill_id,skill_lv)); + break; + case 11: // 4444 damage + status_fix_damage(src, bl, 4444, 0); + clif_damage(src,bl,tick,0,0,4444,0,0,0); + break; + case 12: // stun + sc_start(bl,SC_STUN,100,skill_lv,5000); + break; + case 13: // atk,matk,hit,flee,def reduced + sc_start(bl,SC_INCATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCMATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCHITRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCFLEERATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCDEFRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + break; + default: + break; + } + } while ((--count) > 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SL_ALCHEMIST: + case SL_ASSASIN: + case SL_BARDDANCER: + case SL_BLACKSMITH: + case SL_CRUSADER: + case SL_HUNTER: + case SL_KNIGHT: + case SL_MONK: + case SL_PRIEST: + case SL_ROGUE: + case SL_SAGE: + case SL_SOULLINKER: + case SL_STAR: + case SL_SUPERNOVICE: + case SL_WIZARD: + //NOTE: here, 'type' has the value of the associated MAPID, not of the SC_SPIRIT constant. + if (sd && !(dstsd && (dstsd->class_&MAPID_UPPERMASK) == type)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (skill_id == SL_SUPERNOVICE && dstsd && dstsd->die_counter && !(rnd()%100)) + { //Erase death count 1% of the casts + dstsd->die_counter = 0; + pc_setglobalreg(dstsd,"PC_DIE_COUNTER", 0); + clif_specialeffect(bl, 0x152, AREA); + //SC_SPIRIT invokes status_calc_pc for us. + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,SC_SPIRIT,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + case SL_HIGH: + if (sd && !(dstsd && (dstsd->class_&JOBL_UPPER) && !(dstsd->class_&JOBL_2) && dstsd->status.base_level < 70)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + + case SL_SWOO: + if (tsce) { + if(sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,10000,8); + status_change_end(bl, SC_SWOO, INVALID_TIMER); + break; + } + case SL_SKA: // [marquis007] + case SL_SKE: + if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (skill_id == SL_SKE) + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + + // New guild skills [Celest] + case GD_BATTLEORDER: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_REGENERATION: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_RESTORE: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + clif_skill_nodamage(src,bl,AL_HEAL,status_percent_heal(bl,90,90),1); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_EMERGENCYCALL: + { + int dx[9]={-1, 1, 0, 0,-1, 1,-1, 1, 0}; + int dy[9]={ 0, 0, 1,-1, 1,-1,-1, 1, 0}; + int j = 0; + struct guild *g = NULL; + // i don't know if it actually summons in a circle, but oh well. ;P + g = sd?sd->state.gmaster_flag:guild_search(status_get_guild_id(src)); + if (!g) + break; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + for(i = 0; i < g->max_member; i++, j++) { + if (j>8) j=0; + if ((dstsd = g->member[i].sd) != NULL && sd != dstsd && !dstsd->state.autotrade && !pc_isdead(dstsd)) { + if (map[dstsd->bl.m].flag.nowarp && !map_flag_gvg2(dstsd->bl.m)) + continue; + if(map_getcell(src->m,src->x+dx[j],src->y+dy[j],CELL_CHKNOREACH)) + dx[j] = dy[j] = 0; + pc_setpos(dstsd, map_id2index(src->m), src->x+dx[j], src->y+dy[j], CLR_RESPAWN); + } + } + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + + case SG_FEEL: + //AuronX reported you CAN memorize the same map as all three. [Skotlex] + if (sd) { + if(!sd->feel_map[skill_lv-1].index) + clif_feel_req(sd->fd,sd, skill_lv); + else + clif_feel_info(sd, skill_lv-1, 1); + } + break; + + case SG_HATE: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (!pc_set_hate_mob(sd, skill_lv-1, bl)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case GS_GLITTERING: + if(sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(rnd()%100 < (20+10*skill_lv)) + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),10); + else if(sd->spiritball > 0) + pc_delspiritball(sd,1,0); + } + break; + + case GS_CRACKER: + /* per official standards, this skill works on players and mobs. */ + if (sd && (dstsd || dstmd)) + { + i =65 -5*distance_bl(src,bl); //Base rate + if (i < 30) i = 30; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + sc_start(bl,SC_STUN, i,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + + case AM_CALLHOMUN: //[orn] + if (sd && !merc_call_homunculus(sd)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case AM_REST: + if (sd) { + if (merc_hom_vaporize(sd,1)) + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case HAMI_CASTLE: //[orn] + if(rnd()%100 < 20*skill_lv && src != bl) + { + int x,y; + x = src->x; + y = src->y; + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); + + if (unit_movepos(src,bl->x,bl->y,0,0)) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); // Homunc + clif_slide(src,bl->x,bl->y) ; + if (unit_movepos(bl,x,y,0,0)) + { + clif_skill_nodamage(bl,bl,skill_id,skill_lv,1); // Master + clif_slide(bl,x,y) ; + } + + //TODO: Shouldn't also players and the like switch targets? + map_foreachinrange(skill_chastle_mob_changetarget,src, + AREA_SIZE, BL_MOB, bl, src); + } + } + // Failed + else if (hd && hd->master) + clif_skill_fail(hd->master, skill_id, USESKILL_FAIL_LEVEL, 0); + else if (sd) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + case HVAN_CHAOTIC: //[orn] + { + static const int per[5][2]={{20,50},{50,60},{25,75},{60,64},{34,67}}; + int r = rnd()%100; + i = (skill_lv-1)%5; + if(r<per[i][0]) //Self + bl = src; + else if(r<per[i][1]) //Master + bl = battle_get_master(src); + else //Enemy + bl = map_id2bl(battle_gettarget(src)); + + if (!bl) bl = src; + i = skill_calc_heal(src, bl, skill_id, 1+rnd()%skill_lv, true); + //Eh? why double skill packet? + clif_skill_nodamage(src,bl,AL_HEAL,i,1); + clif_skill_nodamage(src,bl,skill_id,i,1); + status_heal(bl, i, 0, 0); + } + break; + //Homun single-target support skills [orn] + case HAMI_BLOODLUST: + case HFLI_FLEET: + case HFLI_SPEED: + case HLIF_CHANGE: + case MH_ANGRIFFS_MODUS: + case MH_GOLDENE_FERSE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); + break; + + case NPC_DRAGONFEAR: + if (flag&1) { + const enum sc_type sc[] = { SC_STUN, SC_SILENCE, SC_CONFUSION, SC_BLEEDING }; + int j; + j = i = rnd()%ARRAYLENGTH(sc); + while ( !sc_start(bl,sc[i],100,skill_lv,skill_get_time2(skill_id,i+1)) ) { + i++; + if ( i == ARRAYLENGTH(sc) ) + i = 0; + if (i == j) + break; + } + break; + } + case NPC_WIDEBLEEDING: + case NPC_WIDECONFUSE: + case NPC_WIDECURSE: + case NPC_WIDEFREEZE: + case NPC_WIDESLEEP: + case NPC_WIDESILENCE: + case NPC_WIDESTONE: + case NPC_WIDESTUN: + case NPC_SLOWCAST: + case NPC_WIDEHELLDIGNITY: + if (flag&1) + sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + else { + skill_area_temp[2] = 0; //For SD_PREAMBLE + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case NPC_WIDESOULDRAIN: + if (flag&1) + status_percent_damage(src,bl,0,((skill_lv-1)%5+1)*20,false); + else { + skill_area_temp[2] = 0; //For SD_PREAMBLE + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case ALL_PARTYFLEE: + if( sd && !(flag&1) ) + { + if( !sd->status.party_id ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + else + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case NPC_TALK: + case ALL_WEWISH: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case ALL_BUYING_STORE: + if( sd ) + {// players only, skill allows 5 buying slots + clif_skill_nodamage(src, bl, skill_id, skill_lv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS)); + } + break; + case RK_ENCHANTBLADE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,// formula not confirmed + sc_start2(bl,type,100,skill_lv,100+20*skill_lv/*+sstatus->int_/2+status_get_lv(bl)/10*/,skill_get_time(skill_id,skill_lv))); + break; + case RK_DRAGONHOWLING: + if( flag&1) + sc_start(bl,type,50 + 6 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + skill_area_temp[2] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id,skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case RK_IGNITIONBREAK: + case LG_EARTHDRIVE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + i = skill_get_splash(skill_id,skill_lv); + if( skill_id == LG_EARTHDRIVE ) { + int dummy = 1; + map_foreachinarea(skill_cell_overlap, src->m, src->x-i, src->y-i, src->x+i, src->y+i, BL_SKILL, LG_EARTHDRIVE, &dummy, src); + } + map_foreachinrange(skill_area_sub, bl,i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + case RK_STONEHARDSKIN: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 4 ) + { + int heal = sstatus->hp / 4; // 25% HP + if( status_charge(bl,heal,0) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start2(bl,type,100,skill_lv,heal,skill_get_time(skill_id,skill_lv))); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case RK_REFRESH: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 8 ) + { + int heal = status_get_max_hp(bl) * 25 / 100; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + status_heal(bl,heal,0,1); + status_change_clear_buffs(bl,4); + } + break; + + case RK_MILLENNIUMSHIELD: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 9 ) + { + short shields = (rnd()%100<50) ? 4 : ((rnd()%100<80) ? 3 : 2); + sc_start4(bl,type,100,skill_lv,shields,1000,0,skill_get_time(skill_id,skill_lv)); + clif_millenniumshield(sd,shields); + clif_skill_nodamage(src,bl,skill_id,1,1); + } + break; + + case RK_GIANTGROWTH: + case RK_VITALITYACTIVATION: + case RK_ABUNDANCE: + case RK_CRUSHSTRIKE: + if( sd ) + { + int lv = 1; // RK_GIANTGROWTH + if( skill_id == RK_VITALITYACTIVATION ) + lv = 2; + else if( skill_id == RK_ABUNDANCE ) + lv = 6; + else if( skill_id == RK_CRUSHSTRIKE ) + lv = 7; + if( pc_checkskill(sd,RK_RUNEMASTERY) >= lv ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + + case RK_FIGHTINGSPIRIT: + if( flag&1 ) { + if( src == bl ) + sc_start2(bl,type,100,skill_area_temp[5],10*(sd?pc_checkskill(sd,RK_RUNEMASTERY):10),skill_get_time(skill_id,skill_lv)); + else + sc_start(bl,type,100,skill_area_temp[5]/4,skill_get_time(skill_id,skill_lv)); + } else if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 5 ) { + if( sd->status.party_id ) { + i = party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,BCT_PARTY,skill_area_sub_count); + skill_area_temp[5] = 7 * i; // ATK + party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); + } else + sc_start2(bl,type,100,7,5,skill_get_time(skill_id,skill_lv)); + } + clif_skill_nodamage(src,bl,skill_id,1,1); + break; + /** + * Guilotine Cross + **/ + case GC_ROLLINGCUTTER: + { + short count = 1; + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|SD_SPLASH|1,skill_castend_damage_id); + if( tsc && tsc->data[SC_ROLLINGCUTTER] ) + { // Every time the skill is casted the status change is reseted adding a counter. + count += (short)tsc->data[SC_ROLLINGCUTTER]->val1; + if( count > 10 ) + count = 10; // Max coounter + status_change_end(bl, SC_ROLLINGCUTTER, INVALID_TIMER); + } + sc_start(bl,SC_ROLLINGCUTTER,100,count,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } + break; + + case GC_WEAPONBLOCKING: + if( tsc && tsc->data[SC_WEAPONBLOCKING] ) + status_change_end(bl, SC_WEAPONBLOCKING, INVALID_TIMER); + else + sc_start(bl,SC_WEAPONBLOCKING,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case GC_CREATENEWPOISON: + if( sd ) + { + clif_skill_produce_mix_list(sd,skill_id,25); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case GC_POISONINGWEAPON: + if( sd ) { + clif_poison_list(sd,skill_lv); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case GC_ANTIDOTE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( tsc ) + { + status_change_end(bl, SC_PARALYSE, INVALID_TIMER); + status_change_end(bl, SC_PYREXIA, INVALID_TIMER); + status_change_end(bl, SC_DEATHHURT, INVALID_TIMER); + status_change_end(bl, SC_LEECHESEND, INVALID_TIMER); + status_change_end(bl, SC_VENOMBLEED, INVALID_TIMER); + status_change_end(bl, SC_MAGICMUSHROOM, INVALID_TIMER); + status_change_end(bl, SC_TOXIN, INVALID_TIMER); + status_change_end(bl, SC_OBLIVIONCURSE, INVALID_TIMER); + } + break; + + case GC_PHANTOMMENACE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case GC_HALLUCINATIONWALK: + { + int heal = status_get_max_hp(bl) / 10; + if( status_get_hp(bl) < heal ) { // if you haven't enough HP skill fails. + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + break; + } + if( !status_charge(bl,heal,0) ) + { + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + /** + * Arch Bishop + **/ + case AB_ANCILLA: + if( sd ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, ITEMID_ANCILLA, 0, 0, 0, 1); + } + break; + + case AB_CLEMENTIA: + case AB_CANTO: + { + int bless_lv = pc_checkskill(sd,AL_BLESSING) + (sd->status.job_level / 10); + int agi_lv = pc_checkskill(sd,AL_INCAGI) + (sd->status.job_level / 10); + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100, + (skill_id == AB_CLEMENTIA)? bless_lv : (skill_id == AB_CANTO)? agi_lv : skill_lv, skill_get_time(skill_id,skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case AB_PRAEFATIO: + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start4(bl, type, 100, skill_lv, 0, 0, 1, skill_get_time(skill_id, skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_CHEAL: + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + { + if( sd && tstatus && !battle_check_undead(tstatus->race, tstatus->def_ele) ) + { + i = skill_calc_heal(src, bl, AL_HEAL, pc_checkskill(sd, AL_HEAL), true); + + if( (dstsd && pc_ismadogear(dstsd)) || status_isimmune(bl)) + i = 0; // Should heal by 0 or won't do anything?? in iRO it breaks the healing to members.. [malufett] + + clif_skill_nodamage(bl, bl, skill_id, i, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && i ) + i = ~i + 1; + status_heal(bl, i, 0, 0); + } + } + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_ORATIO: + if( flag&1 ) + sc_start(bl, type, 40 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + else + { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case AB_LAUDAAGNUS: + if( flag&1 || sd == NULL ) { + if( tsc && (tsc->data[SC_FREEZE] || tsc->data[SC_STONE] || tsc->data[SC_BLIND] || + tsc->data[SC_BURNING] || tsc->data[SC_FREEZING] || tsc->data[SC_CRYSTALIZE])) { + // Success Chance: (40 + 10 * Skill Level) % + if( rnd()%100 > 40+10*skill_lv ) break; + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); + }else //Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets + clif_skill_nodamage(bl, bl, skill_id, skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + } else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), + src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_LAUDARAMUS: + if( flag&1 || sd == NULL ) { + if( tsc && (tsc->data[SC_SLEEP] || tsc->data[SC_STUN] || tsc->data[SC_MANDRAGORA] || tsc->data[SC_SILENCE]) ){ + // Success Chance: (40 + 10 * Skill Level) % + if( rnd()%100 > 40+10*skill_lv ) break; + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_MANDRAGORA, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + }else // Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets + clif_skill_nodamage(bl, bl, skill_id, skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + } else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), + src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_CLEARANCE: + if( flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1 ) + { //As of the behavior in official server Clearance is just a super version of Dispell skill. [Jobbie] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) || rnd()%100 >= 30 + 10 * skill_lv) + { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc || !tsc->count) + break; + for(i=0;i<SC_MAX;i++) + { + if (!tsc->data[i]) + continue; + switch (i) { + case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: + case SC_STRIPWEAPON: case SC_STRIPSHIELD: case SC_STRIPARMOR: + case SC_STRIPHELM: case SC_CP_WEAPON: case SC_CP_SHIELD: + case SC_CP_ARMOR: case SC_CP_HELM: case SC_COMBO: + case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: + case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: + case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: + case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: + case SC_SPIRIT: case SC_AUTOBERSERK: + case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: + case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: + case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: + case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: + case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: + case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: + case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: + case SC_READYCOUNTER:case SC_DODGE: case SC_WARM: + case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: + case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: + case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: + case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: + case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: + case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: + case SC_INTOABYSS: case SC_SIEGFRIED: case SC_WHISTLE: + case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: + case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: + case SC_SERVICE4U: case SC_FOOD_STR_CASH: case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: case SC_ELECTRICSHOCKER: case SC_BITE: + case SC__STRIPACCESSORY: case SC__ENERVATION: case SC__GROOMY: + case SC__IGNORANCE: case SC__LAZINESS: case SC__UNLUCKY: + case SC__WEAKNESS: //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: + case SC_MAGNETICFIELD://case SC_MINOR_BBQ: case SC_SIROMA_ICE_TEA: + //case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: + case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: + case SC_STEALTHFIELD_MASTER: case SC_STEALTHFIELD: + case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: + case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: + case SC_PARTYFLEE: case SC_GT_REVITALIZE: + case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: +#ifdef RENEWAL + case SC_EXTREMITYFIST2: +#endif + continue; + case SC_ASSUMPTIO: + if( bl->type == BL_MOB ) + continue; + break; + } + if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. + status_change_end(bl,(sc_type)i,INVALID_TIMER); + } + break; + } + map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, src, skill_id, skill_lv, tick, flag|1, skill_castend_damage_id); + break; + + case AB_SILENTIUM: + // Should the level of Lex Divina be equivalent to the level of Silentium or should the highest level learned be used? [LimitLine] + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, PR_LEXDIVINA, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + break; + /** + * Warlock + **/ + case WL_STASIS: + if( flag&1 ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id, skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,(map_flag_vs(src->m)?BCT_ALL:BCT_ENEMY|BCT_SELF)|flag|1,skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case WL_WHITEIMPRISON: + if( (src == bl || battle_check_target(src, bl, BCT_ENEMY)) && !is_boss(bl) )// Should not work with bosses. + { + int rate = ( sd? sd->status.job_level : 50 ) / 4; + + if( src == bl ) rate = 100; // Success Chance: On self, 100% + else if(bl->type == BL_PC) rate += 20 + 10 * skill_lv; // On Players, (20 + 10 * Skill Level) % + else rate += 40 + 10 * skill_lv; // On Monsters, (40 + 10 * Skill Level) % + + if( sd ) + skill_blockpc_start(sd,skill_id,4000); + + if( !(tsc && tsc->data[type]) ){ + i = sc_start2(bl,type,rate,skill_lv,src->id,(src == bl)?5000:(bl->type == BL_PC)?skill_get_time(skill_id,skill_lv):skill_get_time2(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + if( !i ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + }else + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); + break; + + case WL_FROSTMISTY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); + break; + + case WL_JACKFROST: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinshootrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case WL_MARSHOFABYSS: + // Should marsh of abyss still apply half reduction to players after the 28/10 patch? [LimitLine] + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start4(bl, type, 100, skill_lv, status_get_int(src), sd ? sd->status.job_level : 50, 0, + skill_get_time(skill_id, skill_lv))); + break; + + case WL_SIENNAEXECRATE: + if( status_isimmune(bl) || !tsc ) + break; + + if( flag&1 ) { + if( bl->id == skill_area_temp[1] ) + break; // Already work on this target + + if( tsc && tsc->data[SC_STONE] ) + status_change_end(bl,SC_STONE,INVALID_TIMER); + else + status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); + } else { + int rate = 40 + 8 * skill_lv + ( sd? sd->status.job_level : 50 ) / 4; + // IroWiki says Rate should be reduced by target stats, but currently unknown + if( rnd()%100 < rate ) { // Success on First Target + if( !tsc->data[SC_STONE] ) + rate = status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); + else { + rate = 1; + status_change_end(bl,SC_STONE,INVALID_TIMER); + } + + if( rate ) { + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); + } + // Doesn't send failure packet if it fails on defense. + } + else if( sd ) // Failure on Rate + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case WL_SUMMONFB: + case WL_SUMMONBL: + case WL_SUMMONWB: + case WL_SUMMONSTONE: + { + short element = 0, sctype = 0, pos = -1; + struct status_change *sc = status_get_sc(src); + if( !sc ) break; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + { + if( !sctype && !sc->data[i] ) + sctype = i; // Take the free SC + if( sc->data[i] ) + pos = max(sc->data[i]->val2,pos); + } + + if( !sctype ) + { + if( sd ) // No free slots to put SC + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); + break; + } + + pos++; // Used in val2 for SC. Indicates the order of this ball + switch( skill_id ) + { // Set val1. The SC element for this ball + case WL_SUMMONFB: element = WLS_FIRE; break; + case WL_SUMMONBL: element = WLS_WIND; break; + case WL_SUMMONWB: element = WLS_WATER; break; + case WL_SUMMONSTONE: element = WLS_STONE; break; + } + + sc_start4(src,sctype,100,element,pos,skill_lv,0,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,0,0); + } + break; + + case WL_READING_SB: + if( sd ) { + struct status_change *sc = status_get_sc(bl); + + for( i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) + if( sc && !sc->data[i] ) + break; + if( i == SC_MAXSPELLBOOK ) { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); + break; + } + + sc_start(bl, SC_STOP, 100, skill_lv, INVALID_TIMER); //Can't move while selecting a spellbook. + clif_spellbook_list(sd); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + /** + * Ranger + **/ + case RA_FEARBREEZE: + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + + case RA_WUGMASTERY: + if( sd ) { + if( !pc_iswug(sd) ) + pc_setoption(sd,sd->sc.option|OPTION_WUG); + else + pc_setoption(sd,sd->sc.option&~OPTION_WUG); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RA_WUGRIDER: + if( sd ) { + if( !pc_isridingwug(sd) && pc_iswug(sd) ) { + pc_setoption(sd,sd->sc.option&~OPTION_WUG); + pc_setoption(sd,sd->sc.option|OPTION_WUGRIDER); + } else if( pc_isridingwug(sd) ) { + pc_setoption(sd,sd->sc.option&~OPTION_WUGRIDER); + pc_setoption(sd,sd->sc.option|OPTION_WUG); + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RA_WUGDASH: + if( tsce ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + if( sd && pc_isridingwug(sd) ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,1)); + clif_walkok(sd); + } + break; + + case RA_SENSITIVEKEEN: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); + break; + /** + * Mechanic + **/ + case NC_F_SIDESLIDE: + case NC_B_SIDESLIDE: + { + uint8 dir = (skill_id == NC_F_SIDESLIDE) ? (unit_getdir(src)+4)%8 : unit_getdir(src); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),dir,0x1); + clif_slide(src,src->x,src->y); + clif_fixpos(src); //Aegis sent this packet + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case NC_SELFDESTRUCTION: + if( sd ) { + if( pc_ismadogear(sd) ) + pc_setmadogear(sd, 0); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); + status_set_sp(src, 0, 0); + } + break; + + case NC_ANALYZE: + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start(bl,type, 30 + 12 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv))); + if( sd ) pc_overheat(sd,1); + break; + + case NC_MAGNETICFIELD: + if( (i = sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))) ) + { + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_SPLASH|1,skill_castend_damage_id);; + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + if (sd) pc_overheat(sd,1); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,i); + break; + + case NC_REPAIR: + if( sd ) + { + int heal; + if( dstsd && pc_ismadogear(dstsd) ) + { + heal = dstsd->status.max_hp * (3+3*skill_lv) / 100; + status_heal(bl,heal,0,2); + } else { + heal = sd->status.max_hp * (3+3*skill_lv) / 100; + status_heal(src,heal,0,2); + } + + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, heal); + } + break; + + case NC_DISJOINT: + { + if( bl->type != BL_MOB ) break; + md = map_id2md(bl->id); + if( md && md->class_ >= MOBID_SILVERSNIPER && md->class_ <= MOBID_MAGICDECOY_WIND ) + status_kill(bl); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + case SC_AUTOSHADOWSPELL: + if( sd ) { + if( sd->status.skill[sd->reproduceskill_id].id || sd->status.skill[sd->cloneskill_id].id ) { + sc_start(src,SC_STOP,100,skill_lv,-1);// The skill_lv is stored in val1 used in skill_select_menu to determine the used skill lvl [Xazax] + clif_autoshadowspell_list(sd); + clif_skill_nodamage(src,bl,skill_id,1,1); + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_IMITATION_SKILL_NONE,0); + } + break; + + case SC_SHADOWFORM: + if( sd && dstsd && src != bl && !dstsd->shadowform_id ) { + if( clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,type,100,skill_lv,bl->id,4+skill_lv,0,skill_get_time(skill_id, skill_lv))) ) + dstsd->shadowform_id = src->id; + } + else if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + + case SC_BODYPAINT: + if( flag&1 ) { + if( tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || + tsc->data[SC_CHASEWALK] || tsc->data[SC_CLOAKINGEXCEED] || + tsc->data[SC__INVISIBILITY]) ) { + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CHASEWALK, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,53 + 2 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } else { + clif_skill_nodamage(src, bl, skill_id, 0, 1); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + } + break; + + case SC_ENERVATION: + case SC_GROOMY: + case SC_LAZINESS: + case SC_UNLUCKY: + case SC_WEAKNESS: + if( !(tsc && tsc->data[type]) ) { + //((rand(myDEX / 12, myDEX / 4) + myJobLevel + 10 * skLevel) + myLevel / 10) - (targetLevel / 10 + targetLUK / 10 + (targetMaxWeight - targetWeight) / 1000 + rand(targetAGI / 6, targetAGI / 3)) + int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 + - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); + rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); + clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv))); + } else if( sd ) + clif_skill_fail(sd,skill_id,0,0); + break; + + case SC_IGNORANCE: + if( !(tsc && tsc->data[type]) ) { + int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 + - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); + rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); + if (clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv)))) { + int sp = 200 * skill_lv; + if( dstmd ) sp = dstmd->level * 2; + if( status_zap(bl,0,sp) ) + status_heal(src,0,sp/2,3); + } + else if( sd ) clif_skill_fail(sd,skill_id,0,0); + } else if( sd ) + clif_skill_fail(sd,skill_id,0,0); + break; + + case LG_TRAMPLE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_destroy_trap,bl,skill_get_splash(skill_id,skill_lv),BL_SKILL,tick); + break; + + case LG_REFLECTDAMAGE: + if( tsc && tsc->data[type] ) + status_change_end(bl,type,INVALID_TIMER); + else + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case LG_SHIELDSPELL: + if( flag&1 ) { + int duration = (sd) ? sd->bonus.shieldmdef * 2000 : 10000; + sc_start(bl,SC_SILENCE,100,skill_lv,duration); + } else if( sd ) { + int opt = skill_lv; + int rate = rnd()%100; + int val, brate; + switch( skill_lv ) { + case 1: + { + struct item_data *shield_data = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; + if( !shield_data || shield_data->type != IT_ARMOR ) { // No shield? + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + brate = shield_data->def * 10; + if( rate < 50 ) + opt = 1; + else if( rate < 75 ) + opt = 2; + else + opt = 3; + + switch( opt ) { + case 1: + sc_start(bl,SC_SHIELDSPELL_DEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + status_change_end(bl,SC_SHIELDSPELL_DEF,INVALID_TIMER); + break; + case 2: + val = shield_data->def / 10; // % Reflected damage. + sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 1000); + break; + case 3: + val = shield_data->def; // Attack increase. + sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 3000); + break; + } + } + break; + + case 2: + brate = sd->bonus.shieldmdef * 20; + if( rate < 30 ) + opt = 1; + else if( rate < 60 ) + opt = 2; + else + opt = 3; + switch( opt ) { + case 1: + sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|2,skill_castend_damage_id); + status_change_end(bl,SC_SHIELDSPELL_MDEF,INVALID_TIMER); + break; + case 2: + sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); + break; + case 3: + if( sc_start(bl,SC_SHIELDSPELL_MDEF,brate,opt,sd->bonus.shieldmdef * 30000) ) + clif_skill_nodamage(src,bl,PR_MAGNIFICAT,skill_lv, + sc_start(bl,SC_MAGNIFICAT,100,1,sd->bonus.shieldmdef * 30000)); + break; + } + break; + + case 3: + { + struct item *it = &sd->status.inventory[sd->equip_index[EQI_HAND_L]]; + if( !it ) { // No shield? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + brate = it->refine * 5; + if( rate < 25 ) + opt = 1; + else if( rate < 50 ) + opt = 2; + else + opt = 3; + switch( opt ) { + case 1: + val = 105 * it->refine / 10; + sc_start2(bl,SC_SHIELDSPELL_REF,brate,opt,val,skill_get_time(skill_id,skill_lv)); + break; + case 2: case 3: + if( rate < brate ) + { + val = sstatus->max_hp * (11 + it->refine) / 100; + status_heal(bl, val, 0, 3); + } + break; + /*case 3: + // Full protection. I need confirm what effect should be here. Moved to case 2 to until we got it. + break;*/ + } + } + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case LG_PIETY: + if( flag&1 ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else { + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_PC,src,skill_id,skill_lv,tick,flag|SD_PREAMBLE|BCT_PARTY|BCT_SELF|1,skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case LG_INSPIRATION: + if( sd && !map[sd->bl.m].flag.noexppenalty && sd->status.base_level != MAX_LEVEL ) { + sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. + sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + } + clif_skill_nodamage(bl,src,skill_id,skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + case SR_CURSEDCIRCLE: + if( flag&1 ) { + if( is_boss(bl) ) break; + if( sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))) { + if( bl->type == BL_MOB ) + mob_unlocktarget((TBL_MOB*)bl,gettick()); + unit_stop_attack(bl); + clif_bladestop(src, bl->id, 1); + map_freeblock_unlock(); + return 1; + } + } else { + int count = 0; + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + count = map_forcountinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), (sd)?sd->spiritball_old:15, // Assume 15 spiritballs in non-charactors + BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + if( sd ) pc_delspiritball(sd, count, 0); + clif_skill_nodamage(src, src, skill_id, skill_lv, + sc_start2(src, SC_CURSEDCIRCLE_ATKER, 100, skill_lv, count, skill_get_time(skill_id,skill_lv))); + } + break; + + case SR_RAISINGDRAGON: + if( sd ) { + short max = 5 + skill_lv; + sc_start(bl, SC_EXPLOSIONSPIRITS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + for( i = 0; i < max; i++ ) // Don't call more than max available spheres. + pc_addspiritball(sd, skill_get_time(skill_id, skill_lv), max); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv))); + } + break; + + case SR_ASSIMILATEPOWER: + if( flag&1 ) { + i = 0; + if( dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER ) + { + i = dstsd->spiritball; //1%sp per spiritball. + pc_delspiritball(dstsd, dstsd->spiritball, 0); + } + if( i ) status_percent_heal(src, 0, i); + clif_skill_nodamage(src, bl, skill_id, skill_lv, i ? 1:0); + } else { + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF|SD_SPLASH|1, skill_castend_nodamage_id); + } + break; + + case SR_POWERVELOCITY: + if( !dstsd ) + break; + if( sd && dstsd->spiritball <= 5 ) { + for(i = 0; i <= 5; i++) { + pc_addspiritball(dstsd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), i); + pc_delspiritball(sd, sd->spiritball, 0); + } + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + break; + + case SR_GENTLETOUCH_CURE: + { + int heal; + + if( status_isimmune(bl) ) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + + heal = 120 * skill_lv + status_get_max_hp(bl) * (2 + skill_lv) / 100; + status_heal(bl, heal, 0, 0); + + if( (tsc && tsc->opt1) && (rnd()%100 < ((skill_lv * 5) + (status_get_dex(src) + status_get_lv(src)) / 4) - (1 + (rnd() % 10))) ) + { + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_HALLUCINATION, INVALID_TIMER); + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + case SR_GENTLETOUCH_CHANGE: + case SR_GENTLETOUCH_REVITALIZE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); + break; + case WA_SWING_DANCE: + case WA_MOONLIT_SERENADE: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else if( sd ) { // Only shows effects on caster. + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case WA_SYMPHONY_OF_LOVER: + case MI_RUSH_WINDMILL: + case MI_ECHOSONG: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + sc_start4(bl,type,100,skill_lv,6*skill_lv,(sd?pc_checkskill(sd,WM_LESSON):0),(sd?sd->status.job_level:0),skill_get_time(skill_id,skill_lv)); + else if( sd ) { // Only shows effects on caster. + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case MI_HARMONIZE: + if( src != bl ) + clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start(src, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); + break; + + case WM_DEADHILLHERE: + if( bl->type == BL_PC ) { + if( !status_isdead(bl) ) + break; + + if( rnd()%100 < 88 + 2 * skill_lv ) { + int heal = tstatus->sp; + if( heal <= 0 ) + heal = 1; + tstatus->hp = heal; + tstatus->sp -= tstatus->sp * ( 120 - 20 * skill_lv ) / 100; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + pc_revive((TBL_PC*)bl,heal,0); + clif_resurrection(bl,1); + } + } + break; + + case WM_SIRCLEOFNATURE: + flag |= BCT_SELF|BCT_PARTY|BCT_GUILD; + case WM_VOICEOFSIREN: + if( skill_id != WM_SIRCLEOFNATURE ) + flag &= ~BCT_SELF; + if( flag&1 ) { + sc_start2(bl,type,(skill_id==WM_VOICEOFSIREN)?20+10*skill_lv:100,skill_lv,(skill_id==WM_VOICEOFSIREN)?src->id:0,skill_get_time(skill_id,skill_lv)); + } else { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),(skill_id==WM_VOICEOFSIREN)?BL_CHAR|BL_SKILL:BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case WM_GLOOMYDAY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( dstsd && ( pc_checkskill(dstsd,KN_BRANDISHSPEAR) || pc_checkskill(dstsd,LK_SPIRALPIERCE) || + pc_checkskill(dstsd,CR_SHIELDCHARGE) || pc_checkskill(dstsd,CR_SHIELDBOOMERANG) || + pc_checkskill(dstsd,PA_SHIELDCHAIN) || pc_checkskill(dstsd,LG_SHIELDPRESS) ) ) + { + sc_start(bl,SC_GLOOMYDAY_SK,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + } + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case WM_SATURDAY_NIGHT_FEVER: + if( flag&1 ) { // Affect to all targets arround the caster and caster too. + if( !(tsc && tsc->data[type]) ) + sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv)); + } else if( flag&2 ) { + if( src->id != bl->id && battle_check_target(src,bl,BCT_ENEMY) > 0 ) + status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); + } else if( sd ) { + short chance = sstatus->int_/6 + sd->status.job_level/5 + skill_lv*4; + if( !sd->status.party_id || (rnd()%100 > chance)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); + break; + } + if( map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id,skill_lv), + BL_PC, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count) > 7 ) + flag |= 2; + else + flag |= 1; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start(src,SC_STOP,100,skill_lv,skill_get_time2(skill_id,skill_lv))); + if( flag&2 ) // Dealed here to prevent conflicts + status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); + } + break; + + case WM_SONG_OF_MANA: + case WM_DANCE_WITH_WUG: + case WM_LERADS_DEW: + if( flag&1 ) { // These affect to to all party members near the caster. + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[type] ) { + sc_start2(bl,type,100,skill_lv,sc->data[type]->val2,skill_get_time(skill_id,skill_lv)); + } + } else if( sd ) { + short lv = (short)skill_lv; + int count = skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1); + if( sc_start2(bl,type,100,skill_lv,count,skill_get_time(skill_id,skill_lv)) ) + party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + } + break; + + case WM_MELODYOFSINK: + case WM_BEYOND_OF_WARCRY: + case WM_UNLIMITED_HUMMING_VOICE: + if( flag&1 ) { + sc_start2(bl,type,100,skill_lv,skill_area_temp[0],skill_get_time(skill_id,skill_lv)); + } else { // These affect to all targets arround the caster. + short lv = (short)skill_lv; + skill_area_temp[0] = (sd) ? skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1) : 50; // 50% chance in non BL_PC (clones). + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case WM_RANDOMIZESPELL: { + int improv_skill_id = 0, improv_skill_lv; + do { + i = rnd() % MAX_SKILL_IMPROVISE_DB; + improv_skill_id = skill_improvise_db[i].skill_id; + } while( improv_skill_id == 0 || rnd()%10000 >= skill_improvise_db[i].per ); + improv_skill_lv = 4 + skill_lv; + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + + if( sd ) { + sd->state.abra_flag = 2; + sd->skillitem = improv_skill_id; + sd->skillitemlv = improv_skill_lv; + clif_item_skill(sd, improv_skill_id, improv_skill_lv); + } else { + struct unit_data *ud = unit_bl2ud(src); + int inf = skill_get_inf(improv_skill_id); + int target_id = 0; + if (!ud) break; + if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { + if (src->type == BL_PET) + bl = (struct block_list*)((TBL_PET*)src)->msd; + if (!bl) bl = src; + unit_skilluse_id(src, bl->id, improv_skill_id, improv_skill_lv); + } else { + if (ud->target) + target_id = ud->target; + else switch (src->type) { + case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; + case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; + } + if (!target_id) + break; + if (skill_get_casttype(improv_skill_id) == CAST_GROUND) { + bl = map_id2bl(target_id); + if (!bl) bl = src; + unit_skilluse_pos(src, bl->x, bl->y, improv_skill_id, improv_skill_lv); + } else + unit_skilluse_id(src, target_id, improv_skill_id, improv_skill_lv); + } + } + } + break; + + + case RETURN_TO_ELDICASTES: + case ALL_GUARDIAN_RECALL: + if( sd ) + { + short x, y; // Destiny position. + unsigned short mapindex; + + if( skill_id == RETURN_TO_ELDICASTES) + { + x = 198; + y = 187; + mapindex = mapindex_name2id(MAP_DICASTES); + } + else + { + x = 44; + y = 151; + mapindex = mapindex_name2id(MAP_MORA); + } + + if(!mapindex) + { //Given map not found? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); + } + break; + + case GM_SANDMAN: + if( tsc ) { + if( tsc->opt1 == OPT1_SLEEP ) + tsc->opt1 = 0; + else + tsc->opt1 = OPT1_SLEEP; + clif_changeoption(bl); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + } + break; + + case SO_ARRULLO: + if( flag&1 ) + sc_start2(bl, type, 88 + 2 * skill_lv, skill_lv, 1, skill_get_time(skill_id, skill_lv)); + else { + clif_skill_nodamage(src, bl, skill_id, 0, 1); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + } + break; + + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + if( sd ) { + int elemental_class = skill_get_elemental_type(skill_id,skill_lv); + + // Remove previous elemental fisrt. + if( sd->ed ) + elemental_delete(sd->ed,0); + + // Summoning the new one. + if( !elemental_create(sd,elemental_class,skill_get_time(skill_id,skill_lv)) ) { + clif_skill_fail(sd,skill_id,0,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SO_EL_CONTROL: + if( sd ) { + int mode = EL_MODE_PASSIVE; // Standard mode. + + if( !sd->ed ) break; + + if( skill_lv == 4 ) {// At level 4 delete elementals. + elemental_delete(sd->ed, 0); + break; + } + switch( skill_lv ) {// Select mode bassed on skill level used. + case 2: mode = EL_MODE_ASSIST; break; + case 3: mode = EL_MODE_AGGRESSIVE; break; + } + if( !elemental_change_mode(sd->ed,mode) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SO_EL_ACTION: + if( sd ) { + int duration = 3000; + if( !sd->ed ) break; + sd->skill_id_old = skill_id; + elemental_action(sd->ed, bl, tick); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + switch(sd->ed->db->class_){ + case 2115:case 2124: + case 2118:case 2121: + duration = 6000; + break; + case 2116:case 2119: + case 2122:case 2125: + duration = 9000; + break; + } + skill_blockpc_start(sd, skill_id, duration); + } + break; + + case SO_EL_CURE: + if( sd ) { + struct elemental_data *ed = sd->ed; + int s_hp = sd->battle_status.hp * 10 / 100, s_sp = sd->battle_status.sp * 10 / 100; + int e_hp, e_sp; + + if( !ed ) break; + if( !status_charge(&sd->bl,s_hp,s_sp) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + e_hp = ed->battle_status.max_hp * 10 / 100; + e_sp = ed->battle_status.max_sp * 10 / 100; + status_heal(&ed->bl,e_hp,e_sp,3); + clif_skill_nodamage(src,&ed->bl,skill_id,skill_lv,1); + } + break; + + case GN_CHANGEMATERIAL: + case SO_EL_ANALYSIS: + if( sd ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_itemlistwindow(sd,skill_id,skill_lv); + } + break; + + case GN_BLOOD_SUCKER: + { + struct status_change *sc = status_get_sc(src); + + if( sc && sc->bs_counter < skill_get_maxcount( skill_id , skill_lv) ) { + if( tsc && tsc->data[type] ){ + (sc->bs_counter)--; + status_change_end(src, type, INVALID_TIMER); // the first one cancels and the last one will take effect resetting the timer + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id,skill_lv)); + (sc->bs_counter)++; + } else if( sd ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + } + break; + + case GN_MANDRAGORA: + if( flag&1 ) { + if ( clif_skill_nodamage(bl, src, skill_id, skill_lv, + sc_start(bl, type, 25 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv))) ) + status_zap(bl, 0, status_get_max_sp(bl) * (25 + 5 * skill_lv) / 100); + } else + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + break; + + case GN_SLINGITEM: + if( sd ) { + short ammo_id; + i = sd->equip_index[EQI_AMMO]; + if( i <= 0 ) + break; // No ammo. + ammo_id = sd->inventory_data[i]->nameid; + if( ammo_id <= 0 ) + break; + sd->itemid = ammo_id; + if( itemdb_is_GNbomb(ammo_id) ) { + if(battle_check_target(src,bl,BCT_ENEMY) > 0) {// Only attack if the target is an enemy. + if( ammo_id == 13263 ) + map_foreachincell(skill_area_sub,bl->m,bl->x,bl->y,BL_CHAR,src,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(BF_WEAPON,src,src,bl,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag); + } else //Otherwise, it fails, shows animation and removes items. + clif_skill_fail(sd,GN_SLINGITEM_RANGEMELEEATK,0xa,0); + } else if( itemdb_is_GNthrowable(ammo_id) ){ + struct script_code *script = sd->inventory_data[i]->script; + if( !script ) + break; + if( dstsd ) + run_script(script,0,dstsd->bl.id,fake_nd->bl.id); + else + run_script(script,0,src->id,0); + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1);// This packet is received twice actually, I think it is to show the animation. + break; + + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + if( sd ) { + int qty = 1; + sd->skill_id_old = skill_id; + sd->skill_lv_old = skill_lv; + if( skill_id != GN_S_PHARMACY && skill_lv > 1 ) + qty = 10; + clif_cooking_list(sd,(skill_id - GN_MIX_COOKING) + 27,skill_id,qty,skill_id==GN_MAKEBOMB?5:6); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + case EL_CIRCLE_OF_FIRE: + case EL_PYROTECHNIC: + case EL_HEATER: + case EL_TROPIC: + case EL_AQUAPLAY: + case EL_COOLER: + case EL_CHILLY_AIR: + case EL_GUST: + case EL_BLAST: + case EL_WILD_STORM: + case EL_PETROLOGY: + case EL_CURSED_SOIL: + case EL_UPHEAVAL: + case EL_FIRE_CLOAK: + case EL_WATER_DROP: + case EL_WIND_CURTAIN: + case EL_SOLID_SKIN: + case EL_STONE_SHIELD: + case EL_WIND_STEP: { + struct elemental_data *ele = BL_CAST(BL_ELEM, src); + if( ele ) { + sc_type type2 = type-1; + struct status_change *sc = status_get_sc(&ele->bl); + + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } else { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_skill_damage(src, ( skill_id == EL_GUST || skill_id == EL_BLAST || skill_id == EL_WILD_STORM )?src:bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( skill_id == EL_WIND_STEP ) // There aren't teleport, just push the master away. + skill_blown(src,bl,(rnd()%skill_get_blewcount(skill_id,skill_lv))+1,rand()%8,0); + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case EL_FIRE_MANTLE: + case EL_WATER_BARRIER: + case EL_ZEPHYR: + case EL_POWER_OF_GAIA: + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + skill_unitsetting(src,skill_id,skill_lv,bl->x,bl->y,0); + break; + + case EL_WATER_SCREEN: { + struct elemental_data *ele = BL_CAST(BL_ELEM, src); + if( ele ) { + struct status_change *sc = status_get_sc(&ele->bl); + sc_type type2 = type-1; + + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } else { + // This not heals at the end. + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,type,100,src->id,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case KO_KAHU_ENTEN: + case KO_HYOUHU_HUBUKI: + case KO_KAZEHU_SEIRAN: + case KO_DOHU_KOUKAI: + if(sd) { + int ttype = skill_get_ele(skill_id, skill_lv); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + pc_add_talisman(sd, skill_get_time(skill_id, skill_lv), 10, ttype); + } + break; + + case KO_ZANZOU: + if(sd){ + struct mob_data *md; + + md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), 2308, "", SZ_SMALL, AI_NONE); + if( md ) + { + md->master_id = src->id; + md->special_state.ai = AI_ZANZOU; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn( md ); + pc_setinvincibletimer(sd,500);// unlock target lock + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); + } + } + break; + + case KO_KYOUGAKU: + if( dstsd && tsc && !tsc->data[type] && rand()%100 < tstatus->int_/2 ){ + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + }else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case KO_JYUSATSU: + if( dstsd && tsc && !tsc->data[type] && + rand()%100 < ((45+5*skill_lv) + skill_lv*5 - status_get_int(bl)/2) ){//[(Base chance of success) + (Skill Level x 5) - (int / 2)]%. + clif_skill_nodamage(src,bl,skill_id,skill_lv, + status_change_start(bl,type,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),1)); + status_zap(bl, tstatus->max_hp*skill_lv*5/100 , 0); + if( status_get_lv(bl) <= status_get_lv(src) ) + status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); + }else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case KO_GENWAKU: + if ( !map_flag_gvg(src->m) && ( dstsd || dstmd ) && battle_check_target(src,bl,BCT_ENEMY) > 0 ) { + int x = src->x, y = src->y; + + if( sd && rnd()%100 > ((45+5*skill_lv) - status_get_int(bl)/10) ){//[(Base chance of success) - (Intelligence Objectives / 10)]%. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + if (unit_movepos(src,bl->x,bl->y,0,0)) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_slide(src,bl->x,bl->y) ; + sc_start(src,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); + if (unit_movepos(bl,x,y,0,0)) + { + clif_skill_damage(bl,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, -1, 6); + if( bl->type == BL_PC && pc_issit((TBL_PC*)bl)) + clif_sitting(bl); //Avoid sitting sync problem + clif_slide(bl,x,y) ; + sc_start(bl,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case OB_AKAITSUKI: + case OB_OBOROGENSOU: + if( sd && ( (skill_id == OB_OBOROGENSOU && bl->type == BL_MOB) // This skill does not work on monsters. + || is_boss(bl) ) ){ // Does not work on Boss monsters. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + case KO_IZAYOI: + case OB_ZANGETSU: + case KG_KYOMU: + case KG_KAGEMUSYA: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + + case KG_KAGEHUMI: + if( flag&1 ){ + if(tsc && ( tsc->option&(OPTION_CLOAK|OPTION_HIDE) || + tsc->data[SC_CAMOUFLAGE] || tsc->data[SC__SHADOWFORM] || + tsc->data[SC_MARIONETTE] || tsc->data[SC_HARMONIZE])){ + sc_start(src, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + } + if( skill_area_temp[2] == 1 ){ + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + sc_start(src, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + } + }else{ + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_nodamage_id); + } + break; + + case MH_SILENT_BREEZE: { + struct status_change *ssc = status_get_sc(src); + struct block_list *m_bl = battle_get_master(src); + const enum sc_type scs[] = { + SC_MANDRAGORA, SC_HARMONIZE, SC_DEEPSLEEP, SC_VOICEOFSIREN, SC_SLEEP, SC_CONFUSION, SC_HALLUCINATION + }; + int heal; + if(tsc){ + for (i = 0; i < ARRAYLENGTH(scs); i++) { + if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); + } + if (!tsc->data[SC_SILENCE]) //put inavoidable silence on target + status_change_start(bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + } + heal = status_get_matk_min(src)*4; + status_heal(bl, heal, 0, 7); + + //now inflict silence on everyone + if(ssc && !ssc->data[SC_SILENCE]) //put inavoidable silence on homun + status_change_start(src, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + if(m_bl){ + struct status_change *msc = status_get_sc(m_bl); + if(msc && !msc->data[SC_SILENCE]) //put inavoidable silence on master + status_change_start(m_bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + } + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + case MH_OVERED_BOOST: + if (hd){ + struct block_list *s_bl = battle_get_master(src); + if(hd->homunculus.hunger>50) //reduce hunger + hd->homunculus.hunger = hd->homunculus.hunger/2; + else + hd->homunculus.hunger = min(1,hd->homunculus.hunger); + if(s_bl && s_bl->type==BL_PC){ + status_set_sp(s_bl,status_get_max_sp(s_bl)/2,0); //master drain 50% sp + clif_send_homdata(((TBL_PC *)s_bl), SP_HUNGRY, hd->homunculus.hunger); //refresh hunger info + sc_start(s_bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); //gene bonus + } + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + case MH_GRANITIC_ARMOR: + case MH_PYROCLASTIC: { + struct block_list *s_bl = battle_get_master(src); + if(s_bl) sc_start2(s_bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); //start on master + sc_start2(bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); + if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + + case MH_LIGHT_OF_REGENE: + if(hd){ + hd->homunculus.intimacy = 251; //change to neutral (can't be cast if < 750) + if(sd) clif_send_homdata(sd, SP_INTIMATE, hd->homunculus.intimacy); //refresh intimacy info + } + //don't break need to start status and start block timer + case MH_STYLE_CHANGE: + case MH_MAGMA_FLOW: + case MH_PAIN_KILLER: + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + break; + case MH_SUMMON_LEGION: + { + int summons[5] = {1004, 1303, 1303, 1994, 1994}; + int qty[5] = {3 , 3 , 4 , 4 , 5}; + struct mob_data *md; + int i; + + for(i=0; i<qty[skill_lv - 1]; i++){ //easy way + md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), summons[skill_lv - 1], "", SZ_SMALL, AI_ATTACK); + if (md) { + md->master_id = src->id; + if (md->deletetimer != INVALID_TIMER) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn(md); //Now it is ready for spawning. + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_ASSIST, 0, 60000); + } + } + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + default: + ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skill_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_freeblock_unlock(); + return 1; + } + + if(skill_id != SR_CURSEDCIRCLE){ + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] )//Should only remove after the skill had been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + } + + if (dstmd) { //Mob skill event for no damage skills (damage ones are handled in battle_calc_damage) [Skotlex] + mob_log_damage(dstmd, src, 0); //Log interaction (counts as 'attacker' for the exp bonus) + mobskill_event(dstmd, src, tick, MSC_SKILLUSED|(skill_id<<16)); + } + + if( sd && !(flag&1) ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk ) + {// consume arrow on last invocation to this skill. + battle_consume_ammo(sd, skill_id, skill_lv); + } + skill_onskillusage(sd, bl, skill_id, tick); + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *target, *src; + struct map_session_data *sd; + struct mob_data *md; + struct unit_data *ud; + struct status_change *sc = NULL; + int inf,inf2,flag = 0; + + src = map_id2bl(id); + if( src == NULL ) + { + ShowDebug("skill_castend_id: src == NULL (tid=%d, id=%d)\n", tid, id); + return 0;// not found + } + + ud = unit_bl2ud(src); + if( ud == NULL ) + { + ShowDebug("skill_castend_id: ud == NULL (tid=%d, id=%d)\n", tid, id); + return 0;// ??? + } + + sd = BL_CAST(BL_PC, src); + md = BL_CAST(BL_MOB, src); + + if( src->prev == NULL ) { + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if(ud->skill_id != SA_CASTCANCEL && ud->skill_id != SO_SPELLFIST) {// otherwise handled in unit_skillcastcancel() + if( ud->skilltimer != tid ) { + ShowError("skill_castend_id: Timer mismatch %d!=%d!\n", ud->skilltimer, tid); + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( sd && ud->skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK) ) + {// restore original walk speed + ud->skilltimer = INVALID_TIMER; + status_calc_bl(&sd->bl, SCB_SPEED); + } + + ud->skilltimer = INVALID_TIMER; + } + + if (ud->skilltarget == id) + target = src; + else + target = map_id2bl(ud->skilltarget); + + // Use a do so that you can break out of it when the skill fails. + do { + if(!target || target->prev==NULL) break; + + if(src->m != target->m || status_isdead(src)) break; + + switch (ud->skill_id) { + //These should become skill_castend_pos + case WE_CALLPARTNER: + if(sd) clif_callpartner(sd); + case WE_CALLPARENT: + case WE_CALLBABY: + case AM_RESURRECTHOMUN: + case PF_SPIDERWEB: + //Find a random spot to place the skill. [Skotlex] + inf2 = skill_get_splash(ud->skill_id, ud->skill_lv); + ud->skillx = target->x + inf2; + ud->skilly = target->y + inf2; + if (inf2 && !map_random_dir(target, &ud->skillx, &ud->skilly)) { + ud->skillx = target->x; + ud->skilly = target->y; + } + ud->skilltimer=tid; + return skill_castend_pos(tid,tick,id,data); + case GN_WALLOFTHORN: + ud->skillx = target->x; + ud->skilly = target->y; + ud->skilltimer = tid; + return skill_castend_pos(tid,tick,id,data); + } + + if(ud->skill_id == RG_BACKSTAP) { + uint8 dir = map_calc_dir(src,target->x,target->y),t_dir = unit_getdir(target); + if(check_distance_bl(src, target, 0) || map_check_dir(dir,t_dir)) { + break; + } + } + + if( ud->skill_id == PR_TURNUNDEAD ) + { + struct status_data *tstatus = status_get_status_data(target); + if( !battle_check_undead(tstatus->race, tstatus->def_ele) ) + break; + } + + if( ud->skill_id == RA_WUGSTRIKE ){ + if( !path_search(NULL,src->m,src->x,src->y,target->x,target->y,1,CELL_CHKNOREACH)) + break; + } + + if( ud->skill_id == PR_LEXDIVINA || ud->skill_id == MER_LEXDIVINA ) + { + sc = status_get_sc(target); + if( battle_check_target(src,target, BCT_ENEMY) <= 0 && (!sc || !sc->data[SC_SILENCE]) ) + { //If it's not an enemy, and not silenced, you can't use the skill on them. [Skotlex] + clif_skill_nodamage (src, target, ud->skill_id, ud->skill_lv, 0); + break; + } + } + else + { // Check target validity. + inf = skill_get_inf(ud->skill_id); + inf2 = skill_get_inf2(ud->skill_id); + + if(inf&INF_ATTACK_SKILL || + (inf&INF_SELF_SKILL && inf2&INF2_NO_TARGET_SELF) //Combo skills + ) // Casted through combo. + inf = BCT_ENEMY; //Offensive skill. + else if(inf2&INF2_NO_ENEMY) + inf = BCT_NOENEMY; + else + inf = 0; + + if(inf2 & (INF2_PARTY_ONLY|INF2_GUILD_ONLY) && src != target) + { + inf |= + (inf2&INF2_PARTY_ONLY?BCT_PARTY:0)| + (inf2&INF2_GUILD_ONLY?BCT_GUILD:0); + //Remove neutral targets (but allow enemy if skill is designed to be so) + inf &= ~BCT_NEUTRAL; + } + + if( ud->skill_id >= SL_SKE && ud->skill_id <= SL_SKA && target->type == BL_MOB ) + { + if( ((TBL_MOB*)target)->class_ == MOBID_EMPERIUM ) + break; + } + else if (inf && battle_check_target(src, target, inf) <= 0){ + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + if(inf&BCT_ENEMY && (sc = status_get_sc(target)) && + sc->data[SC_FOGWALL] && + rnd() % 100 < 75) { //Fogwall makes all offensive-type targetted skills fail at 75% + if (sd) clif_skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + } + + //Avoid doing double checks for instant-cast skills. + if (tid != INVALID_TIMER && !status_check_skilluse(src, target, ud->skill_id, 1)) + break; + + if(md) { + md->last_thinktime=tick +MIN_MOBTHINKTIME; + if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) + clif_emotion(src, md->db->skill[md->skill_idx].emotion); + } + + if(src != target && battle_config.skill_add_range && + !check_distance_bl(src, target, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) + { + if (sd) { + clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + if(battle_config.skill_out_range_consume) //Consume items anyway. [Skotlex] + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); + } + break; + } + + if( sd ) + { + if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) + break; + else + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); + } +#ifdef OFFICIAL_WALKPATH + if( !path_search_long(NULL, src->m, src->x, src->y, target->x, target->y, CELL_CHKWALL) ) + break; +#endif + if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) + break; + + if (ud->state.running && ud->skill_id == TK_JUMPKICK) + { + ud->state.running = 0; + status_change_end(src, SC_RUN, INVALID_TIMER); + flag = 1; + } + + if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH) + unit_stop_walking(src,1); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); //Tests show wings don't overwrite the delay but skill scrolls do. [Inkfish] + if (sd) { //Cooldown application + int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); + for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. + if (sd->skillcooldown[i].id == ud->skill_id){ + cooldown += sd->skillcooldown[i].val; + break; + } + } + if(cooldown) + skill_blockpc_start(sd, ud->skill_id, cooldown); + } + if( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); + if( sd ) + { + switch( ud->skill_id ) + { + case GS_DESPERADO: + sd->canequip_tick = tick + skill_get_time(ud->skill_id, ud->skill_lv); + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + if( (sc = status_get_sc(src)) && sc->data[SC_STRIPSHIELD] ) + { + const struct TimerData *timer = get_timer(sc->data[SC_STRIPSHIELD]->timer); + if( timer && timer->func == status_change_timer && DIFF_TICK(timer->tick,gettick()+skill_get_time(ud->skill_id, ud->skill_lv)) > 0 ) + break; + } + sc_start2(src, SC_STRIPSHIELD, 100, 0, 1, skill_get_time(ud->skill_id, ud->skill_lv)); + break; + } + } + if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE) + unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); + + if(battle_config.skill_log && battle_config.skill_log&src->type) + ShowInfo("Type %d, ID %d skill castend id [id =%d, lv=%d, target ID %d]\n", + src->type, src->id, ud->skill_id, ud->skill_lv, target->id); + + map_freeblock_lock(); + + // SC_MAGICPOWER needs to switch states before any damage is actually dealt + skill_toggle_magicpower(src, ud->skill_id); + if( ud->skill_id != RA_CAMOUFLAGE ) // only normal attack and auto cast skills benefit from its bonuses + status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER); + + if (skill_get_casttype(ud->skill_id) == CAST_NODAMAGE) + skill_castend_nodamage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); + else + skill_castend_damage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); + + sc = status_get_sc(src); + if(sc && sc->count) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == ud->skill_id && + ud->skill_id != WZ_WATERBALL) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + + if( sc->data[SC_DANCING] && skill_get_inf2(ud->skill_id)&INF2_SONG_DANCE && sd ) + skill_blockpc_start(sd,BD_ADAPTATION,3000); + } + + if( sd && ud->skill_id != SA_ABRACADABRA && ud->skill_id != WM_RANDOMIZESPELL ) // they just set the data so leave it as it is.[Inkfish] + sd->skillitem = sd->skillitemlv = 0; + + if (ud->skilltimer == INVALID_TIMER) { + if(md) md->skill_idx = -1; + else ud->skill_id = 0; //mobs can't clear this one as it is used for skill condition 'afterskill' + ud->skill_lv = ud->skilltarget = 0; + } + map_freeblock_unlock(); + return 1; + } while(0); + + //Skill failed. + if (ud->skill_id == MO_EXTREMITYFIST && sd && !(sc && sc->data[SC_FOGWALL])) + { //When Asura fails... (except when it fails from Fog of Wall) + //Consume SP/spheres + skill_consume_requirement(sd,ud->skill_id, ud->skill_lv,1); + status_set_sp(src, 0, 0); + sc = &sd->sc; + if (sc->count) + { //End states + status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); +#ifdef RENEWAL + sc_start(src, SC_EXTREMITYFIST2, 100, ud->skill_lv, skill_get_time(ud->skill_id, ud->skill_lv)); +#endif + } + if (target && target->m == src->m) + { //Move character to target anyway. + if (unit_movepos(src, src->x+3, src->y+3, 1, 1)) + { //Display movement + animation. + clif_slide(src,src->x,src->y); + clif_skill_damage(src,target,tick,sd->battle_status.amotion,0,0,1,ud->skill_id, ud->skill_lv, 5); + } + clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + } + } + + ud->skill_id = ud->skill_lv = ud->skilltarget = 0; + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick; + //You can't place a skill failed packet here because it would be + //sent in ALL cases, even cases where skill_check_condition fails + //which would lead to double 'skill failed' messages u.u [Skotlex] + if(sd) + sd->skillitem = sd->skillitemlv = 0; + else if(md) + md->skill_idx = -1; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_pos(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list* src = map_id2bl(id); + int maxcount; + struct map_session_data *sd; + struct unit_data *ud = unit_bl2ud(src); + struct mob_data *md; + + nullpo_ret(ud); + + sd = BL_CAST(BL_PC , src); + md = BL_CAST(BL_MOB, src); + + if( src->prev == NULL ) { + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( ud->skilltimer != tid ) + { + ShowError("skill_castend_pos: Timer mismatch %d!=%d\n", ud->skilltimer, tid); + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( sd && ud->skilltimer != INVALID_TIMER && ( pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK ) ) + {// restore original walk speed + ud->skilltimer = INVALID_TIMER; + status_calc_bl(&sd->bl, SCB_SPEED); + } + ud->skilltimer = INVALID_TIMER; + + do { + if( status_isdead(src) ) + break; + + if( !(src->type&battle_config.skill_reiteration) && + skill_get_unit_flag(ud->skill_id)&UF_NOREITERATION && + skill_check_unit_range(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) + ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( src->type&battle_config.skill_nofootset && + skill_get_unit_flag(ud->skill_id)&UF_NOFOOTSET && + skill_check_unit_range2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) + ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( src->type&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(ud->skill_id, ud->skill_lv)) > 0 + ) { + int i; + for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i] && maxcount;i++) { + if(ud->skillunit[i]->skill_id == ud->skill_id) + maxcount--; + } + if( maxcount == 0 ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + + if(tid != INVALID_TIMER) + { //Avoid double checks on instant cast skills. [Skotlex] + if (!status_check_skilluse(src, NULL, ud->skill_id, 1)) + break; + if(battle_config.skill_add_range && + !check_distance_blxy(src, ud->skillx, ud->skilly, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) { + if (sd && battle_config.skill_out_range_consume) //Consume items anyway. + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); + break; + } + } + + if( sd ) + { + if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) + break; + else + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); + } + + if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) + break; + + if(md) { + md->last_thinktime=tick +MIN_MOBTHINKTIME; + if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) + clif_emotion(src, md->db->skill[md->skill_idx].emotion); + } + + if(battle_config.skill_log && battle_config.skill_log&src->type) + ShowInfo("Type %d, ID %d skill castend pos [id =%d, lv=%d, (%d,%d)]\n", + src->type, src->id, ud->skill_id, ud->skill_lv, ud->skillx, ud->skilly); + + if (ud->walktimer != INVALID_TIMER) + unit_stop_walking(src,1); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); + if (sd) { //Cooldown application + int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); + for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. + if (sd->skillcooldown[i].id == ud->skill_id){ + cooldown += sd->skillcooldown[i].val; + break; + } + } + if(cooldown) + skill_blockpc_start(sd, ud->skill_id, cooldown); + } + if( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); +// if( sd ) +// { +// switch( ud->skill_id ) +// { +// case ????: +// sd->canequip_tick = tick + ????; +// break; +// } +// } + unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); + status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER);// only normal attack and auto cast skills benefit from its bonuses + map_freeblock_lock(); + skill_castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0); + + if( sd && sd->skillitem != AL_WARP ) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] + sd->skillitem = sd->skillitemlv = 0; + + if (ud->skilltimer == INVALID_TIMER) { + if (md) md->skill_idx = -1; + else ud->skill_id = 0; //Non mobs can't clear this one as it is used for skill condition 'afterskill' + ud->skill_lv = ud->skillx = ud->skilly = 0; + } + + map_freeblock_unlock(); + return 1; + } while(0); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick; + ud->skill_id = ud->skill_lv = 0; + if(sd) + sd->skillitem = sd->skillitemlv = 0; + else if(md) + md->skill_idx = -1; + return 0; + +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data* sd; + struct status_change* sc; + struct status_change_entry *sce; + struct skill_unit_group* sg; + enum sc_type type; + int i; + + //if(skill_lv <= 0) return 0; + if(skill_id > 0 && !skill_lv) return 0; // celest + + nullpo_ret(src); + + if(status_isdead(src)) + return 0; + + sd = BL_CAST(BL_PC, src); + + sc = status_get_sc(src); + type = status_skill2sc(skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + switch (skill_id) { //Skill effect. + case WZ_METEOR: + case MO_BODYRELOCATION: + case CR_CULTIVATION: + case HW_GANBANTEIN: + case LG_EARTHDRIVE: + break; //Effect is displayed on respective switch case. + default: + if(skill_get_inf(skill_id)&INF_SELF_SKILL) + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + else + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + } + + // SC_MAGICPOWER needs to switch states before any damage is actually dealt + skill_toggle_magicpower(src, skill_id); + + switch(skill_id) + { + case PR_BENEDICTIO: + skill_area_temp[1] = src->id; + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_PC, + src, skill_id, skill_lv, tick, flag|BCT_ALL|1, + skill_castend_nodamage_id); + map_foreachinarea(skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case BS_HAMMERFALL: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea (skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|2, + skill_castend_nodamage_id); + break; + + case HT_DETECTING: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea( status_change_timer_sub, + src->m, x-i, y-i, x+i,y+i,BL_CHAR, + src,NULL,SC_SIGHT,tick); + if(battle_config.traps_setting&1) + map_foreachinarea( skill_reveal_trap, + src->m, x-i, y-i, x+i,y+i,BL_SKILL); + break; + + case SR_RIDEINLIGHTNING: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + break; + + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + { //Does not consumes if the skill is already active. [Skotlex] + struct skill_unit_group *sg; + if ((sg= skill_locate_element_field(src)) != NULL && ( sg->skill_id == SA_VOLCANO || sg->skill_id == SA_DELUGE || sg->skill_id == SA_VIOLENTGALE )) + { + if (sg->limit - DIFF_TICK(gettick(), sg->tick) > 0) + { + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + return 0; // not to consume items + } + else + sg->limit = 0; //Disable it. + } + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + } + case MG_SAFETYWALL: + case MG_FIREWALL: + case MG_THUNDERSTORM: + + case AL_PNEUMA: + case WZ_ICEWALL: + case WZ_FIREPILLAR: + case WZ_QUAGMIRE: + case WZ_VERMILION: + case WZ_STORMGUST: + case WZ_HEAVENDRIVE: + case PR_SANCTUARY: + case PR_MAGNUS: + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case AS_VENOMDUST: + case AM_DEMONSTRATION: + case PF_FOGWALL: + case PF_SPIDERWEB: + case HT_TALKIEBOX: + case WE_CALLPARTNER: + case WE_CALLPARENT: + case WE_CALLBABY: + case AC_SHOWER: //Ground-placed skill implementation. + case MA_SHOWER: + case SA_LANDPROTECTOR: + case BD_LULLABY: + case BD_RICHMANKIM: + case BD_ETERNALCHAOS: + case BD_DRUMBATTLEFIELD: + case BD_RINGNIBELUNGEN: + case BD_ROKISWEIL: + case BD_INTOABYSS: + case BD_SIEGFRIED: + case BA_DISSONANCE: + case BA_POEMBRAGI: + case BA_WHISTLE: + case BA_ASSASSINCROSS: + case BA_APPLEIDUN: + case DC_UGLYDANCE: + case DC_HUMMING: + case DC_DONTFORGETME: + case DC_FORTUNEKISS: + case DC_SERVICEFORYOU: + case CG_MOONLIT: + case GS_DESPERADO: + case NJ_KAENSIN: + case NJ_BAKUENRYU: + case NJ_SUITON: + case NJ_HYOUSYOURAKU: + case NJ_RAIGEKISAI: + case NJ_KAMAITACHI: +#ifdef RENEWAL + case NJ_HUUMA: +#endif + case NPC_EVILLAND: + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + case SC_MANHOLE: + case SC_DIMENSIONDOOR: + case SC_CHAOSPANIC: + case SC_MAELSTROM: + case WM_REVERBERATION: + case WM_SEVERE_RAINSTORM: + case WM_POEMOFNETHERWORLD: + case SO_PSYCHIC_WAVE: + case SO_VACUUM_EXTREME: + case GN_WALLOFTHORN: + case GN_THORNS_TRAP: + case GN_DEMONIC_FIRE: + case GN_HELLS_PLANT: + case SO_EARTHGRAVE: + case SO_DIAMONDDUST: + case SO_FIRE_INSIGNIA: + case SO_WATER_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + case KO_HUUMARANKA: + case KO_MUCHANAGE: + case KO_BAKURETSU: + case KO_ZENKAI: + case MH_LAVA_SLIDE: + case MH_VOLCANIC_ASH: + case MH_POISON_MIST: + case MH_STEINWAND: + case MH_XENO_SLASHER: + flag|=1;//Set flag to 1 to prevent deleting ammo (it will be deleted on group-delete). + case GS_GROUNDDRIFT: //Ammo should be deleted right away. + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + case RG_GRAFFITI: /* Graffiti [Valaris] */ + skill_clear_unitgroup(src); + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + flag|=1; + break; + case HP_BASILICA: + if( sc->data[SC_BASILICA] ) + status_change_end(src, SC_BASILICA, INVALID_TIMER); // Cancel Basilica + else + { // Create Basilica. Start SC on caster. Unit timer start SC on others. + skill_clear_unitgroup(src); + if( skill_unitsetting(src,skill_id,skill_lv,x,y,0) ) + sc_start4(src,type,100,skill_lv,0,0,src->id,skill_get_time(skill_id,skill_lv)); + flag|=1; + } + break; + case CG_HERMODE: + skill_clear_unitgroup(src); + if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) + sc_start4(src,SC_DANCING,100, + skill_id,0,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + case RG_CLEANER: // [Valaris] + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_graffitiremover,src->m,x-i,y-i,x+i,y+i,BL_SKILL); + break; + + case SO_WARMER: + flag|= 8; + case SO_CLOUD_KILL: + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + + case WZ_METEOR: { + int area = skill_get_splash(skill_id, skill_lv); + short tmpx = 0, tmpy = 0, x1 = 0, y1 = 0; + + for( i = 0; i < 2 + (skill_lv>>1); i++ ) { + // Creates a random Cell in the Splash Area + tmpx = x - area + rnd()%(area * 2 + 1); + tmpy = y - area + rnd()%(area * 2 + 1); + + if( i == 0 && path_search_long(NULL, src->m, src->x, src->y, tmpx, tmpy, CELL_CHKWALL) ) + clif_skill_poseffect(src,skill_id,skill_lv,tmpx,tmpy,tick); + + if( i > 0 ) + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,(x1<<16)|y1,0); + + x1 = tmpx; + y1 = tmpy; + } + + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,-1,0); + } + break; + + case AL_WARP: + if(sd) + { + clif_skill_warppoint(sd, skill_id, skill_lv, sd->status.save_point.map, + (skill_lv >= 2) ? sd->status.memo_point[0].map : 0, + (skill_lv >= 3) ? sd->status.memo_point[1].map : 0, + (skill_lv >= 4) ? sd->status.memo_point[2].map : 0 + ); + } + return 0; // not to consume item. + + case MO_BODYRELOCATION: + if (unit_movepos(src, x, y, 1, 1)) { +#if PACKETVER >= 20111005 + clif_snap(src, src->x, src->y); +#else + clif_skill_poseffect(src,skill_id,skill_lv,src->x,src->y,tick); +#endif + if (sd) + skill_blockpc_start (sd, MO_EXTREMITYFIST, 2000); + } + break; + case NJ_SHADOWJUMP: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) { //You don't move on GVG grounds. + unit_movepos(src, x, y, 1, 0); + clif_slide(src,x,y); + } + status_change_end(src, SC_HIDING, INVALID_TIMER); + break; + case AM_SPHEREMINE: + case AM_CANNIBALIZE: + { + int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; + //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; + int class_ = skill_id==AM_SPHEREMINE?1142:summons[skill_lv-1]; + struct mob_data *md; + + // Correct info, don't change any of this! [celest] + md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); + if (md) { + md->master_id = src->id; + md->special_state.ai = (skill_id == AM_SPHEREMINE) ? AI_SPHERE : AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id,skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn (md); //Now it is ready for spawning. + } + } + break; + + // Slim Pitcher [Celest] + case CR_SLIMPITCHER: + if (sd) { + int i = skill_lv%11 - 1; + int j = pc_search_inventory(sd,skill_db[skill_id].itemid[i]); + if( j < 0 || skill_db[skill_id].itemid[i] <= 0 || sd->inventory_data[j] == NULL || sd->status.inventory[j].amount < skill_db[skill_id].amount[i] ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + potion_flag = 1; + potion_hp = 0; + potion_sp = 0; + run_script(sd->inventory_data[j]->script,0,sd->bl.id,0); + potion_flag = 0; + //Apply skill bonuses + i = pc_checkskill(sd,CR_SLIMPITCHER)*10 + + pc_checkskill(sd,AM_POTIONPITCHER)*10 + + pc_checkskill(sd,AM_LEARNINGPOTION)*5 + + pc_skillheal_bonus(sd, skill_id); + + potion_hp = potion_hp * (100+i)/100; + potion_sp = potion_sp * (100+i)/100; + + if(potion_hp > 0 || potion_sp > 0) { + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m,x-i,y-i,x+i,y+i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, + skill_castend_nodamage_id); + } + } else { + int i = skill_lv%11 - 1; + struct item_data *item; + i = skill_db[skill_id].itemid[i]; + item = itemdb_search(i); + potion_flag = 1; + potion_hp = 0; + potion_sp = 0; + run_script(item->script,0,src->id,0); + potion_flag = 0; + i = skill_get_max(CR_SLIMPITCHER)*10; + + potion_hp = potion_hp * (100+i)/100; + potion_sp = potion_sp * (100+i)/100; + + if(potion_hp > 0 || potion_sp > 0) { + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m,x-i,y-i,x+i,y+i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, + skill_castend_nodamage_id); + } + } + break; + + case HW_GANBANTEIN: + if (rnd()%100 < 80) { + int dummy = 1; + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_cell_overlap, src->m, x-i, y-i, x+i, y+i, BL_SKILL, HW_GANBANTEIN, &dummy, src); + } else { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + + case HW_GRAVITATION: + if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) + sc_start4(src,type,100,skill_lv,0,BCT_SELF,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + + // Plant Cultivation [Celest] + case CR_CULTIVATION: + if (sd) { + if( map_count_oncell(src->m,x,y,BL_CHAR) > 0 ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + if (rnd()%100 < 50) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } else { + TBL_MOB* md = mob_once_spawn_sub(src, src->m, x, y, "--ja--",(skill_lv < 2 ? 1084+rnd()%2 : 1078+rnd()%6),"", SZ_SMALL, AI_NONE); + int i; + if (!md) break; + if ((i = skill_get_time(skill_id, skill_lv)) > 0) + { + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (tick + i, mob_timer_delete, md->bl.id, 0); + } + mob_spawn (md); + } + } + break; + + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + skill_clear_unitgroup(src); + if ((sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0))) + sc_start4(src,type,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + + case PA_GOSPEL: + if (sce && sce->val4 == BCT_SELF) + { + status_change_end(src, SC_GOSPEL, INVALID_TIMER); + return 0; + } + else + { + sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0); + if (!sg) break; + if (sce) + status_change_end(src, type, INVALID_TIMER); //Was under someone else's Gospel. [Skotlex] + sc_start4(src,type,100,skill_lv,0,sg->group_id,BCT_SELF,skill_get_time(skill_id,skill_lv)); + clif_skill_poseffect(src, skill_id, skill_lv, 0, 0, tick); // PA_GOSPEL music packet + } + break; + case NJ_TATAMIGAESHI: + if (skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) + sc_start(src,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case AM_RESURRECTHOMUN: //[orn] + if (sd) + { + if (!merc_resurrect_homunculus(sd, 20*skill_lv, x, y)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + break; + + case RK_WINDCUTTER: + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + case NC_COLDSLOWER: + case NC_ARMSCANNON: + case RK_DRAGONBREATH: + case WM_LULLABY_DEEPSLEEP: + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), + src,skill_id,skill_lv,tick,flag|(skill_id==WM_LULLABY_DEEPSLEEP?BCT_ALL:BCT_ENEMY)|1,skill_castend_damage_id); + break; + /** + * Guilotine Cross + **/ + case GC_POISONSMOKE: + if( !(sc && sc->data[SC_POISONINGWEAPON]) ) { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_POISONINGWEAPON,0); + return 0; + } + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + skill_unitsetting(src, skill_id, skill_lv, x, y, flag); + //status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); // 08/31/2011 - When using poison smoke, you no longer lose the poisoning weapon effect. + break; + /** + * Arch Bishop + **/ + case AB_EPICLESIS: + if( (sg = skill_unitsetting(src, skill_id, skill_lv, x, y, 0)) ) { + i = sg->unit->range; + map_foreachinarea(skill_area_sub, src->m, x - i, y - i, x + i, y + i, BL_CHAR, src, ALL_RESURRECTION, 1, tick, flag|BCT_NOENEMY|1,skill_castend_nodamage_id); + } + break; + /** + * Warlock + **/ + case WL_COMET: + if( sc ) { + sc->comet_x = x; + sc->comet_y = y; + } + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case WL_EARTHSTRAIN: + { + int i, wave = skill_lv + 4, dir = map_calc_dir(src,x,y); + int sx = x = src->x, sy = y = src->y; // Store first caster's location to avoid glitch on unit setting + + for( i = 1; i <= wave; i++ ) + { + switch( dir ){ + case 0: case 1: case 7: sy = y + i; break; + case 3: case 4: case 5: sy = y - i; break; + case 2: sx = x - i; break; + case 6: sx = x + i; break; + } + skill_addtimerskill(src,gettick() + (150 * i),0,sx,sy,skill_id,skill_lv,dir,flag&2); + } + } + break; + /** + * Ranger + **/ + case RA_DETONATOR: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_detonator, src->m, x-i, y-i, x+i, y+i, BL_SKILL, src); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + /** + * Mechanic + **/ + case NC_NEUTRALBARRIER: + case NC_STEALTHFIELD: + skill_clear_unitgroup(src); // To remove previous skills - cannot used combined + if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { + sc_start2(src,skill_id == NC_NEUTRALBARRIER ? SC_NEUTRALBARRIER_MASTER : SC_STEALTHFIELD_MASTER,100,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); + if( sd ) pc_overheat(sd,1); + } + break; + + case NC_SILVERSNIPER: + { + int class_ = 2042; + struct mob_data *md; + + md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); + if( md ) + { + md->master_id = src->id; + md->special_state.ai = AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn( md ); + } + } + break; + + case NC_MAGICDECOY: + if( sd ) clif_magicdecoy_list(sd,skill_lv,x,y); + break; + + case SC_FEINTBOMB: + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position + if( skill_blown(src,src,6,unit_getdir(src),0) ) + skill_castend_nodamage_id(src,src,TF_HIDING,1,tick,0); + break; + + case LG_OVERBRAND: + { + int width;//according to data from irowiki it actually is a square + for( width = 0; width < 7; width++ ) + for( i = 0; i < 7; i++ ) + map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, LG_OVERBRAND_BRANDISH, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); + for( width = 0; width < 7; width++ ) + for( i = 0; i < 7; i++ ) + map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); + } + break; + + case LG_BANDING: + if( sc && sc->data[SC_BANDING] ) + status_change_end(src,SC_BANDING,INVALID_TIMER); + else if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { + sc_start4(src,SC_BANDING,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); + if( sd ) pc_banding(sd,skill_lv); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + break; + + case LG_RAYOFGENESIS: + if( status_charge(src,status_get_max_hp(src)*3*skill_lv / 100,0) ) { + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + } else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + break; + + case WM_DOMINION_IMPULSE: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea( skill_ative_reverberation, + src->m, x-i, y-i, x+i,y+i,BL_SKILL); + break; + + case WM_GREAT_ECHO: + flag|=1; // Should counsume 1 item per skill usage. + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_castend_damage_id); + break; + case GN_CRAZYWEED: { + int area = skill_get_splash(GN_CRAZYWEED_ATK, skill_lv); + short x1 = 0, y1 = 0; + + for( i = 0; i < 3 + (skill_lv/2); i++ ) { + x1 = x - area + rnd()%(area * 2 + 1); + y1 = y - area + rnd()%(area * 2 + 1); + skill_addtimerskill(src,tick+i*150,0,x1,y1,GN_CRAZYWEED_ATK,skill_lv,-1,0); + } + } + break; + case GN_FIRE_EXPANSION: { + int i; + struct unit_data *ud = unit_bl2ud(src); + + if( !ud ) break; + + for( i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i ++ ) { + if( ud->skillunit[i]->skill_id == GN_DEMONIC_FIRE && + distance_xy(x, y, ud->skillunit[i]->unit->bl.x, ud->skillunit[i]->unit->bl.y) < 4 ) { + switch( skill_lv ) { + case 3: + ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_SMOKE_POWDER; + clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_SMOKE_POWDER); + break; + case 4: + ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_TEAR_GAS; + clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_TEAR_GAS); + break; + case 5: + map_foreachinarea(skill_area_sub, src->m, + ud->skillunit[i]->unit->bl.x - 3, ud->skillunit[i]->unit->bl.y - 3, + ud->skillunit[i]->unit->bl.x + 3, ud->skillunit[i]->unit->bl.y + 3, BL_CHAR, + src, CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : skill_lv, tick, flag|BCT_ENEMY|1|SD_LEVEL, skill_castend_damage_id); + skill_delunit(ud->skillunit[i]->unit); + break; + default: + ud->skillunit[i]->unit->val2 = skill_lv; + ud->skillunit[i]->unit->group->val2 = skill_lv; + break; + } + } + } + } + break; + + case SO_FIREWALK: + case SO_ELECTRICWALK: + if( sc && sc->data[type] ) + status_change_end(src,type,INVALID_TIMER); + clif_skill_nodamage(src, src ,skill_id, skill_lv, + sc_start2(src, type, 100, skill_id, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + + case SC_BLOODYLUST: //set in another group so instance will move if recasted + flag |= 33; + skill_unitsetting(src, skill_id, skill_lv, x, y, 0); + break; + + case KO_MAKIBISHI: + for( i = 0; i < (skill_lv+2); i++ ) { + x = src->x - 1 + rnd()%3; + y = src->y - 1 + rnd()%3; + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + } + break; + + default: + ShowWarning("skill_castend_pos2: Unknown skill used:%d\n",skill_id); + return 1; + } + + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + + if( sd ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk && !(flag&1) ) + {// consume arrow if this is a ground skill + battle_consume_ammo(sd, skill_id, skill_lv); + } + + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char *map) +{ + nullpo_ret(sd); + +//Simplify skill_failed code. +#define skill_failed(sd) { sd->menuskill_id = sd->menuskill_val = 0; } + if(skill_id != sd->menuskill_id) + return 0; + + if( sd->bl.prev == NULL || pc_isdead(sd) ) { + skill_failed(sd); + return 0; + } + + if( ( sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING ) || sd->sc.option&OPTION_HIDE ) { + skill_failed(sd); + return 0; + } + if(sd->sc.count && ( + sd->sc.data[SC_SILENCE] || + sd->sc.data[SC_ROKISWEIL] || + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_STEELBODY] || + (sd->sc.data[SC_DANCING] && skill_id < RK_ENCHANTBLADE && !pc_checkskill(sd, WM_LESSON)) || + sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + sd->sc.data[SC_BASILICA] || + sd->sc.data[SC_MARIONETTE] || + sd->sc.data[SC_WHITEIMPRISON] || + (sd->sc.data[SC_STASIS] && skill_block_check(&sd->bl, SC_STASIS, skill_id)) || + (sd->sc.data[SC_KAGEHUMI] && skill_block_check(&sd->bl, SC_KAGEHUMI, skill_id)) || + sd->sc.data[SC_OBLIVIONCURSE] || + sd->sc.data[SC__MANHOLE] || + (sd->sc.data[SC_ASH] && rnd()%2) //50% fail chance under ASH + )) { + skill_failed(sd); + return 0; + } + + pc_stop_attack(sd); + pc_stop_walking(sd,0); + + if(battle_config.skill_log && battle_config.skill_log&BL_PC) + ShowInfo("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_id,map); + + if(strcmp(map,"cancel")==0) { + skill_failed(sd); + return 0; + } + + switch(skill_id) + { + case AL_TELEPORT: + if(strcmp(map,"Random")==0) + pc_randomwarp(sd,CLR_TELEPORT); + else if (sd->menuskill_val > 1) //Need lv2 to be able to warp here. + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + + case AL_WARP: + { + const struct point *p[4]; + struct skill_unit_group *group; + int i, lv, wx, wy; + int maxcount=0; + int x,y; + unsigned short mapindex; + + mapindex = mapindex_name2id((char*)map); + if(!mapindex) { //Given map not found? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + skill_failed(sd); + return 0; + } + p[0] = &sd->status.save_point; + p[1] = &sd->status.memo_point[0]; + p[2] = &sd->status.memo_point[1]; + p[3] = &sd->status.memo_point[2]; + + if((maxcount = skill_get_maxcount(skill_id, sd->menuskill_val)) > 0) { + for(i=0;i<MAX_SKILLUNITGROUP && sd->ud.skillunit[i] && maxcount;i++) { + if(sd->ud.skillunit[i]->skill_id == skill_id) + maxcount--; + } + if(!maxcount) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + skill_failed(sd); + return 0; + } + } + + lv = sd->skillitem==skill_id?sd->skillitemlv:pc_checkskill(sd,skill_id); + wx = sd->menuskill_val>>16; + wy = sd->menuskill_val&0xffff; + + if( lv <= 0 ) return 0; + if( lv > 4 ) lv = 4; // crash prevention + + // check if the chosen map exists in the memo list + ARR_FIND( 0, lv, i, mapindex == p[i]->map ); + if( i < lv ) { + x=p[i]->x; + y=p[i]->y; + } else { + skill_failed(sd); + return 0; + } + + if(!skill_check_condition_castend(sd, sd->menuskill_id, lv)) + { // This checks versus skill_id/skill_lv... + skill_failed(sd); + return 0; + } + + skill_consume_requirement(sd,sd->menuskill_id,lv,2); + sd->skillitem = sd->skillitemlv = 0; // Clear data that's skipped in 'skill_castend_pos' [Inkfish] + + if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==NULL) { + skill_failed(sd); + return 0; + } + + group->val1 = (group->val1<<16)|(short)0; + // record the destination coordinates + group->val2 = (x<<16)|y; + group->val3 = mapindex; + } + break; + } + + sd->menuskill_id = sd->menuskill_val = 0; + return 0; +#undef skill_failed +} + +/// transforms 'target' skill unit into dissonance (if conditions are met) +static int skill_dance_overlap_sub(struct block_list* bl, va_list ap) +{ + struct skill_unit* target = (struct skill_unit*)bl; + struct skill_unit* src = va_arg(ap, struct skill_unit*); + int flag = va_arg(ap, int); + + if (src == target) + return 0; + if (!target->group || !(target->group->state.song_dance&0x1)) + return 0; + if (!(target->val2 & src->val2 & ~UF_ENSEMBLE)) //They don't match (song + dance) is valid. + return 0; + + if (flag) //Set dissonance + target->val2 |= UF_ENSEMBLE; //Add ensemble to signal this unit is overlapping. + else //Remove dissonance + target->val2 &= ~UF_ENSEMBLE; + + clif_skill_setunit(target); //Update look of affected cell. + + return 1; +} + +//Does the song/dance overlapping -> dissonance check. [Skotlex] +//When flag is 0, this unit is about to be removed, cancel the dissonance effect +//When 1, this unit has been positioned, so start the cancel effect. +int skill_dance_overlap(struct skill_unit* unit, int flag) +{ + if (!unit || !unit->group || !(unit->group->state.song_dance&0x1)) + return 0; + if (!flag && !(unit->val2&UF_ENSEMBLE)) + return 0; //Nothing to remove, this unit is not overlapped. + + if (unit->val1 != unit->group->skill_id) + { //Reset state + unit->val1 = unit->group->skill_id; + unit->val2 &= ~UF_ENSEMBLE; + } + + return map_foreachincell(skill_dance_overlap_sub, unit->bl.m,unit->bl.x,unit->bl.y,BL_SKILL, unit,flag); +} + +/*========================================== + * Converts this group information so that it is handled as a Dissonance or Ugly Dance cell. + * Flag: 0 - Convert, 1 - Revert. + *------------------------------------------*/ +static bool skill_dance_switch(struct skill_unit* unit, int flag) +{ + static int prevflag = 1; // by default the backup is empty + static struct skill_unit_group backup; + struct skill_unit_group* group = unit->group; + + // val2&UF_ENSEMBLE is a hack to indicate dissonance + if ( !(group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) ) + return false; + + if( flag == prevflag ) + {// protection against attempts to read an empty backup / write to a full backup + ShowError("skill_dance_switch: Attempted to %s (skill_id=%d, skill_lv=%d, src_id=%d).\n", + flag ? "read an empty backup" : "write to a full backup", + group->skill_id, group->skill_lv, group->src_id); + return false; + } + prevflag = flag; + + if( !flag ) + { //Transform + uint16 skill_id = unit->val2&UF_SONG ? BA_DISSONANCE : DC_UGLYDANCE; + + // backup + backup.skill_id = group->skill_id; + backup.skill_lv = group->skill_lv; + backup.unit_id = group->unit_id; + backup.target_flag = group->target_flag; + backup.bl_flag = group->bl_flag; + backup.interval = group->interval; + + // replace + group->skill_id = skill_id; + group->skill_lv = 1; + group->unit_id = skill_get_unit_id(skill_id,0); + group->target_flag = skill_get_unit_target(skill_id); + group->bl_flag = skill_get_unit_bl_target(skill_id); + group->interval = skill_get_unit_interval(skill_id); + } + else + { //Restore + group->skill_id = backup.skill_id; + group->skill_lv = backup.skill_lv; + group->unit_id = backup.unit_id; + group->target_flag = backup.target_flag; + group->bl_flag = backup.bl_flag; + group->interval = backup.interval; + } + + return true; +} +/** + * Upon Ice Wall cast it checks all nearby mobs to find any who may be blocked by the IW + **/ +static int skill_icewall_block(struct block_list *bl,va_list ap) { + struct block_list *target = NULL; + struct mob_data *md = ((TBL_MOB*)bl); + + nullpo_ret(bl); + nullpo_ret(md); + if( !md->target_id || ( target = map_id2bl(md->target_id) ) == NULL ) + return 0; + + if( path_search_long(NULL,bl->m,bl->x,bl->y,target->x,target->y,CELL_CHKICEWALL) ) + return 0; + + if( !check_distance_bl(bl, target, status_get_range(bl) ) ) { + mob_unlocktarget(md,gettick()); + mob_stop_walking(md,1); + } + + return 0; +} +/*========================================== + * Initializes and sets a ground skill. + * flag&1 is used to determine when the skill 'morphs' (Warp portal becomes active, or Fire Pillar becomes active) + *------------------------------------------*/ +struct skill_unit_group* skill_unitsetting (struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag) +{ + struct skill_unit_group *group; + int i,limit,val1=0,val2=0,val3=0; + int target,interval,range,unit_flag,req_item=0; + struct s_skill_unit_layout *layout; + struct map_session_data *sd; + struct status_data *status; + struct status_change *sc; + int active_flag=1; + int subunt=0; + + nullpo_retr(NULL, src); + + limit = skill_get_time(skill_id,skill_lv); + range = skill_get_unit_range(skill_id,skill_lv); + interval = skill_get_unit_interval(skill_id); + target = skill_get_unit_target(skill_id); + unit_flag = skill_get_unit_flag(skill_id); + layout = skill_get_unit_layout(skill_id,skill_lv,src,x,y); + + sd = BL_CAST(BL_PC, src); + status = status_get_status_data(src); + sc = status_get_sc(src); // for traps, firewall and fogwall - celest + + switch( skill_id ) { + case MH_STEINWAND: + val2 = 4 + skill_lv; //nb of attack blocked + break; + case MG_SAFETYWALL: + #ifdef RENEWAL + /** + * According to data provided in RE, SW life is equal to 3 times caster's health + **/ + val2 = status_get_max_hp(src) * 3; + #else + val2 = skill_lv+1; + #endif + break; + case MG_FIREWALL: + if(sc && sc->data[SC_VIOLENTGALE]) + limit = limit*3/2; + val2=4+skill_lv; + break; + + case AL_WARP: + val1=skill_lv+6; + if(!(flag&1)) + limit=2000; + else // previous implementation (not used anymore) + { //Warp Portal morphing to active mode, extract relevant data from src. [Skotlex] + if( src->type != BL_SKILL ) return NULL; + group = ((TBL_SKILL*)src)->group; + src = map_id2bl(group->src_id); + if( !src ) return NULL; + val2 = group->val2; //Copy the (x,y) position you warp to + val3 = group->val3; //as well as the mapindex to warp to. + } + break; + case HP_BASILICA: + val1 = src->id; // Store caster id. + break; + + case PR_SANCTUARY: + case NPC_EVILLAND: + val1=(skill_lv+3)*2; + break; + + case WZ_FIREPILLAR: + if((flag&1)!=0) + limit=1000; + val1=skill_lv+2; + break; + case WZ_QUAGMIRE: //The target changes to "all" if used in a gvg map. [Skotlex] + case AM_DEMONSTRATION: + case GN_HELLS_PLANT: + if (map_flag_vs(src->m) && battle_config.vs_traps_bctall + && (src->type&battle_config.vs_traps_bctall)) + target = BCT_ALL; + break; + case HT_SHOCKWAVE: + val1=skill_lv*15+10; + case HT_SANDMAN: + case MA_SANDMAN: + case HT_CLAYMORETRAP: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + /** + * Ranger + **/ + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + { + struct skill_condition req = skill_get_requirement(sd,skill_id,skill_lv); + ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, i, req.itemid[i] && (req.itemid[i] == ITEMID_TRAP || req.itemid[i] == ITEMID_TRAP_ALLOY)); + if( req.itemid[i] ) + req_item = req.itemid[i]; + if( map_flag_gvg(src->m) || map[src->m].flag.battleground ) + limit *= 4; // longer trap times in WOE [celest] + if( battle_config.vs_traps_bctall && map_flag_vs(src->m) && (src->type&battle_config.vs_traps_bctall) ) + target = BCT_ALL; + } + break; + + case SA_LANDPROTECTOR: + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + { + struct skill_unit_group *old_sg; + if ((old_sg = skill_locate_element_field(src)) != NULL) + { //HelloKitty confirmed that these are interchangeable, + //so you can change element and not consume gemstones. + if (( + old_sg->skill_id == SA_VOLCANO || + old_sg->skill_id == SA_DELUGE || + old_sg->skill_id == SA_VIOLENTGALE + ) && old_sg->limit > 0) + { //Use the previous limit (minus the elapsed time) [Skotlex] + limit = old_sg->limit - DIFF_TICK(gettick(), old_sg->tick); + if (limit < 0) //This can happen... + limit = skill_get_time(skill_id,skill_lv); + } + skill_clear_group(src,1); + } + break; + } + + case BA_DISSONANCE: + case DC_UGLYDANCE: + val1 = 10; //FIXME: This value is not used anywhere, what is it for? [Skotlex] + break; + case BA_WHISTLE: + val1 = skill_lv +status->agi/10; // Flee increase + val2 = ((skill_lv+1)/2)+status->luk/10; // Perfect dodge increase + if(sd){ + val1 += pc_checkskill(sd,BA_MUSICALLESSON); + val2 += pc_checkskill(sd,BA_MUSICALLESSON); + } + break; + case DC_HUMMING: + val1 = 2*skill_lv+status->dex/10; // Hit increase + #ifdef RENEWAL + val1 *= 2; + #endif + if(sd) + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + break; + case BA_POEMBRAGI: + val1 = 3*skill_lv+status->dex/10; // Casting time reduction + //For some reason at level 10 the base delay reduction is 50%. + val2 = (skill_lv<10?3*skill_lv:50)+status->int_/5; // After-cast delay reduction + if(sd){ + val1 += 2*pc_checkskill(sd,BA_MUSICALLESSON); + val2 += 2*pc_checkskill(sd,BA_MUSICALLESSON); + } + break; + case DC_DONTFORGETME: + val1 = status->dex/10 + 3*skill_lv + 5; // ASPD decrease + val2 = status->agi/10 + 3*skill_lv + 5; // Movement speed adjustment. + if(sd){ + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + val2 += pc_checkskill(sd,DC_DANCINGLESSON); + } + break; + case BA_APPLEIDUN: + val1 = 5+2*skill_lv+status->vit/10; // MaxHP percent increase + if(sd) + val1 += pc_checkskill(sd,BA_MUSICALLESSON); + break; + case DC_SERVICEFORYOU: + val1 = 15+skill_lv+(status->int_/10); // MaxSP percent increase TO-DO: this INT bonus value is guessed + val2 = 20+3*skill_lv+(status->int_/10); // SP cost reduction + if(sd){ + val1 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO This bonus value is guessed + val2 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO Should be half this value + } + break; + case BA_ASSASSINCROSS: + val1 = 100+(10*skill_lv)+(status->agi/10); // ASPD increase + if(sd) + val1 += 5*pc_checkskill(sd,BA_MUSICALLESSON); + break; + case DC_FORTUNEKISS: + val1 = 10+skill_lv+(status->luk/10); // Critical increase + if(sd) + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + val1*=10; //Because every 10 crit is an actual cri point. + break; + case BD_DRUMBATTLEFIELD: + #ifdef RENEWAL + val1 = (skill_lv+5)*25; //Watk increase + val2 = skill_lv*10; //Def increase + #else + val1 = (skill_lv+1)*25; //Watk increase + val2 = (skill_lv+1)*2; //Def increase + #endif + break; + case BD_RINGNIBELUNGEN: + val1 = (skill_lv+2)*25; //Watk increase + break; + case BD_RICHMANKIM: + val1 = 25 + 11*skill_lv; //Exp increase bonus. + break; + case BD_SIEGFRIED: + val1 = 55 + skill_lv*5; //Elemental Resistance + val2 = skill_lv*10; //Status ailment resistance + break; + case WE_CALLPARTNER: + if (sd) val1 = sd->status.partner_id; + break; + case WE_CALLPARENT: + if (sd) { + val1 = sd->status.father; + val2 = sd->status.mother; + } + break; + case WE_CALLBABY: + if (sd) val1 = sd->status.child; + break; + case NJ_KAENSIN: + skill_clear_group(src, 1); //Delete previous Kaensins/Suitons + val2 = (skill_lv+1)/2 + 4; + break; + case NJ_SUITON: + skill_clear_group(src, 1); + break; + + case GS_GROUNDDRIFT: + { + int element[5]={ELE_WIND,ELE_DARK,ELE_POISON,ELE_WATER,ELE_FIRE}; + + val1 = status->rhw.ele; + if (!val1) + val1=element[rnd()%5]; + + switch (val1) + { + case ELE_FIRE: + subunt++; + case ELE_WATER: + subunt++; + case ELE_POISON: + subunt++; + case ELE_DARK: + subunt++; + case ELE_WIND: + break; + default: + subunt=rnd()%5; + break; + } + + break; + } + case GC_POISONSMOKE: + if( !(sc && sc->data[SC_POISONINGWEAPON]) ) + return NULL; + val2 = sc->data[SC_POISONINGWEAPON]->val2; // Type of Poison + val3 = sc->data[SC_POISONINGWEAPON]->val1; + limit = 4000 + 2000 * skill_lv; + break; + case GD_LEADERSHIP: + case GD_GLORYWOUNDS: + case GD_SOULCOLD: + case GD_HAWKEYES: + limit = 1000000;//it doesn't matter + break; + case LG_BANDING: + limit = -1; + break; + case WM_REVERBERATION: + interval = limit; + val2 = 1; + case WM_POEMOFNETHERWORLD: // Can't be placed on top of Land Protector. + if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) + return NULL; + break; + case SO_CLOUD_KILL: + skill_clear_group(src, 4); + break; + case SO_WARMER: + skill_clear_group(src, 8); + break; + case SO_VACUUM_EXTREME: + range++; + + break; + case SC_BLOODYLUST: + skill_clear_group(src, 32); + break; + case GN_WALLOFTHORN: + if( flag&1 ) + limit = 3000; + val3 = (x<<16)|y; + break; + case KO_ZENKAI: + if( sd ){ + ARR_FIND(1, 6, i, sd->talisman[i] > 0); + if( i < 5 ){ + val1 = sd->talisman[i]; // no. of aura + val2 = i; // aura type + limit += val1 * 1000; + subunt = i - 1; + pc_del_talisman(sd, sd->talisman[i], i); + } + } + break; + } + + nullpo_retr(NULL, group=skill_initunitgroup(src,layout->count,skill_id,skill_lv,skill_get_unit_id(skill_id,flag&1)+subunt, limit, interval)); + group->val1=val1; + group->val2=val2; + group->val3=val3; + group->target_flag=target; + group->bl_flag= skill_get_unit_bl_target(skill_id); + group->state.ammo_consume = (sd && sd->state.arrow_atk && skill_id != GS_GROUNDDRIFT); //Store if this skill needs to consume ammo. + group->state.song_dance = (unit_flag&(UF_DANCE|UF_SONG)?1:0)|(unit_flag&UF_ENSEMBLE?2:0); //Signals if this is a song/dance/duet + group->state.guildaura = ( skill_id >= GD_LEADERSHIP && skill_id <= GD_HAWKEYES )?1:0; + group->item_id = req_item; + //if tick is greater than current, do not invoke onplace function just yet. [Skotlex] + if (DIFF_TICK(group->tick, gettick()) > SKILLUNITTIMER_INTERVAL) + active_flag = 0; + + if(skill_id==HT_TALKIEBOX || skill_id==RG_GRAFFITI){ + group->valstr=(char *) aMalloc(MESSAGE_SIZE*sizeof(char)); + if (sd) + safestrncpy(group->valstr, sd->message, MESSAGE_SIZE); + else //Eh... we have to write something here... even though mobs shouldn't use this. [Skotlex] + safestrncpy(group->valstr, "Boo!", MESSAGE_SIZE); + } + + if (group->state.song_dance) { + if(sd){ + sd->skill_id_dance = skill_id; + sd->skill_lv_dance = skill_lv; + } + if ( + sc_start4(src, SC_DANCING, 100, skill_id, group->group_id, skill_lv, + (group->state.song_dance&2?BCT_SELF:0), limit+1000) && + sd && group->state.song_dance&2 && skill_id != CG_HERMODE //Hermod is a encore with a warp! + ) + skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); + } + + limit = group->limit; + for( i = 0; i < layout->count; i++ ) + { + struct skill_unit *unit; + int ux = x + layout->dx[i]; + int uy = y + layout->dy[i]; + int val1 = skill_lv; + int val2 = 0; + int alive = 1; + + if( !group->state.song_dance && !map_getcell(src->m,ux,uy,CELL_CHKREACH) ) + continue; // don't place skill units on walls (except for songs/dances/encores) + if( battle_config.skill_wall_check && skill_get_unit_flag(skill_id)&UF_PATHCHECK && !path_search_long(NULL,src->m,ux,uy,x,y,CELL_CHKWALL) ) + continue; // no path between cell and center of casting. + + switch( skill_id ) + { + case MG_FIREWALL: + case NJ_KAENSIN: + val2=group->val2; + break; + case WZ_ICEWALL: + val1 = (skill_lv <= 1) ? 500 : 200 + 200*skill_lv; + val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE); + break; + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_TALKIEBOX: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_CLAYMORETRAP: + case HT_BLASTMINE: + /** + * Ranger + **/ + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + val1 = 3500; + break; + case GS_DESPERADO: + val1 = abs(layout->dx[i]); + val2 = abs(layout->dy[i]); + if (val1 < 2 || val2 < 2) { //Nearby cross, linear decrease with no diagonals + if (val2 > val1) val1 = val2; + if (val1) val1--; + val1 = 36 -12*val1; + } else //Diagonal edges + val1 = 28 -4*val1 -4*val2; + if (val1 < 1) val1 = 1; + val2 = 0; + break; + case WM_REVERBERATION: + val1 = 1 + skill_lv; + break; + case GN_WALLOFTHORN: + val1 = 1000 * skill_lv; // Need official value. [LimitLine] + break; + default: + if (group->state.song_dance&0x1) + val2 = unit_flag&(UF_DANCE|UF_SONG); //Store whether this is a song/dance + break; + } + if (skill_get_unit_flag(skill_id) & UF_RANGEDSINGLEUNIT && i == (layout->count / 2)) + val2 |= UF_RANGEDSINGLEUNIT; // center. + + if( range <= 0 ) + map_foreachincell(skill_cell_overlap,src->m,ux,uy,BL_SKILL,skill_id, &alive, src); + if( !alive ) + continue; + + nullpo_retr(NULL, unit=skill_initunit(group,i,ux,uy,val1,val2)); + unit->limit=limit; + unit->range=range; + + if (skill_id == PF_FOGWALL && alive == 2) + { //Double duration of cells on top of Deluge/Suiton + unit->limit *= 2; + group->limit = unit->limit; + } + + // execute on all targets standing on this cell + if (range==0 && active_flag) + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); + } + + if (!group->alive_count) + { //No cells? Something that was blocked completely by Land Protector? + skill_delunitgroup(group); + return NULL; + } + + //success, unit created. + switch( skill_id ) { + case WZ_ICEWALL: + map_foreachinrange(skill_icewall_block, src, AREA_SIZE, BL_MOB); + break; + case NJ_TATAMIGAESHI: //Store number of tiles. + group->val1 = group->alive_count; + break; + } + + return group; +} + +/*========================================== + * + *------------------------------------------*/ +void ext_skill_unit_onplace(struct skill_unit *src, struct block_list *bl, unsigned int tick){skill_unit_onplace(src, bl, tick);} +static int skill_unit_onplace (struct skill_unit *src, struct block_list *bl, unsigned int tick) +{ + struct skill_unit_group *sg; + struct block_list *ss; + struct status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + uint16 skill_id; + + nullpo_ret(src); + nullpo_ret(bl); + + if(bl->prev==NULL || !src->alive || status_isdead(bl)) + return 0; + + nullpo_ret(sg=src->group); + nullpo_ret(ss=map_id2bl(sg->src_id)); + + if( skill_get_type(sg->skill_id) == BF_MAGIC && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) && sg->skill_id != SA_LANDPROTECTOR ) + return 0; //AoE skills are ineffective. [Skotlex] + + sc = status_get_sc(bl); + + if (sc && sc->option&OPTION_HIDE && sg->skill_id != WZ_HEAVENDRIVE && sg->skill_id != WL_EARTHSTRAIN ) + return 0; //Hidden characters are immune to AoE skills except to these. [Skotlex] + + type = status_skill2sc(sg->skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + skill_id = sg->skill_id; //In case the group is deleted, we need to return the correct skill id, still. + switch (sg->unit_id) + { + case UNT_SPIDERWEB: + if( sc && sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1 > 0 ) + { // If you are fiberlocked and can't move, it will only increase your fireweakness level. [Inkfish] + sc->data[SC_SPIDERWEB]->val2++; + break; + } + else if( sc ) + { + int sec = skill_get_time2(sg->skill_id,sg->skill_lv); + if( status_change_start(bl,type,10000,sg->skill_lv,1,sg->group_id,0,sec,8) ) + { + const struct TimerData* td = sc->data[type]?get_timer(sc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + map_moveblock(bl, src->bl.x, src->bl.y, tick); + clif_fixpos(bl); + sg->val2 = bl->id; + } + else + sec = 3000; //Couldn't trap it? + sg->limit = DIFF_TICK(tick,sg->tick)+sec; + } + break; + case UNT_SAFETYWALL: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->skill_id,sg->group_id,0,sg->limit); + break; + + case UNT_PNEUMA: + case UNT_CHAOSPANIC: + case UNT_MAELSTROM: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); + break; + case UNT_BLOODYLUST: + if (sg->src_id == bl->id) + break; //Does not affect the caster. + if (!sce) { + TBL_PC *sd = BL_CAST(BL_PC, bl); //prevent fullheal exploit + if (sd && sd->bloodylust_tick && DIFF_TICK(gettick(), sd->bloodylust_tick) < skill_get_time2(SC_BLOODYLUST, 1)) + clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, + sc_start4(bl, type, 100, sg->skill_lv, 1, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); + else { + if (sd) sd->bloodylust_tick = gettick(); + clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, + sc_start4(bl, type, 100, sg->skill_lv, 0, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); + } + } + break; + + case UNT_WARP_WAITING: { + int working = sg->val1&0xffff; + + if(bl->type==BL_PC && !working){ + struct map_session_data *sd = (struct map_session_data *)bl; + if((!sd->chatID || battle_config.chat_warpportal) + && sd->ud.to_x == src->bl.x && sd->ud.to_y == src->bl.y) + { + int x = sg->val2>>16; + int y = sg->val2&0xffff; + int count = sg->val1>>16; + unsigned short m = sg->val3; + + if( --count <= 0 ) + skill_delunitgroup(sg); + + if ( map_mapindex2mapid(sg->val3) == sd->bl.m && x == sd->bl.x && y == sd->bl.y ) + working = 1;/* we break it because officials break it, lovely stuff. */ + + sg->val1 = (count<<16)|working; + + pc_setpos(sd,m,x,y,CLR_TELEPORT); + } + } else if(bl->type == BL_MOB && battle_config.mob_warp&2) { + int16 m = map_mapindex2mapid(sg->val3); + if (m < 0) break; //Map not available on this map-server. + unit_warp(bl,m,sg->val2>>16,sg->val2&0xffff,CLR_TELEPORT); + } + } + break; + + case UNT_QUAGMIRE: + if( !sce && battle_check_target(&sg->unit->bl,bl,sg->target_flag) > 0 ) + sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); + break; + + case UNT_VOLCANO: + case UNT_DELUGE: + case UNT_VIOLENTGALE: + if(!sce) + sc_start(bl,type,100,sg->skill_lv,sg->limit); + break; + + case UNT_SUITON: + if(!sce) + sc_start4(bl,type,100,sg->skill_lv, + map_flag_vs(bl->m) || battle_check_target(&src->bl,bl,BCT_ENEMY)>0?1:0, //Send val3 =1 to reduce agi. + 0,0,sg->limit); + break; + + case UNT_HERMODE: + if (sg->src_id!=bl->id && battle_check_target(&src->bl,bl,BCT_PARTY|BCT_GUILD) > 0) + status_change_clear_buffs(bl,1); //Should dispell only allies. + case UNT_RICHMANKIM: + case UNT_ETERNALCHAOS: + case UNT_DRUMBATTLEFIELD: + case UNT_RINGNIBELUNGEN: + case UNT_ROKISWEIL: + case UNT_INTOABYSS: + case UNT_SIEGFRIED: + //Needed to check when a dancer/bard leaves their ensemble area. + if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) + return skill_id; + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); + break; + case UNT_WHISTLE: + case UNT_ASSASSINCROSS: + case UNT_POEMBRAGI: + case UNT_APPLEIDUN: + case UNT_HUMMING: + case UNT_DONTFORGETME: + case UNT_FORTUNEKISS: + case UNT_SERVICEFORYOU: + if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) + return 0; + + if (!sc) return 0; + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); + else if (sce->val4 == 1) { + //Readjust timers since the effect will not last long. + sce->val4 = 0; + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(tick+sg->limit, status_change_timer, bl->id, type); + } + break; + + case UNT_FOGWALL: + if (!sce) + { + sc_start4(bl, type, 100, sg->skill_lv, sg->val1, sg->val2, sg->group_id, sg->limit); + if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + skill_additional_effect (ss, bl, sg->skill_id, sg->skill_lv, BF_MISC, ATK_DEF, tick); + } + break; + + case UNT_GRAVITATION: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,0,BCT_ENEMY,sg->group_id,sg->limit); + break; + +// officially, icewall has no problems existing on occupied cells [ultramage] +// case UNT_ICEWALL: //Destroy the cell. [Skotlex] +// src->val1 = 0; +// if(src->limit + sg->tick > tick + 700) +// src->limit = DIFF_TICK(tick+700,sg->tick); +// break; + + case UNT_MOONLIT: + //Knockback out of area if affected char isn't in Moonlit effect + if (sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == CG_MOONLIT) + break; + if (ss == bl) //Also needed to prevent infinite loop crash. + break; + skill_blown(ss,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); + break; + + case UNT_WALLOFTHORN: + if( status_get_mode(bl)&MD_BOSS ) + break; // iRO Wiki says that this skill don't affect to Boss monsters. + if( map_flag_vs(bl->m) || bl->id == src->bl.id || battle_check_target(&src->bl,bl, BCT_ENEMY) == 1 ) + skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + break; + + case UNT_VOLCANIC_ASH: + if (!sce) + sc_start(bl, SC_ASH, 100, sg->skill_lv, skill_get_time(MH_VOLCANIC_ASH, sg->skill_lv)); + break; + + case UNT_GD_LEADERSHIP: + case UNT_GD_GLORYWOUNDS: + case UNT_GD_SOULCOLD: + case UNT_GD_HAWKEYES: + if ( !sce ) + sc_start4(bl,type,100,sg->skill_lv,0,0,0,1000); + break; + } + return skill_id; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_onplace_timer (struct skill_unit *src, struct block_list *bl, unsigned int tick) +{ + struct skill_unit_group *sg; + struct block_list *ss; + TBL_PC* tsd; + struct status_data *tstatus; + struct status_change *tsc; + struct skill_unit_group_tickset *ts; + enum sc_type type; + uint16 skill_id; + int diff=0; + + nullpo_ret(src); + nullpo_ret(bl); + + if (bl->prev==NULL || !src->alive || status_isdead(bl)) + return 0; + + nullpo_ret(sg=src->group); + nullpo_ret(ss=map_id2bl(sg->src_id)); + tsd = BL_CAST(BL_PC, bl); + tsc = status_get_sc(bl); + + if ( tsc && tsc->data[SC_HOVERING] ) + return 0; //Under hovering characters are immune to trap and ground target skills. + + tstatus = status_get_status_data(bl); + type = status_skill2sc(sg->skill_id); + skill_id = sg->skill_id; + + if (sg->interval == -1) { + switch (sg->unit_id) { + case UNT_ANKLESNARE: //These happen when a trap is splash-triggered by multiple targets on the same cell. + case UNT_FIREPILLAR_ACTIVE: + case UNT_ELECTRICSHOCKER: + case UNT_MANHOLE: + return 0; + default: + ShowError("skill_unit_onplace_timer: interval error (unit id %x)\n", sg->unit_id); + return 0; + } + } + + if ((ts = skill_unitgrouptickset_search(bl,sg,tick))) + { //Not all have it, eg: Traps don't have it even though they can be hit by Heaven's Drive [Skotlex] + diff = DIFF_TICK(tick,ts->tick); + if (diff < 0) + return 0; + ts->tick = tick+sg->interval; + + if ((skill_id==CR_GRANDCROSS || skill_id==NPC_GRANDDARKNESS) && !battle_config.gx_allhit) + ts->tick += sg->interval*(map_count_oncell(bl->m,bl->x,bl->y,BL_CHAR)-1); + } + + switch (sg->unit_id) + { + case UNT_FIREWALL: + case UNT_KAEN: + { + int count=0; + const int x = bl->x, y = bl->y; + + if( sg->skill_id == GN_WALLOFTHORN && !map_flag_vs(bl->m) ) + break; + + //Take into account these hit more times than the timer interval can handle. + do + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0); + while(--src->val2 && x == bl->x && y == bl->y && + ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl)); + + if (src->val2<=0) + skill_delunit(src); + } + break; + + case UNT_SANCTUARY: + if( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race==RC_DEMON ) + { //Only damage enemies with offensive Sanctuary. [Skotlex] + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 && skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0) ) + sg->val1 -= 2; // reduce healing count if this was meant for damaging [hekate] + } + else + { + int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); + struct mob_data *md = BL_CAST(BL_MOB, bl); +#ifdef RENEWAL + if( md && md->class_ == MOBID_EMPERIUM ) + break; +#endif + if( md && mob_is_battleground(md) ) + break; + if( tstatus->hp >= tstatus->max_hp ) + break; + if( status_isimmune(bl) ) + heal = 0; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + status_heal(bl, heal, 0, 0); + if( diff >= 500 ) + sg->val1--; + } + if( sg->val1 <= 0 ) + skill_delunitgroup(sg); + break; + + case UNT_EVILLAND: + //Will heal demon and undead element monsters, but not players. + if ((bl->type == BL_PC) || (!battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race!=RC_DEMON)) + { //Damage enemies + if(battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + } else { + int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); + if (tstatus->hp >= tstatus->max_hp) + break; + if (status_isimmune(bl)) + heal = 0; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + status_heal(bl, heal, 0, 0); + } + break; + + case UNT_MAGNUS: + if (!battle_check_undead(tstatus->race,tstatus->def_ele) && tstatus->race!=RC_DEMON) + break; + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_DUMMYSKILL: + switch (sg->skill_id) + { + case SG_SUN_WARM: //SG skills [Komurka] + case SG_MOON_WARM: + case SG_STAR_WARM: + { + int count = 0; + const int x = bl->x, y = bl->y; + + //If target isn't knocked back it should hit every "interval" ms [Playtester] + do + { + if( bl->type == BL_PC ) + status_zap(bl, 0, 15); // sp damage to players + else // mobs + if( status_charge(ss, 0, 2) ) // costs 2 SP per hit + { + if( !skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0) ) + status_charge(ss, 0, 8); //costs additional 8 SP if miss + } + else + { //should end when out of sp. + sg->limit = DIFF_TICK(tick,sg->tick); + break; + } + } while( x == bl->x && y == bl->y && + ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl) ); + } + break; + /** + * The storm gust counter was dropped in renewal + **/ + #ifndef RENEWAL + case WZ_STORMGUST: //SG counter does not reset per stormgust. IE: One hit from a SG and two hits from another will freeze you. + if (tsc) + tsc->sg_counter++; //SG hit counter. + if (skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0) <= 0 && tsc) + tsc->sg_counter=0; //Attack absorbed. + break; + #endif + case GS_DESPERADO: + if (rnd()%100 < src->val1) + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + case GN_CRAZYWEED_ATK: + if( bl->type == BL_SKILL ){ + struct skill_unit *su = (struct skill_unit *)bl; + if( su && !(skill_get_inf2(su->group->skill_id)&INF2_TRAP) ) + break; + } + default: + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case UNT_FIREPILLAR_WAITING: + skill_unitsetting(ss,sg->skill_id,sg->skill_lv,src->bl.x,src->bl.y,1); + skill_delunit(src); + break; + + case UNT_SKIDTRAP: + { + skill_blown(&src->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); + sg->unit_id = UNT_USED_TRAPS; + clif_changetraplook(&src->bl, UNT_USED_TRAPS); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + } + break; + + case UNT_ANKLESNARE: + case UNT_MANHOLE: + if( sg->val2 == 0 && tsc && (sg->unit_id == UNT_ANKLESNARE || bl->id != sg->src_id) ) { + int sec = skill_get_time2(sg->skill_id,sg->skill_lv); + if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,sec, 8) ) { + const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); + clif_fixpos(bl); + sg->val2 = bl->id; + } else + sec = 3000; //Couldn't trap it? + if( sg->unit_id == UNT_ANKLESNARE ) { + clif_skillunit_update(&src->bl); + /** + * If you're snared from a trap that was invisible this makes the trap be + * visible again -- being you stepped on it (w/o this the trap remains invisible and you go "WTF WHY I CANT MOVE") + * bugreport:3961 + **/ + clif_changetraplook(&src->bl, UNT_ANKLESNARE); + } + sg->limit = DIFF_TICK(tick,sg->tick)+sec; + sg->interval = -1; + src->range = 0; + } + break; + + case UNT_ELECTRICSHOCKER: + if( bl->id != ss->id ) { + if( status_get_mode(bl)&MD_BOSS ) + break; + if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id, sg->skill_lv), 8) ) { + + map_moveblock(bl, src->bl.x, src->bl.y, tick); + clif_fixpos(bl); + + } + + map_foreachinrange(skill_trap_splash, &src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl, tick); + sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. + } + break; + + case UNT_VENOMDUST: + if(tsc && !tsc->data[type]) + status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + break; + + + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + if( bl->type == BL_PC )// it won't work on players + break; + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + case UNT_CLUSTERBOMB: + if( bl->id == ss->id )// it won't trigger on caster + break; + case UNT_LANDMINE: + case UNT_CLAYMORETRAP: + case UNT_BLASTMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_FIREPILLAR_ACTIVE: + map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); + if (sg->unit_id != UNT_FIREPILLAR_ACTIVE) + clif_changetraplook(&src->bl, sg->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); + sg->limit=DIFF_TICK(tick,sg->tick)+1500 + + (sg->unit_id== UNT_CLUSTERBOMB || sg->unit_id== UNT_ICEBOUNDTRAP?1000:0);// Cluster Bomb/Icebound has 1s to disappear once activated. + sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. + break; + + case UNT_TALKIEBOX: + if (sg->src_id == bl->id) + break; + if (sg->val2 == 0){ + clif_talkiebox(&src->bl, sg->valstr); + sg->unit_id = UNT_USED_TRAPS; + clif_changetraplook(&src->bl, UNT_USED_TRAPS); + sg->limit = DIFF_TICK(tick, sg->tick) + 5000; + sg->val2 = -1; + } + break; + + case UNT_LULLABY: + if (ss->id == bl->id) + break; + skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); + break; + + case UNT_UGLYDANCE: //Ugly Dance [Skotlex] + if (ss->id != bl->id) + skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); + break; + + case UNT_DISSONANCE: + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + break; + + case UNT_APPLEIDUN: //Apple of Idun [Skotlex] + { + int heal; +#ifdef RENEWAL + struct mob_data *md = BL_CAST(BL_MOB, bl); + if( md && md->class_ == MOBID_EMPERIUM ) + break; +#endif + if( sg->src_id == bl->id && !(tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_BARDDANCER) ) + break; // affects self only when soullinked + heal = skill_calc_heal(ss,bl,sg->skill_id, sg->skill_lv, true); + if( tsc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + status_heal(bl, heal, 0, 0); + break; + } + + case UNT_TATAMIGAESHI: + case UNT_DEMONSTRATION: + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_GOSPEL: + if (rnd()%100 > sg->skill_lv*10 || ss == bl) + break; + if (battle_check_target(ss,bl,BCT_PARTY)>0) + { // Support Effect only on party, not guild + int heal; + int i = rnd()%13; // Positive buff count + int time = skill_get_time2(sg->skill_id, sg->skill_lv); //Duration + switch (i) + { + case 0: // Heal 1~9999 HP + heal = rnd() %9999+1; + clif_skill_nodamage(ss,bl,AL_HEAL,heal,1); + status_heal(bl,heal,0,0); + break; + case 1: // End all negative status + status_change_clear_buffs(bl,6); + if (tsd) clif_gospel_info(tsd, 0x15); + break; + case 2: // Immunity to all status + sc_start(bl,SC_SCRESIST,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x16); + break; + case 3: // MaxHP +100% + sc_start(bl,SC_INCMHPRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x17); + break; + case 4: // MaxSP +100% + sc_start(bl,SC_INCMSPRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x18); + break; + case 5: // All stats +20 + sc_start(bl,SC_INCALLSTATUS,100,20,time); + if (tsd) clif_gospel_info(tsd, 0x19); + break; + case 6: // Level 10 Blessing + sc_start(bl,SC_BLESSING,100,10,time); + break; + case 7: // Level 10 Increase AGI + sc_start(bl,SC_INCREASEAGI,100,10,time); + break; + case 8: // Enchant weapon with Holy element + sc_start(bl,SC_ASPERSIO,100,1,time); + if (tsd) clif_gospel_info(tsd, 0x1c); + break; + case 9: // Enchant armor with Holy element + sc_start(bl,SC_BENEDICTIO,100,1,time); + if (tsd) clif_gospel_info(tsd, 0x1d); + break; + case 10: // DEF +25% + sc_start(bl,SC_INCDEFRATE,100,25,time); + if (tsd) clif_gospel_info(tsd, 0x1e); + break; + case 11: // ATK +100% + sc_start(bl,SC_INCATKRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x1f); + break; + case 12: // HIT/Flee +50 + sc_start(bl,SC_INCHIT,100,50,time); + sc_start(bl,SC_INCFLEE,100,50,time); + if (tsd) clif_gospel_info(tsd, 0x20); + break; + } + } + else if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + { // Offensive Effect + int i = rnd()%9; // Negative buff count + int time = skill_get_time2(sg->skill_id, sg->skill_lv); + switch (i) + { + case 0: // Deal 1~9999 damage + skill_attack(BF_MISC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + case 1: // Curse + sc_start(bl,SC_CURSE,100,1,time); + break; + case 2: // Blind + sc_start(bl,SC_BLIND,100,1,time); + break; + case 3: // Poison + sc_start(bl,SC_POISON,100,1,time); + break; + case 4: // Level 10 Provoke + sc_start(bl,SC_PROVOKE,100,10,time); + break; + case 5: // DEF -100% + sc_start(bl,SC_INCDEFRATE,100,-100,time); + break; + case 6: // ATK -100% + sc_start(bl,SC_INCATKRATE,100,-100,time); + break; + case 7: // Flee -100% + sc_start(bl,SC_INCFLEERATE,100,-100,time); + break; + case 8: // Speed/ASPD -25% + sc_start4(bl,SC_GOSPEL,100,1,0,0,BCT_ENEMY,time); + break; + } + } + break; + + case UNT_BASILICA: + { + int i = battle_check_target(&src->bl, bl, BCT_ENEMY); + if( i > 0 && !(status_get_mode(bl)&MD_BOSS) ) + { // knock-back any enemy except Boss + skill_blown(&src->bl, bl, 2, unit_getdir(bl), 0); + clif_fixpos(bl); + } + + if( sg->src_id != bl->id && i <= 0 ) + sc_start4(bl, type, 100, 0, 0, 0, src->bl.id, sg->interval + 100); + } + break; + + case UNT_GRAVITATION: + case UNT_EARTHSTRAIN: + case UNT_FIREWALK: + case UNT_ELECTRICWALK: + case UNT_PSYCHIC_WAVE: + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_GROUNDDRIFT_WIND: + case UNT_GROUNDDRIFT_DARK: + case UNT_GROUNDDRIFT_POISON: + case UNT_GROUNDDRIFT_WATER: + case UNT_GROUNDDRIFT_FIRE: + map_foreachinrange(skill_trap_splash,&src->bl, + skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, + &src->bl,tick); + sg->unit_id = UNT_USED_TRAPS; + //clif_changetraplook(&src->bl, UNT_FIREPILLAR_ACTIVE); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + /** + * 3rd stuff + **/ + case UNT_POISONSMOKE: + if( battle_check_target(ss,bl,BCT_ENEMY) > 0 && !(tsc && tsc->data[sg->val2]) && rnd()%100 < 20 ) + sc_start(bl,sg->val2,100,sg->val3,skill_get_time2(GC_POISONINGWEAPON, 1)); + break; + + case UNT_EPICLESIS: + if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) + { + if( ++sg->val2 % 3 == 0 ) { + int hp, sp; + switch( sg->skill_lv ) + { + case 1: case 2: hp = 3; sp = 2; break; + case 3: case 4: hp = 4; sp = 3; break; + case 5: default: hp = 5; sp = 4; break; + } + hp = tstatus->max_hp * hp / 100; + sp = tstatus->max_sp * sp / 100; + status_heal(bl, hp, sp, 2); + sc_start(bl, type, 100, sg->skill_lv, (sg->interval * 3) + 100); + } + // Reveal hidden players every 5 seconds. + if( sg->val2 % 5 == 0 ) { + // TODO: check if other hidden status can be removed. + status_change_end(bl,SC_HIDING,INVALID_TIMER); + status_change_end(bl,SC_CLOAKING,INVALID_TIMER); + } + } + /* Enable this if kRO fix the current skill. Currently no damage on undead and demon monster. [Jobbie] + else if( battle_check_target(ss, bl, BCT_ENEMY) > 0 && battle_check_undead(tstatus->race, tstatus->def_ele) ) + skill_castend_damage_id(&src->bl, bl, sg->skill_id, sg->skill_lv, 0, 0);*/ + break; + + case UNT_STEALTHFIELD: + if( bl->id == sg->src_id ) + break; // Dont work on Self (video shows that) + case UNT_NEUTRALBARRIER: + sc_start(bl,type,100,sg->skill_lv,sg->interval + 100); + break; + + case UNT_DIMENSIONDOOR: + if( tsd && !map[bl->m].flag.noteleport ) + pc_randomwarp(tsd,3); + else if( bl->type == BL_MOB && battle_config.mob_warp&8 ) + unit_warp(bl,-1,-1,-1,3); + break; + + case UNT_REVERBERATION: + clif_changetraplook(&src->bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); + sg->limit = DIFF_TICK(tick,sg->tick)+1000; + sg->unit_id = UNT_USED_TRAPS; + break; + + case UNT_SEVERE_RAINSTORM: + if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) + skill_attack(BF_WEAPON,ss,&src->bl,bl,WM_SEVERE_RAINSTORM_MELEE,sg->skill_lv,tick,0); + break; + case UNT_NETHERWORLD: + if( !(status_get_mode(bl)&MD_BOSS) && ss != bl && battle_check_target(&src->bl, bl, BCT_PARTY) ) { + if( !(tsc && tsc->data[type]) ){ + sc_start(bl, type, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); + sg->limit = DIFF_TICK(tick,sg->tick); + sg->unit_id = UNT_USED_TRAPS; + } + } + break; + case UNT_THORNS_TRAP: + if( tsc ) { + if( !sg->val2 ) { + int sec = skill_get_time2(sg->skill_id, sg->skill_lv); + if( sc_start(bl, type, 100, sg->skill_lv, sec) ) { + const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + ///map_moveblock(bl, src->bl.x, src->bl.y, tick); // in official server it doesn't behave like this. [malufett] + clif_fixpos(bl); + sg->val2 = bl->id; + } else + sec = 3000; // Couldn't trap it? + sg->limit = DIFF_TICK(tick, sg->tick) + sec; + } else if( tsc->data[SC_THORNSTRAP] && bl->id == sg->val2 ) + skill_attack(skill_get_type(GN_THORNS_TRAP), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); + } + break; + + case UNT_DEMONIC_FIRE: { + TBL_PC* sd = BL_CAST(BL_PC, ss); + switch( sg->val2 ) { + case 1: + case 2: + default: + sc_start(bl, SC_BURNING, 4 + 4 * sg->skill_lv, sg->skill_lv, + skill_get_time2(sg->skill_id, sg->skill_lv)); + skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, + sg->skill_id, sg->skill_lv + 10 * sg->val2, tick, 0); + break; + case 3: + skill_attack(skill_get_type(CR_ACIDDEMONSTRATION), ss, &src->bl, bl, + CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : sg->skill_lv, tick, 0); + break; + + } + } + break; + + case UNT_FIRE_EXPANSION_SMOKE_POWDER: + sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_SMOKE_POWDER), 100, sg->skill_lv, 1000); + break; + + case UNT_FIRE_EXPANSION_TEAR_GAS: + sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_TEAR_GAS), 100, sg->skill_lv, 1000); + break; + + case UNT_HELLS_PLANT: + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ) + skill_attack(skill_get_type(GN_HELLS_PLANT_ATK), ss, &src->bl, bl, GN_HELLS_PLANT_ATK, sg->skill_lv, tick, 0); + if( ss != bl) //The caster is the only one who can step on the Plants, without destroying them + sg->limit = DIFF_TICK(tick, sg->tick) + 100; + break; + + case UNT_CLOUD_KILL: + if(tsc && !tsc->data[type]) + status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),8); + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_WARMER: + if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) { + int hp = 125 * sg->skill_lv; // Officially is 125 * skill_lv. + struct status_change *ssc = status_get_sc(ss); + if( ssc && ssc->data[SC_HEATER_OPTION] ) + hp += hp * ssc->data[SC_HEATER_OPTION]->val3 / 100; + if( tstatus->hp != tstatus->max_hp ) + clif_skill_nodamage(&src->bl, bl, AL_HEAL, hp, 0); + if( tsc && tsc->data[SC_AKAITSUKI] && hp ) + hp = ~hp + 1; + status_heal(bl, hp, 0, 0); + sc_start(bl, SC_WARMER, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); + } + break; + + case UNT_FIRE_INSIGNIA: + case UNT_WATER_INSIGNIA: + case UNT_WIND_INSIGNIA: + case UNT_EARTH_INSIGNIA: + case UNT_ZEPHYR: + sc_start(bl,type, 100, sg->skill_lv, sg->interval); + if (sg->unit_id != UNT_ZEPHYR && !battle_check_undead(tstatus->race, tstatus->def_ele)) { + int hp = tstatus->max_hp / 100; //+1% each 5s + if ((sg->val3) % 5) { //each 5s + if (tstatus->def_ele == skill_get_ele(sg->skill_id,sg->skill_lv)){ + status_heal(bl, hp, 0, 2); + } else if((sg->unit_id == UNT_FIRE_INSIGNIA && tstatus->def_ele == ELE_EARTH) + ||(sg->unit_id == UNT_WATER_INSIGNIA && tstatus->def_ele == ELE_FIRE) + ||(sg->unit_id == UNT_WIND_INSIGNIA && tstatus->def_ele == ELE_WATER) + ||(sg->unit_id == UNT_EARTH_INSIGNIA && tstatus->def_ele == ELE_WIND) + ){ + status_heal(bl, -hp, 0, 0); + } + } + sg->val3++; //timer + if (sg->val3 > 5) sg->val3 = 0; + } + break; + + case UNT_VACUUM_EXTREME: + {// TODO: official behavior in gvg area. [malufett] + int sec = sg->limit - DIFF_TICK(tick, sg->tick); + int range = skill_get_unit_range(sg->skill_id, sg->skill_lv); + + if( tsc && !tsc->data[type] && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range)// don't consider outer bounderies + sc_start(bl, type, 100, sg->skill_lv, sec); + + if( unit_is_walking(bl) && // wait until target stop walking + ( tsc && tsc->data[type] && tsc->data[type]->val4 >= tsc->data[type]->val3-range )) + break; + + if( tsc && ( !tsc->data[type] || (tsc->data[type] && tsc->data[type]->val4 < 1 ) ) ) + break; + + if( unit_is_walking(bl) && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) > range )// going outside of boundaries? then force it to stop + unit_stop_walking(bl,1); + + if( !unit_is_walking(bl) && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range && // only snap if the target is inside the range or + src->bl.x != bl->x && src->bl.y != bl->y){// diagonal position parallel to VE's center + unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); + clif_fixpos(bl); + } + } + break; + + case UNT_FIRE_MANTLE: + if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_ZENKAI_WATER: + case UNT_ZENKAI_LAND: + case UNT_ZENKAI_FIRE: + case UNT_ZENKAI_WIND: + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ){ + switch( sg->unit_id ){ + case UNT_ZENKAI_WATER: + sc_start(bl, SC_CRYSTALIZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_FREEZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_FREEZING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_LAND: + sc_start(bl, SC_STONE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_POISON, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_FIRE: + sc_start(bl, SC_BURNING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_WIND: + sc_start(bl, SC_SILENCE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_SLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_DEEPSLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + } + }else + sc_start2(bl,type,100,sg->val1,sg->val2,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + + case UNT_MAKIBISHI: + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + sg->limit = DIFF_TICK(tick, sg->tick); + sg->unit_id = UNT_USED_TRAPS; + break; + + case UNT_LAVA_SLIDE: + skill_attack(BF_WEAPON, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + if(++sg->val1 > 4) //after 5 stop hit and destroy me + sg->limit = DIFF_TICK(tick, sg->tick); + break; + case UNT_POISON_MIST: + skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + status_change_start(bl, SC_BLIND, rnd() % 100 > sg->skill_lv * 10, sg->skill_lv, sg->skill_id, 0, 0, skill_get_time2(sg->skill_id, sg->skill_lv), 2|8); + break; + } + + if (bl->type == BL_MOB && ss != bl) + mobskill_event((TBL_MOB*)bl, ss, tick, MSC_SKILLUSED|(skill_id<<16)); + + return skill_id; +} +/*========================================== + * Triggered when a char steps out of a skill cell + *------------------------------------------*/ +int skill_unit_onout (struct skill_unit *src, struct block_list *bl, unsigned int tick) +{ + struct skill_unit_group *sg; + struct status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + + nullpo_ret(src); + nullpo_ret(bl); + nullpo_ret(sg=src->group); + sc = status_get_sc(bl); + type = status_skill2sc(sg->skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + if( bl->prev==NULL || + (status_isdead(bl) && sg->unit_id != UNT_ANKLESNARE && sg->unit_id != UNT_SPIDERWEB) ) //Need to delete the trap if the source died. + return 0; + + switch(sg->unit_id){ + case UNT_SAFETYWALL: + case UNT_PNEUMA: + case UNT_EPICLESIS://Arch Bishop + case UNT_NEUTRALBARRIER: + case UNT_STEALTHFIELD: + if (sce) + status_change_end(bl, type, INVALID_TIMER); + break; + + case UNT_BASILICA: + if( sce && sce->val4 == src->bl.id ) + status_change_end(bl, type, INVALID_TIMER); + break; + case UNT_HERMODE: //Clear Hermode if the owner moved. + if (sce && sce->val3 == BCT_SELF && sce->val4 == sg->group_id) + status_change_end(bl, type, INVALID_TIMER); + break; + + case UNT_SPIDERWEB: + { + struct block_list *target = map_id2bl(sg->val2); + if (target && target==bl) + { + if (sce && sce->val3 == sg->group_id) + status_change_end(bl, type, INVALID_TIMER); + sg->limit = DIFF_TICK(tick,sg->tick)+1000; + } + break; + } + } + return sg->skill_id; +} + +/*========================================== + * Triggered when a char steps out of a skill group (entirely) [Skotlex] + *------------------------------------------*/ +static int skill_unit_onleft (uint16 skill_id, struct block_list *bl, unsigned int tick) +{ + struct status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + + type = status_skill2sc(skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + switch (skill_id) + { + case WZ_QUAGMIRE: + if (bl->type==BL_MOB) + break; + if (sce) + status_change_end(bl, type, INVALID_TIMER); + break; + + case BD_LULLABY: + case BD_RICHMANKIM: + case BD_ETERNALCHAOS: + case BD_DRUMBATTLEFIELD: + case BD_RINGNIBELUNGEN: + case BD_ROKISWEIL: + case BD_INTOABYSS: + case BD_SIEGFRIED: + if(sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == skill_id) + { //Check if you just stepped out of your ensemble skill to cancel dancing. [Skotlex] + //We don't check for SC_LONGING because someone could always have knocked you back and out of the song/dance. + //FIXME: This code is not perfect, it doesn't checks for the real ensemble's owner, + //it only checks if you are doing the same ensemble. So if there's two chars doing an ensemble + //which overlaps, by stepping outside of the other parther's ensemble will cause you to cancel + //your own. Let's pray that scenario is pretty unlikely and noone will complain too much about it. + status_change_end(bl, SC_DANCING, INVALID_TIMER); + } + case MH_STEINWAND: + case MG_SAFETYWALL: + case AL_PNEUMA: + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + case CG_HERMODE: + case HW_GRAVITATION: + case NJ_SUITON: + case SC_MAELSTROM: + case EL_WATER_BARRIER: + case EL_ZEPHYR: + case EL_POWER_OF_GAIA: + case SO_FIRE_INSIGNIA: + case SO_WATER_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + if (sce) + status_change_end(bl, type, INVALID_TIMER); + break; + case SC_BLOODYLUST: + if (sce) { + status_change_end(bl, type, INVALID_TIMER); + status_set_sp(bl, 0, 0); //set sp to 0 when quitting zone + } + break; + + case BA_POEMBRAGI: + case BA_WHISTLE: + case BA_ASSASSINCROSS: + case BA_APPLEIDUN: + case DC_HUMMING: + case DC_DONTFORGETME: + case DC_FORTUNEKISS: + case DC_SERVICEFORYOU: + if (sce) + { + delete_timer(sce->timer, status_change_timer); + //NOTE: It'd be nice if we could get the skill_lv for a more accurate extra time, but alas... + //not possible on our current implementation. + sce->val4 = 1; //Store the fact that this is a "reduced" duration effect. + sce->timer = add_timer(tick+skill_get_time2(skill_id,1), status_change_timer, bl->id, type); + } + break; + case PF_FOGWALL: + if (sce) + { + status_change_end(bl, type, INVALID_TIMER); + if ((sce=sc->data[SC_BLIND])) + { + if (bl->type == BL_PC) //Players get blind ended inmediately, others have it still for 30 secs. [Skotlex] + status_change_end(bl, SC_BLIND, INVALID_TIMER); + else { + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(30000+tick, status_change_timer, bl->id, SC_BLIND); + } + } + } + break; + case GD_LEADERSHIP: + case GD_GLORYWOUNDS: + case GD_SOULCOLD: + case GD_HAWKEYES: + if( !(sce && sce->val4) ) + status_change_end(bl, type, INVALID_TIMER); + break; + } + + return skill_id; +} + +/*========================================== + * Invoked when a unit cell has been placed/removed/deleted. + * flag values: + * flag&1: Invoke onplace function (otherwise invoke onout) + * flag&4: Invoke a onleft call (the unit might be scheduled for deletion) + *------------------------------------------*/ +static int skill_unit_effect (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = va_arg(ap,struct skill_unit*); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + unsigned int flag = va_arg(ap,unsigned int); + uint16 skill_id; + bool dissonance; + + if( (!unit->alive && !(flag&4)) || bl->prev == NULL ) + return 0; + + nullpo_ret(group); + + dissonance = skill_dance_switch(unit, 0); + + //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] + skill_id = group->skill_id; + //Target-type check. + if( !(group->bl_flag&bl->type && battle_check_target(&unit->bl,bl,group->target_flag)>0) && (flag&4) ) { + if( group->state.song_dance&0x1 || (group->src_id == bl->id && group->state.song_dance&0x2) ) + skill_unit_onleft(skill_id, bl, tick);//Ensemble check to terminate it. + } else { + if( flag&1 ) + skill_unit_onplace(unit,bl,tick); + else + skill_unit_onout(unit,bl,tick); + + if( flag&4 ) + skill_unit_onleft(skill_id, bl, tick); + } + + if( dissonance ) skill_dance_switch(unit, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_ondamaged (struct skill_unit *src, struct block_list *bl, int damage, unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_ret(src); + nullpo_ret(sg=src->group); + + switch( sg->unit_id ) { + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_CLAYMORETRAP: + case UNT_FREEZINGTRAP: + case UNT_TALKIEBOX: + case UNT_ANKLESNARE: + case UNT_ICEWALL: + case UNT_REVERBERATION: + case UNT_WALLOFTHORN: + src->val1-=damage; + break; + default: + damage = 0; + break; + } + return damage; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_check_condition_char_sub (struct block_list *bl, va_list ap) +{ + int *c, skill_id; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *tsd; + int *p_sd; //Contains the list of characters found. + + nullpo_ret(bl); + nullpo_ret(tsd=(struct map_session_data*)bl); + nullpo_ret(src=va_arg(ap,struct block_list *)); + nullpo_ret(sd=(struct map_session_data*)src); + + c=va_arg(ap,int *); + p_sd = va_arg(ap, int *); + skill_id = va_arg(ap,int); + + if ( ((skill_id != PR_BENEDICTIO && *c >=1) || *c >=2) && !(skill_get_inf2(skill_id)&INF2_CHORUS_SKILL) ) + return 0; //Partner found for ensembles, or the two companions for Benedictio. [Skotlex] + + if (bl == src) + return 0; + + if(pc_isdead(tsd)) + return 0; + + if (tsd->sc.data[SC_SILENCE] || ( tsd->sc.opt1 && tsd->sc.opt1 != OPT1_BURNING )) + return 0; + + if( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ) { + if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_MINSTRELWANDERER ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + } else { + + switch(skill_id) { + case PR_BENEDICTIO: { + uint8 dir = map_calc_dir(&sd->bl,tsd->bl.x,tsd->bl.y); + dir = (unit_getdir(&sd->bl) + dir)%8; //This adjusts dir to account for the direction the sd is facing. + if ((tsd->class_&MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == 2 || dir == 6) //Must be standing to the left/right of Priest. + && sd->status.sp >= 10) + p_sd[(*c)++]=tsd->bl.id; + return 1; + } + case AB_ADORAMUS: + // Adoramus does not consume Blue Gemstone when there is at least 1 Priest class next to the caster + if( (tsd->class_&MAPID_UPPERMASK) == MAPID_PRIEST ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + case WL_COMET: + // Comet does not consume Red Gemstones when there is at least 1 Warlock class next to the caster + if( ( sd->class_&MAPID_THIRDMASK ) == MAPID_WARLOCK ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + case LG_RAYOFGENESIS: + if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_ROYAL_GUARD && + tsd->sc.data[SC_BANDING] ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + default: //Warning: Assuming Ensemble Dance/Songs for code speed. [Skotlex] + { + uint16 skill_lv; + if(pc_issit(tsd) || !unit_can_move(&tsd->bl)) + return 0; + if (sd->status.sex != tsd->status.sex && + (tsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && + (skill_lv = pc_checkskill(tsd, skill_id)) > 0 && + (tsd->weapontype1==W_MUSICAL || tsd->weapontype1==W_WHIP) && + sd->status.party_id && tsd->status.party_id && + sd->status.party_id == tsd->status.party_id && + !tsd->sc.data[SC_DANCING]) + { + p_sd[(*c)++]=tsd->bl.id; + return skill_lv; + } else { + return 0; + } + } + break; + } + + } + return 0; +} + +/*========================================== + * Checks and stores partners for ensemble skills [Skotlex] + *------------------------------------------*/ +int skill_check_pc_partner (struct map_session_data *sd, uint16 skill_id, short* skill_lv, int range, int cast_flag) +{ + static int c=0; + static int p_sd[2] = { 0, 0 }; + int i; + bool is_chorus = ( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ); + + if (!battle_config.player_skill_partner_check || pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) + return is_chorus ? MAX_PARTY : 99; //As if there were infinite partners. + + if (cast_flag) { //Execute the skill on the partners. + struct map_session_data* tsd; + switch (skill_id) { + case PR_BENEDICTIO: + for (i = 0; i < c; i++) { + if ((tsd = map_id2sd(p_sd[i])) != NULL) + status_charge(&tsd->bl, 0, 10); + } + return c; + case AB_ADORAMUS: + if( c > 0 && (tsd = map_id2sd(p_sd[0])) != NULL ) { + i = 2 * (*skill_lv); + status_charge(&tsd->bl, 0, i); + } + break; + case WM_GREAT_ECHO: + for( i = 0; i < c; i++ ) { + if( (tsd = map_id2sd(p_sd[i])) != NULL ) + status_zap(&tsd->bl,0,skill_get_sp(skill_id,*skill_lv)/c); + } + break; + default: //Warning: Assuming Ensemble skills here (for speed) + if( is_chorus ) + break;//Chorus skills are not to be parsed as ensambles + if (c > 0 && sd->sc.data[SC_DANCING] && (tsd = map_id2sd(p_sd[0])) != NULL) { + sd->sc.data[SC_DANCING]->val4 = tsd->bl.id; + sc_start4(&tsd->bl,SC_DANCING,100,skill_id,sd->sc.data[SC_DANCING]->val2,*skill_lv,sd->bl.id,skill_get_time(skill_id,*skill_lv)+1000); + clif_skill_nodamage(&tsd->bl, &sd->bl, skill_id, *skill_lv, 1); + tsd->skill_id_dance = skill_id; + tsd->skill_lv_dance = *skill_lv; + } + return c; + } + } + + //Else: new search for partners. + c = 0; + memset (p_sd, 0, sizeof(p_sd)); + if( is_chorus ) + i = party_foreachsamemap(skill_check_condition_char_sub,sd,AREA_SIZE,&sd->bl, &c, &p_sd, skill_id, *skill_lv); + else + i = map_foreachinrange(skill_check_condition_char_sub, &sd->bl, range, BL_PC, &sd->bl, &c, &p_sd, skill_id); + + if ( skill_id != PR_BENEDICTIO && skill_id != AB_ADORAMUS && skill_id != WL_COMET ) //Apply the average lv to encore skills. + *skill_lv = (i+(*skill_lv))/(c+1); //I know c should be one, but this shows how it could be used for the average of n partners. + return c; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_check_condition_mob_master_sub (struct block_list *bl, va_list ap) +{ + int *c,src_id,mob_class,skill; + struct mob_data *md; + + md=(struct mob_data*)bl; + src_id=va_arg(ap,int); + mob_class=va_arg(ap,int); + skill=va_arg(ap,int); + c=va_arg(ap,int *); + + if( md->master_id != src_id || md->special_state.ai != (unsigned)(skill == AM_SPHEREMINE?2:skill == KO_ZANZOU?4:3) ) + return 0; //Non alchemist summoned mobs have nothing to do here. + + if(md->class_==mob_class) + (*c)++; + + return 1; +} + +/*========================================== + * Determines if a given skill should be made to consume ammo + * when used by the player. [Skotlex] + *------------------------------------------*/ +int skill_isammotype (struct map_session_data *sd, int skill) +{ + return ( + battle_config.arrow_decrement==2 && + (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) && + skill != HT_PHANTASMIC && + skill_get_type(skill) == BF_WEAPON && + !(skill_get_nk(skill)&NK_NO_DAMAGE) && + !skill_get_spiritball(skill,1) //Assume spirit spheres are used as ammo instead. + ); +} + +int skill_check_condition_castbegin(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) { + struct status_data *status; + struct status_change *sc; + struct skill_condition require; + int i; + + nullpo_ret(sd); + + if (sd->chatID) return 0; + + if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) + { //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] + sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. + sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. + return 1; + } + + switch( sd->menuskill_id ) { + case AM_PHARMACY: + switch( skill_id ) { + case AM_PHARMACY: + case AC_MAKINGARROW: + case BS_REPAIRWEAPON: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + return 0; + } + break; + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + if( sd->menuskill_id != skill_id ) + return 0; + break; + } + status = &sd->battle_status; + sc = &sd->sc; + if( !sc->count ) + sc = NULL; + + if( sd->skillitem == skill_id ) + { + if( sd->state.abra_flag ) // Hocus-Pocus was used. [Inkfish] + sd->state.abra_flag = 0; + else + { // When a target was selected, consume items that were skipped in pc_use_item [Skotlex] + if( (i = sd->itemindex) == -1 || + sd->status.inventory[i].nameid != sd->itemid || + sd->inventory_data[i] == NULL || + !sd->inventory_data[i]->flag.delay_consume || + sd->status.inventory[i].amount < 1 + ) + { //Something went wrong, item exploit? + sd->itemid = sd->itemindex = -1; + return 0; + } + //Consume + sd->itemid = sd->itemindex = -1; + if( skill_id == WZ_EARTHSPIKE && sc && sc->data[SC_EARTHSCROLL] && rnd()%100 > sc->data[SC_EARTHSCROLL]->val2 ) // [marquis007] + ; //Do not consume item. + else if( sd->status.inventory[i].expire_time == 0 ) + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); // Rental usable items are not consumed until expiration + } + return 1; + } + + if( pc_is90overweight(sd) ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); + return 0; + } + + if( sc && ( sc->data[SC__SHADOWFORM] || sc->data[SC__IGNORANCE] ) ) + return 0; + + switch( skill_id ) { // Turn off check. + case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: + case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: + case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: + case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case RA_WUGDASH: case KO_YAMIKUMO: + if( sc && sc->data[status_skill2sc(skill_id)] ) + return 1; + } + + // Check the skills that can be used while mounted on a warg + if( pc_isridingwug(sd) ) { + switch( skill_id ) { + case HT_SKIDTRAP: case HT_LANDMINE: case HT_ANKLESNARE: case HT_SHOCKWAVE: + case HT_SANDMAN: case HT_FLASHER: case HT_FREEZINGTRAP: case HT_BLASTMINE: + case HT_CLAYMORETRAP: case HT_SPRINGTRAP: case RA_DETONATOR: case RA_CLUSTERBOMB: + case HT_TALKIEBOX: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: + case RA_WUGDASH: case RA_WUGRIDER: case RA_WUGSTRIKE: + break; + default: // in official there is no message. + return 0; + } + + } + if( pc_ismadogear(sd) ) { + switch( skill_id ) { //None Mado skills are unusable when Mado is equipped. [Jobbie] + case BS_REPAIRWEAPON: case WS_MELTDOWN: + case BS_HAMMERFALL: case WS_CARTBOOST: + case BS_ADRENALINE: case WS_WEAPONREFINE: + case BS_WEAPONPERFECT: case WS_CARTTERMINATION: + case BS_OVERTHRUST: case WS_OVERTHRUSTMAX: + case BS_MAXIMIZE: case NC_AXEBOOMERANG: + case BS_ADRENALINE2: case NC_POWERSWING: + case BS_UNFAIRLYTRICK: case NC_AXETORNADO: + case BS_GREED: + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + default: //Only Mechanic exlcusive skill can be used. + break; + } + } + if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) + return 0; + + require = skill_get_requirement(sd,skill_id,skill_lv); + + //Can only update state when weapon/arrow info is checked. + sd->state.arrow_atk = require.ammo?1:0; + + // perform skill-specific checks (and actions) + switch( skill_id ) { + case SO_SPELLFIST: + if(sd->skill_id_old != MG_FIREBOLT && sd->skill_id_old != MG_COLDBOLT && sd->skill_id_old != MG_LIGHTNINGBOLT){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case SA_CASTCANCEL: + if(sd->ud.skilltimer == INVALID_TIMER) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case AL_WARP: + if(!battle_config.duel_allow_teleport && sd->duel_group) { // duel restriction [LuzZza] + char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_WARP)); + clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." + return 0; + } + break; + case MO_CALLSPIRITS: + if(sc && sc->data[SC_RAISINGDRAGON]) + skill_lv += sc->data[SC_RAISINGDRAGON]->val1; + if(sd->spiritball >= skill_lv) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case MO_FINGEROFFENSIVE: + case GS_FLING: + case SR_RAMPAGEBLASTER: + case SR_RIDEINLIGHTNING: + if( sd->spiritball > 0 && sd->spiritball < require.spiritball ) + sd->spiritball_old = require.spiritball = sd->spiritball; + else + sd->spiritball_old = require.spiritball; + break; + case MO_CHAINCOMBO: + if(!sc) + return 0; + if(sc->data[SC_BLADESTOP]) + break; + if(sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_TRIPLEATTACK) + break; + return 0; + case MO_COMBOFINISH: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_CHAINCOMBO)) + return 0; + break; + case CH_TIGERFIST: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_COMBOFINISH)) + return 0; + break; + case CH_CHAINCRUSH: + if(!(sc && sc->data[SC_COMBO])) + return 0; + if(sc->data[SC_COMBO]->val1 != MO_COMBOFINISH && sc->data[SC_COMBO]->val1 != CH_TIGERFIST) + return 0; + break; + case MO_EXTREMITYFIST: + // if(sc && sc->data[SC_EXTREMITYFIST]) //To disable Asura during the 5 min skill block uncomment this... + // return 0; + if( sc && (sc->data[SC_BLADESTOP] || sc->data[SC_CURSEDCIRCLE_ATKER]) ) + break; + if( sc && sc->data[SC_COMBO] ) + { + switch(sc->data[SC_COMBO]->val1) { + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + break; + default: + return 0; + } + } + else if( !unit_can_move(&sd->bl) ) + { //Placed here as ST_MOVE_ENABLE should not apply if rooted or on a combo. [Skotlex] + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_MISSION: + if( (sd->class_&MAPID_UPPERMASK) != MAPID_TAEKWON ) + {// Cannot be used by Non-Taekwon classes + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_READYCOUNTER: + case TK_READYDOWN: + case TK_READYSTORM: + case TK_READYTURN: + case TK_JUMPKICK: + if( (sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER ) + {// Soul Linkers cannot use this skill + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_TURNKICK: + case TK_STORMKICK: + case TK_DOWNKICK: + case TK_COUNTER: + if ((sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) + return 0; //Anti-Soul Linker check in case you job-changed with Stances active. + if(!(sc && sc->data[SC_COMBO]) || sc->data[SC_COMBO]->val1 == TK_JUMPKICK) + return 0; //Combo needs to be ready + + if (sc->data[SC_COMBO]->val3) { //Kick chain + //Do not repeat a kick. + if (sc->data[SC_COMBO]->val3 != skill_id) + break; + status_change_end(&sd->bl, SC_COMBO, INVALID_TIMER); + return 0; + } + if(sc->data[SC_COMBO]->val1 != skill_id && !( sd && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) )) { //Cancel combo wait. + unit_cancel_combo(&sd->bl); + return 0; + } + break; //Combo ready. + case BD_ADAPTATION: + { + int time; + if(!(sc && sc->data[SC_DANCING])) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + time = 1000*(sc->data[SC_DANCING]->val3>>16); + if (skill_get_time( + (sc->data[SC_DANCING]->val1&0xFFFF), //Dance Skill ID + (sc->data[SC_DANCING]->val1>>16)) //Dance Skill LV + - time < skill_get_time2(skill_id,skill_lv)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + break; + + case PR_BENEDICTIO: + if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 2) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case SL_SMA: + if(!(sc && sc->data[SC_SMA])) + return 0; + break; + + case HT_POWER: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id)) + return 0; + break; + + case CG_HERMODE: + if(!npc_check_areanpc(1,sd->bl.m,sd->bl.x,sd->bl.y,skill_get_splash(skill_id, skill_lv))) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case CG_MOONLIT: //Check there's no wall in the range+1 area around the caster. [Skotlex] + { + int i,x,y,range = skill_get_splash(skill_id, skill_lv)+1; + int size = range*2+1; + for (i=0;i<size*size;i++) { + x = sd->bl.x+(i%size-range); + y = sd->bl.y+(i/size-range); + if (map_getcell(sd->bl.m,x,y,CELL_CHKWALL)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + } + break; + case PR_REDEMPTIO: + { + int exp; + if( ((exp = pc_nextbaseexp(sd)) > 0 && get_percentage(sd->status.base_exp, exp) < 1) || + ((exp = pc_nextjobexp(sd)) > 0 && get_percentage(sd->status.job_exp, exp) < 1)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); //Not enough exp. + return 0; + } + break; + } + case AM_TWILIGHT2: + case AM_TWILIGHT3: + if (!party_skill_check(sd, sd->status.party_id, skill_id, skill_lv)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + if (sc && sc->data[SC_MIRACLE]) + break; + i = skill_id-SG_SUN_WARM; + if (sd->bl.m == sd->feel_map[i].m) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + break; + case SG_SUN_COMFORT: + case SG_MOON_COMFORT: + case SG_STAR_COMFORT: + if (sc && sc->data[SC_MIRACLE]) + break; + i = skill_id-SG_SUN_COMFORT; + if (sd->bl.m == sd->feel_map[i].m && + (battle_config.allow_skill_without_day || sg_info[i].day_func())) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + case SG_FUSION: + if (sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_STAR) + break; + //Auron insists we should implement SP consumption when you are not Soul Linked. [Skotlex] + //Only invoke on skill begin cast (instant cast skill). [Kevin] + if( require.sp > 0 ) + { + if (status->sp < (unsigned int)require.sp) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); + else + status_zap(&sd->bl, 0, require.sp); + } + return 0; + case GD_BATTLEORDER: + case GD_REGENERATION: + case GD_RESTORE: + if (!map_flag_gvg2(sd->bl.m)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case GD_EMERGENCYCALL: + // other checks were already done in skillnotok() + if (!sd->status.guild_id || !sd->state.gmaster_flag) + return 0; + break; + + case GS_GLITTERING: + if(sd->spiritball >= 10) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case NJ_ISSEN: +#ifdef RENEWAL + if (status->hp < (status->hp/100)) { +#else + if (status->hp < 2) { +#endif + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case NJ_BUNSINJYUTSU: + if (!(sc && sc->data[SC_NEN])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case NJ_ZENYNAGE: + case KO_MUCHANAGE: + if(sd->status.zeny < require.zeny) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); + return 0; + } + break; + case PF_HPCONVERSION: + if (status->sp == status->max_sp) + return 0; //Unusable when at full SP. + break; + case AM_CALLHOMUN: //Can't summon if a hom is already out + if (sd->status.hom_id && sd->hd && !sd->hd->homunculus.vaporize) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case AM_REST: //Can't vapo homun if you don't have an active homunc or it's hp is < 80% + if (!merc_is_hom_active(sd->hd) || sd->hd->battle_status.hp < (sd->hd->battle_status.max_hp*80/100)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Arch Bishop + **/ + case AB_ANCILLA: + { + int count = 0; + for( i = 0; i < MAX_INVENTORY; i ++ ) + if( sd->status.inventory[i].nameid == ITEMID_ANCILLA ) + count += sd->status.inventory[i].amount; + if( count >= 3 ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_ANCILLA_NUMOVER, 0); + return 0; + } + } + break; + /** + * Keeping as a note: + * Bug Report #17 provides a link to a sep-2011 changelog that shows this requirement was removed + **/ + //case AB_LAUDAAGNUS: + //case AB_LAUDARAMUS: + // if( !sd->status.party_id ) { + // clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + // return 0; + // } + // break; + + case AB_ADORAMUS: + /** + * Warlock + **/ + case WL_COMET: + if( skill_check_pc_partner(sd,skill_id,&skill_lv,1,0) <= 0 && ((i = pc_search_inventory(sd,require.itemid[0])) < 0 || sd->status.inventory[i].amount < require.amount[0]) ) + { + //clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_ITEM,require.amount[0],require.itemid[0]); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case WL_SUMMONFB: + case WL_SUMMONBL: + case WL_SUMMONWB: + case WL_SUMMONSTONE: + if( sc ) + { + ARR_FIND(SC_SPHERE_1,SC_SPHERE_5+1,i,!sc->data[i]); + if( i == SC_SPHERE_5+1 ) + { // No more free slots + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); + return 0; + } + } + break; + /** + * Guilotine Cross + **/ + case GC_HALLUCINATIONWALK: + if( sc && (sc->data[SC_HALLUCINATIONWALK] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case GC_COUNTERSLASH: + case GC_WEAPONCRUSH: + if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING) ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_WEAPONBLOCKING, 0); + return 0; + } + break; + /** + * Ranger + **/ + case RA_WUGMASTERY: + if( pc_isfalcon(sd) || pc_isridingwug(sd) || sd->sc.data[SC__GROOMY]) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGSTRIKE: + if( !pc_iswug(sd) && !pc_isridingwug(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGRIDER: + if( pc_isfalcon(sd) || ( !pc_isridingwug(sd) && !pc_iswug(sd) ) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGDASH: + if(!pc_isridingwug(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Royal Guard + **/ + case LG_BANDING: + if( sc && sc->data[SC_INSPIRATION] ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_PRESTIGE: + if( sc && (sc->data[SC_BANDING] || sc->data[SC_INSPIRATION]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_RAGEBURST: + if( sd->spiritball == 0 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SKILLINTERVAL,0); + return 0; + } + sd->spiritball_old = require.spiritball = sd->spiritball; + break; + case LG_RAYOFGENESIS: + if( sc && sc->data[SC_INSPIRATION] ) + return 1; // Don't check for partner. + if( !(sc && sc->data[SC_BANDING]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + return 0; + } else if( skill_check_pc_partner(sd,skill_id,&skill_lv,skill_get_range(skill_id,skill_lv),0) < 1 ) + return 0; // Just fails, no msg here. + break; + case LG_HESPERUSLIT: + if( !sc || !sc->data[SC_BANDING] ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case SR_FALLENEMPIRE: + if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_DRAGONCOMBO) ) + return 0; + break; + + case SR_CRESCENTELBOW: + if( sc && sc->data[SC_CRESCENTELBOW] ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_DUPLICATE, 0); + return 0; + } + break; + case SR_CURSEDCIRCLE: + if (map_flag_gvg(sd->bl.m)) { + if (map_foreachinrange(mob_count_sub, &sd->bl, skill_get_splash(skill_id, skill_lv), BL_MOB, + MOBID_EMPERIUM, MOBID_GUARIDAN_STONE1, MOBID_GUARIDAN_STONE2)) { + char output[128]; + sprintf(output, "You're too close to a stone or emperium to do this skill"); + clif_colormes(sd, COLOR_RED, output); + return 0; + } + } + if( sd->spiritball > 0 ) + sd->spiritball_old = require.spiritball = sd->spiritball; + else { + clif_skill_fail(sd,skill_id,0,0); + return 0; + } + break; + case SR_GATEOFHELL: + if( sd->spiritball > 0 ) + sd->spiritball_old = require.spiritball; + break; + case SC_MANHOLE: + case SC_DIMENSIONDOOR: + if( sc && sc->data[SC_MAGNETICFIELD] ) { + clif_skill_fail(sd,skill_id,0,0); + return 0; + } + break; + case WM_GREAT_ECHO: { + int count; + count = skill_check_pc_partner(sd, skill_id, &skill_lv, skill_get_splash(skill_id,skill_lv), 0); + if( count < 1 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); + return 0; + } else + require.sp -= require.sp * 20 * count / 100; // -20% each W/M in the party. + } + break; + case SO_FIREWALK: + case SO_ELECTRICWALK: // Can't be casted until you've walked all cells. + if( sc && sc->data[SC_PROPERTYWALK] && + sc->data[SC_PROPERTYWALK]->val3 < skill_get_maxcount(sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2) ) { + clif_skill_fail(sd,skill_id,0x0,0); + return 0; + } + break; + case SO_EL_CONTROL: + if( !sd->status.ele_id || !sd->ed ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RETURN_TO_ELDICASTES: + if( pc_ismadogear(sd) ) { //Cannot be used if Mado is equipped. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_REFLECTDAMAGE: + case CR_REFLECTSHIELD: + if( sc && sc->data[SC_KYOMU] && rand()%100 < 30){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case KO_KAHU_ENTEN: + case KO_HYOUHU_HUBUKI: + case KO_KAZEHU_SEIRAN: + case KO_DOHU_KOUKAI: + { + int ttype = skill_get_ele(skill_id, skill_lv); + ARR_FIND(1, 5, i, sd->talisman[i] > 0 && i != ttype); + if( (i < 5 && i != ttype) || sd->talisman[ttype] >= 10 ){ + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + break; + case KO_KAIHOU: + case KO_ZENKAI: + ARR_FIND(1, 6, i, sd->talisman[i] > 0); + if( i > 4 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + } + + switch(require.state) { + case ST_HIDING: + if(!(sc && sc->option&OPTION_HIDE)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_CLOAKING: + if(!pc_iscloaking(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_HIDDEN: + if(!pc_ishiding(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_RIDING: + if(!pc_isriding(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_FALCON: + if(!pc_isfalcon(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_CARTBOOST: + if(!(sc && sc->data[SC_CARTBOOST])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case ST_CART: + if(!pc_iscarton(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_SHIELD: + if(sd->status.shield <= 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_SIGHT: + if(!(sc && sc->data[SC_SIGHT])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_EXPLOSIONSPIRITS: + if(!(sc && sc->data[SC_EXPLOSIONSPIRITS])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_RECOV_WEIGHT_RATE: + if(battle_config.natural_heal_weight_rate <= 100 && sd->weight*100/sd->max_weight >= (unsigned int)battle_config.natural_heal_weight_rate) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_MOVE_ENABLE: + if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id) + sd->ud.canmove_tick = gettick(); //When using a combo, cancel the can't move delay to enable the skill. [Skotlex] + + if (!unit_can_move(&sd->bl)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_WATER: + if (sc && (sc->data[SC_DELUGE] || sc->data[SC_SUITON])) + break; + if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKWATER)) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + /** + * Rune Knight + **/ + case ST_RIDINGDRAGON: + if( !pc_isridingdragon(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Wug + **/ + case ST_WUG: + if( !pc_iswug(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Riding Wug + **/ + case ST_RIDINGWUG: + if( !pc_isridingwug(sd) ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Mechanic + **/ + case ST_MADO: + if( !pc_ismadogear(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Sorcerer + **/ + case ST_ELEMENTALSPIRIT: + if(!sd->ed) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_EL_SUMMON,0); + return 0; + } + break; + case ST_POISONINGWEAPON: + if (!(sc && sc->data[SC_POISONINGWEAPON])) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_POISONINGWEAPON, 0); + return 0; + } + break; + case ST_ROLLINGCUTTER: + if (!(sc && sc->data[SC_ROLLINGCUTTER])) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_CONDITION, 0); + return 0; + } + break; + case ST_MH_FIGHTING: + if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_FIGHTING)){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case ST_MH_GRAPPLING: + if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_GRAPPLING)){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + + if(require.mhp > 0 && get_percentage(status->hp, status->max_hp) > require.mhp) { + //mhp is the max-hp-requirement, that is, + //you must have this % or less of HP to cast it. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + return 0; + } + + if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + + if( require.sp > 0 && status->sp < (unsigned int)require.sp) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); + return 0; + } + + if( require.zeny > 0 && sd->status.zeny < require.zeny ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); + return 0; + } + + if( require.spiritball > 0 && sd->spiritball < require.spiritball) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SPIRITS,require.spiritball); + return 0; + } + + return 1; +} + +int skill_check_condition_castend(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) +{ + struct skill_condition require; + struct status_data *status; + int i; + int index[MAX_SKILL_ITEM_REQUIRE]; + + nullpo_ret(sd); + + if( sd->chatID ) + return 0; + + if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) { + //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] + sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. + sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. + return 1; + } + + switch( sd->menuskill_id ) { // Cast start or cast end?? + case AM_PHARMACY: + switch( skill_id ) { + case AM_PHARMACY: + case AC_MAKINGARROW: + case BS_REPAIRWEAPON: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + return 0; + } + break; + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + if( sd->menuskill_id != skill_id ) + return 0; + break; + } + + if( sd->skillitem == skill_id ) // Casting finished (Item skill or Hocus-Pocus) + return 1; + + if( pc_is90overweight(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); + return 0; + } + + // perform skill-specific checks (and actions) + switch( skill_id ) { + case PR_BENEDICTIO: + skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); + break; + case AM_CANNIBALIZE: + case AM_SPHEREMINE: { + int c=0; + int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; + //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; + int maxcount = (skill_id==AM_CANNIBALIZE)? 6-skill_lv : skill_get_maxcount(skill_id,skill_lv); + int mob_class = (skill_id==AM_CANNIBALIZE)? summons[skill_lv-1] :1142; + if(battle_config.land_skill_limit && maxcount>0 && (battle_config.land_skill_limit&BL_PC)) { + i = map_foreachinmap(skill_check_condition_mob_master_sub ,sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); + if(c >= maxcount || + (skill_id==AM_CANNIBALIZE && c != i && battle_config.summon_flora&2)) + { //Fails when: exceed max limit. There are other plant types already out. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + break; + } + case NC_SILVERSNIPER: + case NC_MAGICDECOY: { + int c = 0, j; + int maxcount = skill_get_maxcount(skill_id,skill_lv); + int mob_class = 2042; + if( skill_id == NC_MAGICDECOY ) + mob_class = 2043; + + if( battle_config.land_skill_limit && maxcount > 0 && ( battle_config.land_skill_limit&BL_PC ) ) { + if( skill_id == NC_MAGICDECOY ) { + for( j = mob_class; j <= 2046; j++ ) + map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, j, skill_id, &c); + } else + map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); + if( c >= maxcount ) { + clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + } + break; + case KO_ZANZOU: { + int c = 0; + i = map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, 2308, skill_id, &c); + if( c >= skill_get_maxcount(skill_id,skill_lv) || c != i) + { + clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + break; + } + + status = &sd->battle_status; + + require = skill_get_requirement(sd,skill_id,skill_lv); + + if( require.hp > 0 && status->hp <= (unsigned int)require.hp) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + return 0; + } + + if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + + if( require.ammo ) { //Skill requires stuff equipped in the arrow slot. + if((i=sd->equip_index[EQI_AMMO]) < 0 || !sd->inventory_data[i] ) { + clif_arrow_fail(sd,0); + return 0; + } else if( sd->status.inventory[i].amount < require.ammo_qty ) { + char e_msg[100]; + sprintf(e_msg,"Skill Failed. [%s] requires %dx %s.", + skill_get_desc(skill_id), + require.ammo_qty, + itemdb_jname(sd->status.inventory[i].nameid)); + clif_colormes(sd,COLOR_RED,e_msg); + return 0; + } + if (!(require.ammo&1<<sd->inventory_data[i]->look)) { //Ammo type check. Send the "wrong weapon type" message + //which is the closest we have to wrong ammo type. [Skotlex] + clif_arrow_fail(sd,0); //Haplo suggested we just send the equip-arrows message instead. [Skotlex] + //clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + } + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { + if( !require.itemid[i] ) + continue; + index[i] = pc_search_inventory(sd,require.itemid[i]); + if( index[i] < 0 || sd->status.inventory[index[i]].amount < require.amount[i] ) { + if( require.itemid[i] == ITEMID_RED_GEMSTONE ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_REDJAMSTONE,0);// red gemstone required + else if( require.itemid[i] == ITEMID_BLUE_GEMSTONE ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_BLUEJAMSTONE,0);// blue gemstone required + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + + return 1; +} + +// type&2: consume items (after skill was used) +// type&1: consume the others (before skill was used) +int skill_consume_requirement( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type) +{ + struct skill_condition req; + + nullpo_ret(sd); + + req = skill_get_requirement(sd,skill_id,skill_lv); + + if( type&1 ) + { + if( skill_id == CG_TAROTCARD || sd->state.autocast ) + req.sp = 0; // TarotCard will consume sp in skill_cast_nodamage_id [Inkfish] + if(req.hp || req.sp) + status_zap(&sd->bl, req.hp, req.sp); + + if(req.spiritball > 0) + pc_delspiritball(sd,req.spiritball,0); + + if(req.zeny > 0) + { + if( skill_id == NJ_ZENYNAGE ) + req.zeny = 0; //Zeny is reduced on skill_attack. + if( sd->status.zeny < req.zeny ) + req.zeny = sd->status.zeny; + pc_payzeny(sd,req.zeny,LOG_TYPE_CONSUME,NULL); + } + } + + if( type&2 ) + { + struct status_change *sc = &sd->sc; + int n,i; + + if( !sc->count ) + sc = NULL; + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) + { + if( !req.itemid[i] ) + continue; + + if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) + continue; //Gemstones are checked, but not substracted from inventory. + + switch( skill_id ){ + case SA_SEISMICWEAPON: + if( sc && sc->data[SC_UPHEAVAL_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_FLAMELAUNCHER: + case SA_VOLCANO: + if( sc && sc->data[SC_TROPIC_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_FROSTWEAPON: + case SA_DELUGE: + if( sc && sc->data[SC_CHILLY_AIR_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_LIGHTNINGLOADER: + case SA_VIOLENTGALE: + if( sc && sc->data[SC_WILD_STORM_OPTION] && rnd()%100 < 50 ) + continue; + break; + } + + if( (n = pc_search_inventory(sd,req.itemid[i])) >= 0 ) + pc_delitem(sd,n,req.amount[i],0,1,LOG_TYPE_CONSUME); + } + } + + return 1; +} + +struct skill_condition skill_get_requirement(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) +{ + struct skill_condition req; + struct status_data *status; + struct status_change *sc; + int i,hp_rate,sp_rate, sp_skill_rate_bonus = 100; + uint16 idx; + + memset(&req,0,sizeof(req)); + + if( !sd ) + return req; + + if( sd->skillitem == skill_id ) + return req; // Item skills and Hocus-Pocus don't have requirements.[Inkfish] + + sc = &sd->sc; + if( !sc->count ) + sc = NULL; + + switch( skill_id ) + { // Turn off check. + case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: + case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: + case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: + case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case KO_YAMIKUMO: + if( sc && sc->data[status_skill2sc(skill_id)] ) + return req; + } + + idx = skill_get_index(skill_id); + if( idx == 0 ) // invalid skill id + return req; + if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) + return req; + + status = &sd->battle_status; + + req.hp = skill_db[idx].hp[skill_lv-1]; + hp_rate = skill_db[idx].hp_rate[skill_lv-1]; + if(hp_rate > 0) + req.hp += (status->hp * hp_rate)/100; + else + req.hp += (status->max_hp * (-hp_rate))/100; + + req.sp = skill_db[idx].sp[skill_lv-1]; + if((sd->skill_id_old == BD_ENCORE) && skill_id == sd->skill_id_dance) + req.sp /= 2; + sp_rate = skill_db[idx].sp_rate[skill_lv-1]; + if(sp_rate > 0) + req.sp += (status->sp * sp_rate)/100; + else + req.sp += (status->max_sp * (-sp_rate))/100; + if( sd->dsprate != 100 ) + req.sp = req.sp * sd->dsprate / 100; + + ARR_FIND(0, ARRAYLENGTH(sd->skillusesprate), i, sd->skillusesprate[i].id == skill_id); + if( i < ARRAYLENGTH(sd->skillusesprate) ) + sp_skill_rate_bonus += sd->skillusesprate[i].val; + ARR_FIND(0, ARRAYLENGTH(sd->skillusesp), i, sd->skillusesp[i].id == skill_id); + if( i < ARRAYLENGTH(sd->skillusesp) ) + req.sp -= sd->skillusesp[i].val; + + req.sp = cap_value(req.sp * sp_skill_rate_bonus / 100, 0, SHRT_MAX); + + if( sc ) { + if( sc->data[SC__LAZINESS] ) + req.sp += req.sp + sc->data[SC__LAZINESS]->val1 * 10; + if (sc->data[SC_UNLIMITEDHUMMINGVOICE]) + req.sp += req.sp * sc->data[SC_UNLIMITEDHUMMINGVOICE]->val2 / 100; + if( sc->data[SC_RECOGNIZEDSPELL] ) + req.sp += req.sp / 4; + } + + req.zeny = skill_db[idx].zeny[skill_lv-1]; + + if( sc && sc->data[SC__UNLUCKY] ) + req.zeny += sc->data[SC__UNLUCKY]->val1 * 500; + + req.spiritball = skill_db[idx].spiritball[skill_lv-1]; + + req.state = skill_db[idx].state; + + req.mhp = skill_db[idx].mhp[skill_lv-1]; + + req.weapon = skill_db[idx].weapon; + + req.ammo_qty = skill_db[idx].ammo_qty[skill_lv-1]; + if (req.ammo_qty) + req.ammo = skill_db[idx].ammo; + + if (!req.ammo && skill_id && skill_isammotype(sd, skill_id)) + { //Assume this skill is using the weapon, therefore it requires arrows. + req.ammo = 0xFFFFFFFF; //Enable use on all ammo types. + req.ammo_qty = 1; + } + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++ ) { + if( (skill_id == AM_POTIONPITCHER || skill_id == CR_SLIMPITCHER || skill_id == CR_CULTIVATION) && i != skill_lv%11 - 1 ) + continue; + + switch( skill_id ) { + case AM_CALLHOMUN: + if (sd->status.hom_id) //Don't delete items when hom is already out. + continue; + break; + case NC_SHAPESHIFT: + if( i < 4 ) + continue; + break; + case WZ_FIREPILLAR: // celest + if (skill_lv <= 5) // no gems required at level 1-5 + continue; + break; + case AB_ADORAMUS: + if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 2) ) + continue; + break; + case WL_COMET: + if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 0) ) + continue; + break; + case GN_FIRE_EXPANSION: + if( i < 5 ) + continue; + break; + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + case SO_WATER_INSIGNIA: + case SO_FIRE_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + if( i < 3 ) + continue; + break; + } + + req.itemid[i] = skill_db[idx].itemid[i]; + req.amount[i] = skill_db[idx].amount[i]; + + if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN ) + { + if( sd->special_state.no_gemstone ) + { //Make it substract 1 gem rather than skipping the cost. + if( --req.amount[i] < 1 ) + req.itemid[i] = 0; + } + if(sc && sc->data[SC_INTOABYSS]) + { + if( skill_id != SA_ABRACADABRA ) + req.itemid[i] = req.amount[i] = 0; + else if( --req.amount[i] < 1 ) + req.amount[i] = 1; // Hocus Pocus allways use at least 1 gem + } + } + if( skill_id >= HT_SKIDTRAP && skill_id <= HT_TALKIEBOX && pc_checkskill(sd, RA_RESEARCHTRAP) > 0){ + int16 itIndex; + if( (itIndex = pc_search_inventory(sd,req.itemid[i])) < 0 || ( itIndex >= 0 && sd->status.inventory[itIndex].amount < req.amount[i] ) ){ + req.itemid[i] = ITEMID_TRAP_ALLOY; + req.amount[i] = 1; + } + break; + } + } + + /* requirements are level-dependent */ + switch( skill_id ) { + case NC_SHAPESHIFT: + case GN_FIRE_EXPANSION: + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + case SO_WATER_INSIGNIA: + case SO_FIRE_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + req.itemid[skill_lv-1] = skill_db[idx].itemid[skill_lv-1]; + req.amount[skill_lv-1] = skill_db[idx].amount[skill_lv-1]; + break; + } + + // Check for cost reductions due to skills & SCs + switch(skill_id) { + case MC_MAMMONITE: + if(pc_checkskill(sd,BS_UNFAIRLYTRICK)>0) + req.zeny -= req.zeny*10/100; + break; + case AL_HOLYLIGHT: + if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_PRIEST) + req.sp *= 5; + break; + case SL_SMA: + case SL_STUN: + case SL_STIN: + { + int kaina_lv = pc_checkskill(sd,SL_KAINA); + + if(kaina_lv==0 || sd->status.base_level<70) + break; + if(sd->status.base_level>=90) + req.sp -= req.sp*7*kaina_lv/100; + else if(sd->status.base_level>=80) + req.sp -= req.sp*5*kaina_lv/100; + else if(sd->status.base_level>=70) + req.sp -= req.sp*3*kaina_lv/100; + } + break; + case MO_TRIPLEATTACK: + case MO_CHAINCOMBO: + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) + req.sp -= req.sp*25/100; //FIXME: Need real data. this is a custom value. + break; + case MO_BODYRELOCATION: + if( sc && sc->data[SC_EXPLOSIONSPIRITS] ) + req.spiritball = 0; + break; + case MO_EXTREMITYFIST: + if( sc ) + { + if( sc->data[SC_BLADESTOP] ) + req.spiritball--; + else if( sc->data[SC_COMBO] ) + { + switch( sc->data[SC_COMBO]->val1 ) + { + case MO_COMBOFINISH: + req.spiritball = 4; + break; + case CH_TIGERFIST: + req.spiritball = 3; + break; + case CH_CHAINCRUSH: //It should consume whatever is left as long as it's at least 1. + req.spiritball = sd->spiritball?sd->spiritball:1; + break; + } + }else if( sc->data[SC_RAISINGDRAGON] && sd->spiritball > 5) + req.spiritball = sd->spiritball; // must consume all regardless of the amount required + } + break; + case SR_RAMPAGEBLASTER: + req.spiritball = sd->spiritball?sd->spiritball:15; + break; + case SR_GATEOFHELL: + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) + req.sp -= req.sp * 10 / 100; + break; + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + req.sp -= req.sp * (5 + 5 * pc_checkskill(sd,SO_EL_SYMPATHY)) / 100; + break; + case SO_PSYCHIC_WAVE: + if( sc && sc->data[SC_BLAST_OPTION] ) + req.sp += req.sp * 150 / 100; + break; + } + + return req; +} + +/*========================================== + * Does cast-time reductions based on dex, item bonuses and config setting + *------------------------------------------*/ +int skill_castfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) { + int time = skill_get_cast(skill_id, skill_lv); + + nullpo_ret(bl); +#ifndef RENEWAL_CAST + { + struct map_session_data *sd; + + sd = BL_CAST(BL_PC, bl); + + // calculate base cast time (reduced by dex) + if( !(skill_get_castnodex(skill_id, skill_lv)&1) ) { + int scale = battle_config.castrate_dex_scale - status_get_dex(bl); + if( scale > 0 ) // not instant cast + time = time * scale / battle_config.castrate_dex_scale; + else + return 0; // instant cast + } + + // calculate cast time reduced by item/card bonuses + if( !(skill_get_castnodex(skill_id, skill_lv)&4) && sd ) + { + int i; + if( sd->castrate != 100 ) + time = time * sd->castrate / 100; + for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) + { + if( sd->skillcast[i].id == skill_id ) + { + time+= time * sd->skillcast[i].val / 100; + break; + } + } + } + + } +#endif + // config cast time multiplier + if (battle_config.cast_rate != 100) + time = time * battle_config.cast_rate / 100; + // return final cast time + time = max(time, 0); + +// ShowInfo("Castime castfix = %d\n",time); + return time; +} + +/*========================================== + * Does cast-time reductions based on sc data. + *------------------------------------------*/ +int skill_castfix_sc (struct block_list *bl, int time) +{ + struct status_change *sc = status_get_sc(bl); + + if( time < 0 ) + return 0; + + if (sc && sc->count) { + if (sc->data[SC_SLOWCAST]) + time += time * sc->data[SC_SLOWCAST]->val2 / 100; + if (sc->data[SC_PARALYSIS]) + time += sc->data[SC_PARALYSIS]->val3; + if (sc->data[SC_SUFFRAGIUM]) { + time -= time * sc->data[SC_SUFFRAGIUM]->val2 / 100; + status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); + } + if (sc->data[SC_MEMORIZE]) { + time>>=1; + if ((--sc->data[SC_MEMORIZE]->val2) <= 0) + status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); + } + if (sc->data[SC_POEMBRAGI]) + time -= time * sc->data[SC_POEMBRAGI]->val2 / 100; + if (sc->data[SC_IZAYOI]) + time -= time * 50 / 100; + } + time = max(time, 0); + +// ShowInfo("Castime castfix_sc = %d\n",time); + return time; +} +#ifdef RENEWAL_CAST +int skill_vfcastfix (struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv) +{ + struct status_change *sc = status_get_sc(bl); + struct map_session_data *sd = BL_CAST(BL_PC,bl); + int fixed = skill_get_fixed_cast(skill_id, skill_lv), fixcast_r = 0, varcast_r = 0, i = 0; + + if( time < 0 ) + return 0; + + if( fixed == 0 ){ + fixed = (int)time * 20 / 100; // fixed time + time = time * 80 / 100; // variable time + }else if( fixed < 0 ) // no fixed cast time + fixed = 0; + + if(sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ // Increases/Decreases fixed/variable cast time of a skill by item/card bonuses. + if( sd->bonus.varcastrate < 0 ) + VARCAST_REDUCTION(sd->bonus.varcastrate); + for (i = 0; i < ARRAYLENGTH(sd->skillfixcast) && sd->skillfixcast[i].id; i++) + if (sd->skillfixcast[i].id == skill_id){ // bonus2 bSkillFixedCast + fixed += sd->skillfixcast[i].val; + break; + } + for( i = 0; i < ARRAYLENGTH(sd->skillvarcast) && sd->skillvarcast[i].id; i++ ) + if( sd->skillvarcast[i].id == skill_id ){ // bonus2 bSkillVariableCast + time += sd->skillvarcast[i].val; + break; + } + for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) + if( sd->skillcast[i].id == skill_id ){ // bonus2 bVariableCastrate + if( (i=sd->skillcast[i].val) < 0) + VARCAST_REDUCTION(i); + break; + } + } + + if (sc && sc->count && !(skill_get_castnodex(skill_id, skill_lv)&2) ) { + // All variable cast additive bonuses must come first + if (sc->data[SC_SLOWCAST]) + VARCAST_REDUCTION(-sc->data[SC_SLOWCAST]->val2); + + // Variable cast reduction bonuses + if (sc->data[SC_SUFFRAGIUM]) { + VARCAST_REDUCTION(sc->data[SC_SUFFRAGIUM]->val2); + status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); + } + if (sc->data[SC_MEMORIZE]) { + VARCAST_REDUCTION(50); + if ((--sc->data[SC_MEMORIZE]->val2) <= 0) + status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); + } + if (sc->data[SC_POEMBRAGI]) + VARCAST_REDUCTION(sc->data[SC_POEMBRAGI]->val2); + if (sc->data[SC_IZAYOI]) + VARCAST_REDUCTION(50); + if (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WATER)) + VARCAST_REDUCTION(30); //Reduces 30% Variable Cast Time of Water spells. + // Fixed cast reduction bonuses + if( sc->data[SC__LAZINESS] ) + fixcast_r = max(fixcast_r, sc->data[SC__LAZINESS]->val2); + if( sc->data[SC_SECRAMENT] ) + fixcast_r = max(fixcast_r, sc->data[SC_SECRAMENT]->val2); + if( sd && ( skill_lv = pc_checkskill(sd, WL_RADIUS) ) && skill_id >= WL_WHITEIMPRISON && skill_id <= WL_FREEZE_SP ) + fixcast_r = max(fixcast_r, 5 + skill_lv * 5); + // Fixed cast non percentage bonuses + if( sc->data[SC_MANDRAGORA] && (skill_id >= SM_BASH && skill_id <= RETURN_TO_ELDICASTES) ) + fixed += sc->data[SC_MANDRAGORA]->val1 * 1000 / 2; + if (sc->data[SC_IZAYOI] && (skill_id >= NJ_TOBIDOUGU && skill_id <= NJ_ISSEN)) + fixed = 0; + if( sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] || sc->data[SC_WILD_STORM_OPTION] ) + fixed -= 1000; + } + + if( sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ + VARCAST_REDUCTION( max(sd->bonus.varcastrate, 0) + max(i, 0) ); + fixcast_r = max(fixcast_r, sd->bonus.fixcastrate) + min(sd->bonus.fixcastrate,0); + } + + if( varcast_r < 0 ) // now compute overall factors + time = time * (1 - (float)varcast_r / 100); + if( !(skill_get_castnodex(skill_id, skill_lv)&1) )// reduction from status point + time = (1 - sqrt( ((float)(status_get_dex(bl)*2 + status_get_int(bl)) / battle_config.vcast_stat_scale) )) * time; + // underflow checking/capping + time = max(time, 0) + (1 - (float)min(fixcast_r, 100) / 100) * max(fixed,0); + + return (int)time; +} +#endif + +/*========================================== + * Does delay reductions based on dex/agi, sc data, item bonuses, ... + *------------------------------------------*/ +int skill_delayfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) +{ + int delaynodex = skill_get_delaynodex(skill_id, skill_lv); + int time = skill_get_delay(skill_id, skill_lv); + struct map_session_data *sd; + struct status_change *sc = status_get_sc(bl); + + nullpo_ret(bl); + sd = BL_CAST(BL_PC, bl); + + if (skill_id == SA_ABRACADABRA || skill_id == WM_RANDOMIZESPELL) + return 0; //Will use picked skill's delay. + + if (bl->type&battle_config.no_skill_delay) + return battle_config.min_skill_delay_limit; + + if (time < 0) + time = -time + status_get_amotion(bl); // If set to <0, add to attack motion. + + // Delay reductions + switch (skill_id) { //Monk combo skills have their delay reduced by agi/dex. + case MO_TRIPLEATTACK: + case MO_CHAINCOMBO: + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + case SR_DRAGONCOMBO: + case SR_FALLENEMPIRE: + time -= 4*status_get_agi(bl) - 2*status_get_dex(bl); + break; + case HP_BASILICA: + if( sc && !sc->data[SC_BASILICA] ) + time = 0; // There is no Delay on Basilica creation, only on cancel + break; + default: + if (battle_config.delay_dependon_dex && !(delaynodex&1)) + { // if skill delay is allowed to be reduced by dex + int scale = battle_config.castrate_dex_scale - status_get_dex(bl); + if (scale > 0) + time = time * scale / battle_config.castrate_dex_scale; + else //To be capped later to minimum. + time = 0; + } + if (battle_config.delay_dependon_agi && !(delaynodex&1)) + { // if skill delay is allowed to be reduced by agi + int scale = battle_config.castrate_dex_scale - status_get_agi(bl); + if (scale > 0) + time = time * scale / battle_config.castrate_dex_scale; + else //To be capped later to minimum. + time = 0; + } + } + + if ( sc && sc->data[SC_SPIRIT] ) + { + switch (skill_id) { + case CR_SHIELDBOOMERANG: + if (sc->data[SC_SPIRIT]->val2 == SL_CRUSADER) + time /= 2; + break; + case AS_SONICBLOW: + if (!map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && sc->data[SC_SPIRIT]->val2 == SL_ASSASIN) + time /= 2; + break; + } + } + + if (!(delaynodex&2)) + { + if (sc && sc->count) { + if (sc->data[SC_POEMBRAGI]) + time -= time * sc->data[SC_POEMBRAGI]->val3 / 100; + if (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WIND)) + time /= 2; // After Delay of Wind element spells reduced by 50%. + } + + } + + if( !(delaynodex&4) && sd && sd->delayrate != 100 ) + time = time * sd->delayrate / 100; + + if (battle_config.delay_rate != 100) + time = time * battle_config.delay_rate / 100; + + //min delay + time = max(time, status_get_amotion(bl)); // Delay can never be below amotion [Playtester] + time = max(time, battle_config.min_skill_delay_limit); + +// ShowInfo("Delay delayfix = %d\n",time); + return time; +} + +/*========================================= + * + *-----------------------------------------*/ +struct square { + int val1[5]; + int val2[5]; +}; + +static void skill_brandishspear_first (struct square *tc, uint8 dir, int16 x, int16 y) +{ + nullpo_retv(tc); + + if(dir == 0){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y-1; + } + else if(dir==2){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x+1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==4){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y+1; + } + else if(dir==6){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x-1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==1){ + tc->val1[0]=x-1; + tc->val1[1]=x; + tc->val1[2]=x+1; + tc->val1[3]=x+2; + tc->val1[4]=x+3; + tc->val2[0]=y-4; + tc->val2[1]=y-3; + tc->val2[2]=y-1; + tc->val2[3]=y; + tc->val2[4]=y+1; + } + else if(dir==3){ + tc->val1[0]=x+3; + tc->val1[1]=x+2; + tc->val1[2]=x+1; + tc->val1[3]=x; + tc->val1[4]=x-1; + tc->val2[0]=y-1; + tc->val2[1]=y; + tc->val2[2]=y+1; + tc->val2[3]=y+2; + tc->val2[4]=y+3; + } + else if(dir==5){ + tc->val1[0]=x+1; + tc->val1[1]=x; + tc->val1[2]=x-1; + tc->val1[3]=x-2; + tc->val1[4]=x-3; + tc->val2[0]=y+3; + tc->val2[1]=y+2; + tc->val2[2]=y+1; + tc->val2[3]=y; + tc->val2[4]=y-1; + } + else if(dir==7){ + tc->val1[0]=x-3; + tc->val1[1]=x-2; + tc->val1[2]=x-1; + tc->val1[3]=x; + tc->val1[4]=x+1; + tc->val2[1]=y; + tc->val2[0]=y+1; + tc->val2[2]=y-1; + tc->val2[3]=y-2; + tc->val2[4]=y-3; + } + +} + +static void skill_brandishspear_dir (struct square* tc, uint8 dir, int are) +{ + int c; + nullpo_retv(tc); + + for( c = 0; c < 5; c++ ) + { + switch( dir ) + { + case 0: tc->val2[c]+=are; break; + case 1: tc->val1[c]-=are; tc->val2[c]+=are; break; + case 2: tc->val1[c]-=are; break; + case 3: tc->val1[c]-=are; tc->val2[c]-=are; break; + case 4: tc->val2[c]-=are; break; + case 5: tc->val1[c]+=are; tc->val2[c]-=are; break; + case 6: tc->val1[c]+=are; break; + case 7: tc->val1[c]+=are; tc->val2[c]+=are; break; + } + } +} + +void skill_brandishspear(struct block_list* src, struct block_list* bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + int c,n=4; + uint8 dir = map_calc_dir(src,bl->x,bl->y); + struct square tc; + int x=bl->x,y=bl->y; + skill_brandishspear_first(&tc,dir,x,y); + skill_brandishspear_dir(&tc,dir,4); + skill_area_temp[1] = bl->id; + + if(skill_lv > 9){ + for(c=1;c<4;c++){ + map_foreachincell(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + } + } + if(skill_lv > 6){ + skill_brandishspear_dir(&tc,dir,-1); + n--; + }else{ + skill_brandishspear_dir(&tc,dir,-2); + n-=2; + } + + if(skill_lv > 3){ + for(c=0;c<5;c++){ + map_foreachincell(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + if(skill_lv > 6 && n==3 && c==4){ + skill_brandishspear_dir(&tc,dir,-1); + n--;c=-1; + } + } + } + for(c=0;c<10;c++){ + if(c==0||c==5) skill_brandishspear_dir(&tc,dir,-1); + map_foreachincell(skill_area_sub, + bl->m,tc.val1[c%5],tc.val2[c%5],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } +} + +/*========================================== + * Weapon Repair [Celest/DracoRPG] + *------------------------------------------*/ +void skill_repairweapon (struct map_session_data *sd, int idx) { + int material; + int materials[4] = { 1002, 998, 999, 756 }; + struct item *item; + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if ( !( target_sd = map_id2sd(sd->menuskill_val) ) ) //Failed.... + return; + + if( idx == 0xFFFF ) // No item selected ('Cancel' clicked) + return; + if( idx < 0 || idx >= MAX_INVENTORY ) + return; //Invalid index?? + + item = &target_sd->status.inventory[idx]; + if( item->nameid <= 0 || item->attribute == 0 ) + return; //Again invalid item.... + + if( sd != target_sd && !battle_check_range(&sd->bl,&target_sd->bl, skill_get_range2(&sd->bl, sd->menuskill_id,sd->menuskill_val2) ) ){ + clif_item_repaireffect(sd,idx,1); + return; + } + + if ( target_sd->inventory_data[idx]->type == IT_WEAPON ) + material = materials [ target_sd->inventory_data[idx]->wlv - 1 ]; // Lv1/2/3/4 weapons consume 1 Iron Ore/Iron/Steel/Rough Oridecon + else + material = materials [2]; // Armors consume 1 Steel + if ( pc_search_inventory(sd,material) < 0 ) { + clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); + return; + } + + clif_skill_nodamage(&sd->bl,&target_sd->bl,sd->menuskill_id,1,1); + + item->attribute = 0;/* clear broken state */ + + clif_equiplist(target_sd); + + pc_delitem(sd,pc_search_inventory(sd,material),1,0,0,LOG_TYPE_CONSUME); + + clif_item_repaireffect(sd,idx,0); + + if( sd != target_sd ) + clif_item_repaireffect(target_sd,idx,0); +} + +/*========================================== + * Item Appraisal + *------------------------------------------*/ +void skill_identify (struct map_session_data *sd, int idx) +{ + int flag=1; + + nullpo_retv(sd); + + if(idx >= 0 && idx < MAX_INVENTORY) { + if(sd->status.inventory[idx].nameid > 0 && sd->status.inventory[idx].identify == 0 ){ + flag=0; + sd->status.inventory[idx].identify=1; + } + } + clif_item_identified(sd,idx,flag); +} + +/*========================================== + * Weapon Refine [Celest] + *------------------------------------------*/ +void skill_weaponrefine (struct map_session_data *sd, int idx) +{ + nullpo_retv(sd); + + if (idx >= 0 && idx < MAX_INVENTORY) + { + int i = 0, ep = 0, per; + int material[5] = { 0, 1010, 1011, 984, 984 }; + struct item *item; + struct item_data *ditem = sd->inventory_data[idx]; + item = &sd->status.inventory[idx]; + + if(item->nameid > 0 && ditem->type == IT_WEAPON) + { + if( item->refine >= sd->menuskill_val + || item->refine >= 10 // if it's no longer refineable + || ditem->flag.no_refine // if the item isn't refinable + || (i = pc_search_inventory(sd, material [ditem->wlv])) < 0 ) + { + clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); + return; + } + + per = status_get_refine_chance(ditem->wlv, (int)item->refine); + per += (((signed int)sd->status.job_level)-50)/2; //Updated per the new kro descriptions. [Skotlex] + + pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); + if (per > rnd() % 100) { + log_pick_pc(sd, LOG_TYPE_OTHER, -1, item); + item->refine++; + log_pick_pc(sd, LOG_TYPE_OTHER, 1, item); + if(item->equip) { + ep = item->equip; + pc_unequipitem(sd,idx,3); + } + clif_refine(sd->fd,0,idx,item->refine); + clif_delitem(sd,idx,1,3); + clif_additem(sd,idx,1,0); + if (ep) + pc_equipitem(sd,idx,ep); + clif_misceffect(&sd->bl,3); + if(item->refine == 10 && + item->card[0] == CARD0_FORGE && + (int)MakeDWord(item->card[2],item->card[3]) == sd->status.char_id) + { // Fame point system [DracoRPG] + switch(ditem->wlv){ + case 1: + pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point + break; + case 2: + pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point + break; + case 3: + pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point + break; + } + } + } else { + item->refine = 0; + if(item->equip) + pc_unequipitem(sd,idx,3); + clif_refine(sd->fd,1,idx,item->refine); + pc_delitem(sd,idx,1,0,2, LOG_TYPE_OTHER); + clif_misceffect(&sd->bl,2); + clif_emotion(&sd->bl, E_OMG); + } + } + } +} + +/*========================================== + * + *------------------------------------------*/ +int skill_autospell (struct map_session_data *sd, uint16 skill_id) +{ + uint16 skill_lv; + int maxlv=1,lv; + + nullpo_ret(sd); + + skill_lv = sd->menuskill_val; + lv=pc_checkskill(sd,skill_id); + + if(!skill_lv || !lv) return 0; // Player must learn the skill before doing auto-spell [Lance] + + if(skill_id==MG_NAPALMBEAT) maxlv=3; + else if(skill_id==MG_COLDBOLT || skill_id==MG_FIREBOLT || skill_id==MG_LIGHTNINGBOLT){ + if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SAGE) + maxlv =10; //Soul Linker bonus. [Skotlex] + else if(skill_lv==2) maxlv=1; + else if(skill_lv==3) maxlv=2; + else if(skill_lv>=4) maxlv=3; + } + else if(skill_id==MG_SOULSTRIKE){ + if(skill_lv==5) maxlv=1; + else if(skill_lv==6) maxlv=2; + else if(skill_lv>=7) maxlv=3; + } + else if(skill_id==MG_FIREBALL){ + if(skill_lv==8) maxlv=1; + else if(skill_lv>=9) maxlv=2; + } + else if(skill_id==MG_FROSTDIVER) maxlv=1; + else return 0; + + if(maxlv > lv) + maxlv = lv; + + sc_start4(&sd->bl,SC_AUTOSPELL,100,skill_lv,skill_id,maxlv,0, + skill_get_time(SA_AUTOSPELL,skill_lv)); + return 0; +} + +/*========================================== + * Sitting skills functions. + *------------------------------------------*/ +static int skill_sit_count (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + sd=(struct map_session_data*)bl; + + if(!pc_issit(sd)) + return 0; + + if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) + return 1; + + if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0)) + return 1; + + return 0; +} + +static int skill_sit_in (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + + sd=(struct map_session_data*)bl; + + if(!pc_issit(sd)) + return 0; + + if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) + sd->state.gangsterparadise=1; + + if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0 )) + { + sd->state.rest=1; + status_calc_regen(bl, &sd->battle_status, &sd->regen); + status_calc_regen_rate(bl, &sd->regen, &sd->sc); + } + + return 0; +} + +static int skill_sit_out (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + sd=(struct map_session_data*)bl; + if(sd->state.gangsterparadise && type&1) + sd->state.gangsterparadise=0; + if(sd->state.rest && type&2) { + sd->state.rest=0; + status_calc_regen(bl, &sd->battle_status, &sd->regen); + status_calc_regen_rate(bl, &sd->regen, &sd->sc); + } + return 0; +} + +int skill_sit (struct map_session_data *sd, int type) +{ + int flag = 0; + int range = 0, lv; + nullpo_ret(sd); + + + if((lv = pc_checkskill(sd,RG_GANGSTER)) > 0) { + flag|=1; + range = skill_get_splash(RG_GANGSTER, lv); + } + if((lv = pc_checkskill(sd,TK_HPTIME)) > 0) { + flag|=2; + range = skill_get_splash(TK_HPTIME, lv); + } + else if ((lv = pc_checkskill(sd,TK_SPTIME)) > 0) { + flag|=2; + range = skill_get_splash(TK_SPTIME, lv); + } + + if( type ) { + clif_status_load(&sd->bl,SI_SIT,1); + } else { + clif_status_load(&sd->bl,SI_SIT,0); + } + + if (!flag) return 0; + + if(type) { + if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) > 1) + map_foreachinrange(skill_sit_in,&sd->bl, range, BL_PC, flag); + } else { + if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) < 2) + map_foreachinrange(skill_sit_out,&sd->bl, range, BL_PC, flag); + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_frostjoke_scream (struct block_list *bl, va_list ap) +{ + struct block_list *src; + uint16 skill_id,skill_lv; + unsigned int tick; + + nullpo_ret(bl); + nullpo_ret(src=va_arg(ap,struct block_list*)); + + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + if(!skill_lv) return 0; + tick=va_arg(ap,unsigned int); + + if (src == bl || status_isdead(bl)) + return 0; + if (bl->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)bl; + if ( sd && sd->sc.option&(OPTION_INVISIBLE|OPTION_MADOGEAR) ) + return 0;//Frost Joke / Scream cannot target invisible or MADO Gear characters [Ind] + } + //It has been reported that Scream/Joke works the same regardless of woe-setting. [Skotlex] + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); + else if(battle_check_target(src,bl,BCT_PARTY) > 0 && rnd()%100 < 10) + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static void skill_unitsetmapcell (struct skill_unit *src, uint16 skill_id, uint16 skill_lv, cell_t cell, bool flag) +{ + int range = skill_get_unit_range(skill_id,skill_lv); + int x,y; + + for( y = src->bl.y - range; y <= src->bl.y + range; ++y ) + for( x = src->bl.x - range; x <= src->bl.x + range; ++x ) + map_setcell(src->bl.m, x, y, cell, flag); +} + +/*========================================== + * + *------------------------------------------*/ +int skill_attack_area (struct block_list *bl, va_list ap) +{ + struct block_list *src,*dsrc; + int atk_type,skill_id,skill_lv,flag,type; + unsigned int tick; + + if(status_isdead(bl)) + return 0; + + atk_type = va_arg(ap,int); + src=va_arg(ap,struct block_list*); + dsrc=va_arg(ap,struct block_list*); + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + type=va_arg(ap,int); + + + if (skill_area_temp[1] == bl->id) //This is the target of the skill, do a full attack and skip target checks. + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); + + if(battle_check_target(dsrc,bl,type) <= 0 || + !status_check_skilluse(NULL, bl, skill_id, 2)) + return 0; + + + switch (skill_id) { + case WZ_FROSTNOVA: //Skills that don't require the animation to be removed + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_THUNDERBREATH: + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); + default: + //Area-splash, disable skill animation. + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + } +} +/*========================================== + * + *------------------------------------------*/ +int skill_clear_group (struct block_list *bl, int flag) +{ + struct unit_data *ud = unit_bl2ud(bl); + struct skill_unit_group *group[MAX_SKILLUNITGROUP]; + int i, count=0; + + nullpo_ret(bl); + if (!ud) return 0; + + //All groups to be deleted are first stored on an array since the array elements shift around when you delete them. [Skotlex] + for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) + { + switch (ud->skillunit[i]->skill_id) { + case SA_DELUGE: + case SA_VOLCANO: + case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: + case NJ_SUITON: + case NJ_KAENSIN: + if (flag&1) + group[count++]= ud->skillunit[i]; + break; + case SO_WARMER: + if( flag&8 ) + group[count++]= ud->skillunit[i]; + break; + case SC_BLOODYLUST: + if (flag & 32) + group[count++] = ud->skillunit[i]; + break; + default: + if (flag&2 && skill_get_inf2(ud->skillunit[i]->skill_id)&INF2_TRAP) + group[count++]= ud->skillunit[i]; + break; + } + + } + for (i=0;i<count;i++) + skill_delunitgroup(group[i]); + return count; +} + +/*========================================== + * Returns the first element field found [Skotlex] + *------------------------------------------*/ +struct skill_unit_group *skill_locate_element_field(struct block_list *bl) +{ + struct unit_data *ud = unit_bl2ud(bl); + int i; + nullpo_ret(bl); + if (!ud) return NULL; + + for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) { + switch (ud->skillunit[i]->skill_id) { + case SA_DELUGE: + case SA_VOLCANO: + case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: + case NJ_SUITON: + case SO_WARMER: + case SC_BLOODYLUST: + return ud->skillunit[i]; + } + } + return NULL; +} + +// for graffiti cleaner [Valaris] +int skill_graffitiremover (struct block_list *bl, va_list ap) +{ + struct skill_unit *unit=NULL; + + nullpo_ret(bl); + nullpo_ret(ap); + + if(bl->type!=BL_SKILL || (unit=(struct skill_unit *)bl) == NULL) + return 0; + + if((unit->group) && (unit->group->unit_id == UNT_GRAFFITI)) + skill_delunit(unit); + + return 0; +} + +int skill_greed (struct block_list *bl, va_list ap) +{ + struct block_list *src; + struct map_session_data *sd=NULL; + struct flooritem_data *fitem=NULL; + + nullpo_ret(bl); + nullpo_ret(src = va_arg(ap, struct block_list *)); + + if(src->type == BL_PC && (sd=(struct map_session_data *)src) && bl->type==BL_ITEM && (fitem=(struct flooritem_data *)bl)) + pc_takeitem(sd, fitem); + + return 0; +} +//For Ranger's Detonator [Jobbie/3CeAM] +int skill_detonator(struct block_list *bl, va_list ap) +{ + struct skill_unit *unit=NULL; + struct block_list *src; + int unit_id; + + nullpo_ret(bl); + nullpo_ret(ap); + src = va_arg(ap,struct block_list *); + + if( bl->type != BL_SKILL || (unit = (struct skill_unit *)bl) == NULL || !unit->group ) + return 0; + if( unit->group->src_id != src->id ) + return 0; + + unit_id = unit->group->unit_id; + switch( unit_id ) + { //List of Hunter and Ranger Traps that can be detonate. + case UNT_BLASTMINE: + case UNT_SANDMAN: + case UNT_CLAYMORETRAP: + case UNT_TALKIEBOX: + case UNT_CLUSTERBOMB: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + if( unit_id == UNT_TALKIEBOX ) + { + clif_talkiebox(bl,unit->group->valstr); + unit->group->val2 = -1; + } + else + map_foreachinrange(skill_trap_splash,bl,skill_get_splash(unit->group->skill_id,unit->group->skill_lv),unit->group->bl_flag,bl,unit->group->tick); + + clif_changetraplook(bl,unit_id == UNT_FIRINGTRAP ? UNT_DUMMYSKILL : UNT_USED_TRAPS); + unit->group->unit_id = UNT_USED_TRAPS; + unit->group->limit = DIFF_TICK(gettick(),unit->group->tick) + + (unit_id == UNT_TALKIEBOX ? 5000 : (unit_id == UNT_CLUSTERBOMB || unit_id == UNT_ICEBOUNDTRAP? 2500 : 1500) ); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_cell_overlap(struct block_list *bl, va_list ap) +{ + uint16 skill_id; + int *alive; + struct skill_unit *unit; + + skill_id = va_arg(ap,int); + alive = va_arg(ap,int *); + unit = (struct skill_unit *)bl; + + if (unit == NULL || unit->group == NULL || (*alive) == 0) + return 0; + + switch (skill_id) { + case SA_LANDPROTECTOR: + if( unit->group->skill_id == SA_LANDPROTECTOR ) {//Check for offensive Land Protector to delete both. [Skotlex] + (*alive) = 0; + skill_delunit(unit); + return 1; + } + if( !(skill_get_inf2(unit->group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP)) ) { //It deletes everything except songs/dances and traps + skill_delunit(unit); + return 1; + } + break; + case HW_GANBANTEIN: + case LG_EARTHDRIVE: + if( !(unit->group->state.song_dance&0x1) ) {// Don't touch song/dance. + skill_delunit(unit); + return 1; + } + break; + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: +// The official implementation makes them fail to appear when casted on top of ANYTHING +// but I wonder if they didn't actually meant to fail when casted on top of each other? +// hence, I leave the alternate implementation here, commented. [Skotlex] + if (unit->range <= 0) + { + (*alive) = 0; + return 1; + } +/* + switch (unit->group->skill_id) + { //These cannot override each other. + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + (*alive) = 0; + return 1; + } +*/ + break; + case PF_FOGWALL: + switch(unit->group->skill_id) { + case SA_VOLCANO: //Can't be placed on top of these + case SA_VIOLENTGALE: + (*alive) = 0; + return 1; + case SA_DELUGE: + case NJ_SUITON: + //Cheap 'hack' to notify the calling function that duration should be doubled [Skotlex] + (*alive) = 2; + break; + } + break; + case HP_BASILICA: + if (unit->group->skill_id == HP_BASILICA) + { //Basilica can't be placed on top of itself to avoid map-cell stacking problems. [Skotlex] + (*alive) = 0; + return 1; + } + break; + case GN_CRAZYWEED_ATK: + switch(unit->group->unit_id){ //TODO: look for other ground skills that are affected. + case UNT_WALLOFTHORN: + case UNT_THORNS_TRAP: + case UNT_BLOODYLUST: + case UNT_CHAOSPANIC: + case UNT_MAELSTROM: + case UNT_FIREPILLAR_ACTIVE: + case UNT_LANDPROTECTOR: + case UNT_VOLCANO: + case UNT_DELUGE: + case UNT_VIOLENTGALE: + case UNT_SAFETYWALL: + case UNT_PNEUMA: + skill_delunit(unit); + return 1; + } + break; + } + + if (unit->group->skill_id == SA_LANDPROTECTOR && !(skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_TRAP))) { //It deletes everything except songs/dances/traps + (*alive) = 0; + return 1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_chastle_mob_changetarget(struct block_list *bl,va_list ap) +{ + struct mob_data* md; + struct unit_data*ud = unit_bl2ud(bl); + struct block_list *from_bl; + struct block_list *to_bl; + md = (struct mob_data*)bl; + from_bl = va_arg(ap,struct block_list *); + to_bl = va_arg(ap,struct block_list *); + + if(ud && ud->target == from_bl->id) + ud->target = to_bl->id; + + if(md->bl.type == BL_MOB && md->target_id == from_bl->id) + md->target_id = to_bl->id; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_trap_splash (struct block_list *bl, va_list ap) +{ + struct block_list *src; + int tick; + struct skill_unit *unit; + struct skill_unit_group *sg; + struct block_list *ss; + src = va_arg(ap,struct block_list *); + unit = (struct skill_unit *)src; + tick = va_arg(ap,int); + + if( !unit->alive || bl->prev == NULL ) + return 0; + + nullpo_ret(sg = unit->group); + nullpo_ret(ss = map_id2bl(sg->src_id)); + + if(battle_check_target(src,bl,sg->target_flag) <= 0) + return 0; + + switch(sg->unit_id){ + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,ATK_DEF,tick); + break; + case UNT_GROUNDDRIFT_WIND: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_STUN,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_DARK: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_BLIND,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_POISON: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_POISON,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_WATER: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_FREEZE,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_FIRE: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + skill_blown(src,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),-1,0); + break; + case UNT_ELECTRICSHOCKER: + clif_skill_damage(src,bl,tick,0,0,-30000,1,sg->skill_id,sg->skill_lv,5); + break; + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + case UNT_CLUSTERBOMB: + if( ss != bl ) + skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1|SD_LEVEL); + break; + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + if( bl->type != BL_PC && !is_boss(bl) ) + sc_start2(bl,SC_ELEMENTALCHANGE,100,sg->skill_lv,skill_get_ele(sg->skill_id,sg->skill_lv),skill_get_time2(sg->skill_id,sg->skill_lv)); + break; + case UNT_REVERBERATION: + skill_addtimerskill(ss,tick+50,bl->id,0,0,WM_REVERBERATION_MELEE,sg->skill_lv,BF_WEAPON,0); // for proper skill delay animation when use with Dominion Impulse + skill_addtimerskill(ss,tick+250,bl->id,0,0,WM_REVERBERATION_MAGIC,sg->skill_lv,BF_MAGIC,0); + break; + default: + skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + } + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_enchant_elemental_end (struct block_list *bl, int type) +{ + struct status_change *sc; + const enum sc_type scs[] = { SC_ENCPOISON, SC_ASPERSIO, SC_FIREWEAPON, SC_WATERWEAPON, SC_WINDWEAPON, SC_EARTHWEAPON, SC_SHADOWWEAPON, SC_GHOSTWEAPON, SC_ENCHANTARMS, SC_EXEEDBREAK }; + int i; + nullpo_ret(bl); + nullpo_ret(sc= status_get_sc(bl)); + + if (!sc->count) return 0; + + for (i = 0; i < ARRAYLENGTH(scs); i++) + if (type != scs[i] && sc->data[scs[i]]) + status_change_end(bl, scs[i], INVALID_TIMER); + + return 0; +} + +bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce) +{ + static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; + static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; + bool wall = true; + + if( (bl->type == BL_PC && battle_config.pc_cloak_check_type&1) + || (bl->type != BL_PC && battle_config.monster_cloak_check_type&1) ) + { //Check for walls. + int i; + ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); + if( i == 8 ) + wall = false; + } + + if( sce ) + { + if( !wall ) + { + if( sce->val1 < 3 ) //End cloaking. + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + else + if( sce->val4&1 ) + { //Remove wall bonus + sce->val4&=~1; + status_calc_bl(bl,SCB_SPEED); + } + } + else + { + if( !(sce->val4&1) ) + { //Add wall speed bonus + sce->val4|=1; + status_calc_bl(bl,SCB_SPEED); + } + } + } + + return wall; +} +bool skill_check_camouflage(struct block_list *bl, struct status_change_entry *sce) +{ + static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; + static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; + bool wall = true; + + if( bl->type == BL_PC ) + { //Check for walls. + int i; + ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); + if( i == 8 ) + wall = false; + } + + if( sce ) + { + if( !wall ) + { + if( sce->val1 < 3 ) //End camouflage. + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + else + if( sce->val3&1 ) + { //Remove wall bonus + sce->val3&=~1; + status_calc_bl(bl,SCB_SPEED); + } + } + } + + return wall; +} + +/*========================================== + * + *------------------------------------------*/ +struct skill_unit *skill_initunit (struct skill_unit_group *group, int idx, int x, int y, int val1, int val2) +{ + struct skill_unit *unit; + + nullpo_retr(NULL, group); + nullpo_retr(NULL, group->unit); // crash-protection against poor coding + nullpo_retr(NULL, unit=&group->unit[idx]); + + if(!unit->alive) + group->alive_count++; + + unit->bl.id=map_get_new_object_id(); + unit->bl.type=BL_SKILL; + unit->bl.m=group->map; + unit->bl.x=x; + unit->bl.y=y; + unit->group=group; + unit->alive=1; + unit->val1=val1; + unit->val2=val2; + + idb_put(skillunit_db, unit->bl.id, unit); + map_addiddb(&unit->bl); + map_addblock(&unit->bl); + + // perform oninit actions + switch (group->skill_id) { + case WZ_ICEWALL: + map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,5); + clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,5,AREA); + skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,true); + map[unit->bl.m].icewall_num++; + break; + case SA_LANDPROTECTOR: + skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,true); + break; + case HP_BASILICA: + skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,true); + break; + case SC_MAELSTROM: + skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,true); + break; + default: + if (group->state.song_dance&0x1) //Check for dissonance. + skill_dance_overlap(unit, 1); + break; + } + + clif_skill_setunit(unit); + + return unit; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_delunit (struct skill_unit* unit) +{ + struct skill_unit_group *group; + + nullpo_ret(unit); + if( !unit->alive ) + return 0; + unit->alive=0; + + nullpo_ret(group=unit->group); + + if( group->state.song_dance&0x1 ) //Cancel dissonance effect. + skill_dance_overlap(unit, 0); + + // invoke onout event + if( !unit->range ) + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),4); + + // perform ondelete actions + switch (group->skill_id) { + case HT_ANKLESNARE: { + struct block_list* target = map_id2bl(group->val2); + if( target ) + status_change_end(target, SC_ANKLE, INVALID_TIMER); + } + break; + case WZ_ICEWALL: + map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); + clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,ALL_SAMEMAP); // hack to avoid clientside cell bug + skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,false); + map[unit->bl.m].icewall_num--; + break; + case SA_LANDPROTECTOR: + skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,false); + break; + case HP_BASILICA: + skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,false); + break; + case RA_ELECTRICSHOCKER: { + struct block_list* target = map_id2bl(group->val2); + if( target ) + status_change_end(target, SC_ELECTRICSHOCKER, INVALID_TIMER); + } + break; + case SC_MAELSTROM: + skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,false); + break; + case SC_MANHOLE: // Note : Removing the unit don't remove the status (official info) + if( group->val2 ) { // Someone Traped + struct status_change *tsc = status_get_sc( map_id2bl(group->val2)); + if( tsc && tsc->data[SC__MANHOLE] ) + tsc->data[SC__MANHOLE]->val4 = 0; // Remove the Unit ID + } + break; + } + + clif_skill_delunit(unit); + + unit->group=NULL; + map_delblock(&unit->bl); // don't free yet + map_deliddb(&unit->bl); + idb_remove(skillunit_db, unit->bl.id); + if(--group->alive_count==0) + skill_delunitgroup(group); + + return 0; +} +/*========================================== + * + *------------------------------------------*/ +static DBMap* group_db = NULL;// int group_id -> struct skill_unit_group* + +/// Returns the target skill_unit_group or NULL if not found. +struct skill_unit_group* skill_id2group(int group_id) +{ + return (struct skill_unit_group*)idb_get(group_db, group_id); +} + + +static int skill_unit_group_newid = MAX_SKILL_DB; + +/// Returns a new group_id that isn't being used in group_db. +/// Fatal error if nothing is available. +static int skill_get_new_group_id(void) +{ + if( skill_unit_group_newid >= MAX_SKILL_DB && skill_id2group(skill_unit_group_newid) == NULL ) + return skill_unit_group_newid++;// available + {// find next id + int base_id = skill_unit_group_newid; + while( base_id != ++skill_unit_group_newid ) + { + if( skill_unit_group_newid < MAX_SKILL_DB ) + skill_unit_group_newid = MAX_SKILL_DB; + if( skill_id2group(skill_unit_group_newid) == NULL ) + return skill_unit_group_newid++;// available + } + // full loop, nothing available + ShowFatalError("skill_get_new_group_id: All ids are taken. Exiting..."); + exit(1); + } +} + +struct skill_unit_group* skill_initunitgroup (struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, int limit, int interval) +{ + struct unit_data* ud = unit_bl2ud( src ); + struct skill_unit_group* group; + int i; + + if(!(skill_id && skill_lv)) return 0; + + nullpo_retr(NULL, src); + nullpo_retr(NULL, ud); + + // find a free spot to store the new unit group + ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == NULL ); + if(i == MAX_SKILLUNITGROUP) + { + // array is full, make room by discarding oldest group + int j=0; + unsigned maxdiff=0,x,tick=gettick(); + for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) + if((x=DIFF_TICK(tick,ud->skillunit[i]->tick))>maxdiff){ + maxdiff=x; + j=i; + } + skill_delunitgroup(ud->skillunit[j]); + //Since elements must have shifted, we use the last slot. + i = MAX_SKILLUNITGROUP-1; + } + + group = ers_alloc(skill_unit_ers, struct skill_unit_group); + group->src_id = src->id; + group->party_id = status_get_party_id(src); + group->guild_id = status_get_guild_id(src); + group->bg_id = bg_team_get_id(src); + group->group_id = skill_get_new_group_id(); + group->unit = (struct skill_unit *)aCalloc(count,sizeof(struct skill_unit)); + group->unit_count = count; + group->alive_count = 0; + group->val1 = 0; + group->val2 = 0; + group->val3 = 0; + group->skill_id = skill_id; + group->skill_lv = skill_lv; + group->unit_id = unit_id; + group->map = src->m; + group->limit = limit; + group->interval = interval; + group->tick = gettick(); + group->valstr = NULL; + + ud->skillunit[i] = group; + + if (skill_id == PR_SANCTUARY) //Sanctuary starts healing +1500ms after casted. [Skotlex] + group->tick += 1500; + + idb_put(group_db, group->group_id, group); + return group; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func) +{ + struct block_list* src; + struct unit_data *ud; + int i,j; + + if( group == NULL ) + { + ShowDebug("skill_delunitgroup: group is NULL (source=%s:%d, %s)! Please report this! (#3504)\n", file, line, func); + return 0; + } + + src=map_id2bl(group->src_id); + ud = unit_bl2ud(src); + if(!src || !ud) { + ShowError("skill_delunitgroup: Group's source not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); + return 0; + } + + if( !status_isdead(src) && ((TBL_PC*)src)->state.warping && !((TBL_PC*)src)->state.changemap ) { + switch( group->skill_id ) { + case BA_DISSONANCE: + case BA_POEMBRAGI: + case BA_WHISTLE: + case BA_ASSASSINCROSS: + case BA_APPLEIDUN: + case DC_UGLYDANCE: + case DC_HUMMING: + case DC_DONTFORGETME: + case DC_FORTUNEKISS: + case DC_SERVICEFORYOU: + skill_usave_add(((TBL_PC*)src), group->skill_id, group->skill_lv); + break; + } + } + + if (skill_get_unit_flag(group->skill_id)&(UF_DANCE|UF_SONG|UF_ENSEMBLE)) + { + struct status_change* sc = status_get_sc(src); + if (sc && sc->data[SC_DANCING]) + { + sc->data[SC_DANCING]->val2 = 0 ; //This prevents status_change_end attempting to redelete the group. [Skotlex] + status_change_end(src, SC_DANCING, INVALID_TIMER); + } + } + + // end Gospel's status change on 'src' + // (needs to be done when the group is deleted by other means than skill deactivation) + if (group->unit_id == UNT_GOSPEL) { + struct status_change *sc = status_get_sc(src); + if(sc && sc->data[SC_GOSPEL]) { + sc->data[SC_GOSPEL]->val3 = 0; //Remove reference to this group. [Skotlex] + status_change_end(src, SC_GOSPEL, INVALID_TIMER); + } + } + + switch( group->skill_id ) { + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_WARM] ) { + sc->data[SC_WARM]->val4 = 0; + status_change_end(src, SC_WARM, INVALID_TIMER); + } + } + break; + case NC_NEUTRALBARRIER: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_NEUTRALBARRIER_MASTER] ) { + sc->data[SC_NEUTRALBARRIER_MASTER]->val2 = 0; + status_change_end(src,SC_NEUTRALBARRIER_MASTER,INVALID_TIMER); + } + } + break; + case NC_STEALTHFIELD: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_STEALTHFIELD_MASTER] ) { + sc->data[SC_STEALTHFIELD_MASTER]->val2 = 0; + status_change_end(src,SC_STEALTHFIELD_MASTER,INVALID_TIMER); + } + } + break; + case LG_BANDING: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) && sc->data[SC_BANDING] ) { + sc->data[SC_BANDING]->val4 = 0; + status_change_end(src,SC_BANDING,INVALID_TIMER); + } + } + break; + } + + if (src->type==BL_PC && group->state.ammo_consume) + battle_consume_ammo((TBL_PC*)src, group->skill_id, group->skill_lv); + + group->alive_count=0; + + // remove all unit cells + if(group->unit != NULL) + for( i = 0; i < group->unit_count; i++ ) + skill_delunit(&group->unit[i]); + + // clear Talkie-box string + if( group->valstr != NULL ) + { + aFree(group->valstr); + group->valstr = NULL; + } + + idb_remove(group_db, group->group_id); + map_freeblock(&group->unit->bl); // schedules deallocation of whole array (HACK) + group->unit=NULL; + group->group_id=0; + group->unit_count=0; + + // locate this group, swap with the last entry and delete it + ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == group ); + ARR_FIND( i, MAX_SKILLUNITGROUP, j, ud->skillunit[j] == NULL ); j--; + if( i < MAX_SKILLUNITGROUP ) + { + ud->skillunit[i] = ud->skillunit[j]; + ud->skillunit[j] = NULL; + ers_free(skill_unit_ers, group); + } + else + ShowError("skill_delunitgroup: Group not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_clear_unitgroup (struct block_list *src) +{ + struct unit_data *ud = unit_bl2ud(src); + + nullpo_ret(ud); + + while (ud->skillunit[0]) + skill_delunitgroup(ud->skillunit[0]); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +struct skill_unit_group_tickset *skill_unitgrouptickset_search (struct block_list *bl, struct skill_unit_group *group, int tick) +{ + int i,j=-1,k,s,id; + struct unit_data *ud; + struct skill_unit_group_tickset *set; + + nullpo_ret(bl); + if (group->interval==-1) + return NULL; + + ud = unit_bl2ud(bl); + if (!ud) return NULL; + + set = ud->skillunittick; + + if (skill_get_unit_flag(group->skill_id)&UF_NOOVERLAP) + id = s = group->skill_id; + else + id = s = group->group_id; + + for (i=0; i<MAX_SKILLUNITGROUPTICKSET; i++) { + k = (i+s) % MAX_SKILLUNITGROUPTICKSET; + if (set[k].id == id) + return &set[k]; + else if (j==-1 && (DIFF_TICK(tick,set[k].tick)>0 || set[k].id==0)) + j=k; + } + + if (j == -1) { + ShowWarning ("skill_unitgrouptickset_search: tickset is full\n"); + j = id % MAX_SKILLUNITGROUPTICKSET; + } + + set[j].id = id; + set[j].tick = tick; + return &set[j]; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_timer_sub_onplace (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = va_arg(ap,struct skill_unit *); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + + if( !unit->alive || bl->prev == NULL ) + return 0; + + nullpo_ret(group); + + if( !(skill_get_inf2(group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP|INF2_NOLP)) && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) ) + return 0; //AoE skills are ineffective. [Skotlex] + + if( battle_check_target(&unit->bl,bl,group->target_flag) <= 0 ) + return 0; + + skill_unit_onplace_timer(unit,bl,tick); + + return 1; +} + +/** + * @see DBApply + */ +static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap) +{ + struct skill_unit* unit = db_data2ptr(data); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + bool dissonance; + struct block_list* bl = &unit->bl; + + if( !unit->alive ) + return 0; + + nullpo_ret(group); + + // check for expiration + if( !group->state.guildaura && (DIFF_TICK(tick,group->tick) >= group->limit || DIFF_TICK(tick,group->tick) >= unit->limit) ) + {// skill unit expired (inlined from skill_unit_onlimit()) + switch( group->unit_id ) + { + case UNT_BLASTMINE: +#ifdef RENEWAL + case UNT_CLAYMORETRAP: +#endif + case UNT_GROUNDDRIFT_WIND: + case UNT_GROUNDDRIFT_DARK: + case UNT_GROUNDDRIFT_POISON: + case UNT_GROUNDDRIFT_WATER: + case UNT_GROUNDDRIFT_FIRE: + group->unit_id = UNT_USED_TRAPS; + //clif_changetraplook(bl, UNT_FIREPILLAR_ACTIVE); + group->limit=DIFF_TICK(tick+1500,group->tick); + unit->limit=DIFF_TICK(tick+1500,group->tick); + break; + + case UNT_ANKLESNARE: + case UNT_ELECTRICSHOCKER: + if( group->val2 > 0 ) { + // Used Trap don't returns back to item + skill_delunit(unit); + break; + } + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: +#ifndef RENEWAL + case UNT_CLAYMORETRAP: +#endif + case UNT_TALKIEBOX: + case UNT_CLUSTERBOMB: + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + + { + struct block_list* src; + if( unit->val1 > 0 && (src = map_id2bl(group->src_id)) != NULL && src->type == BL_PC ) + { // revert unit back into a trap + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = group->item_id?group->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); + } + skill_delunit(unit); + } + break; + + case UNT_WARP_ACTIVE: + // warp portal opens (morph to a UNT_WARP_WAITING cell) + group->unit_id = skill_get_unit_id(group->skill_id, 1); // UNT_WARP_WAITING + clif_changelook(&unit->bl, LOOK_BASE, group->unit_id); + // restart timers + group->limit = skill_get_time(group->skill_id,group->skill_lv); + unit->limit = skill_get_time(group->skill_id,group->skill_lv); + // apply effect to all units standing on it + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); + break; + + case UNT_CALLFAMILY: + { + struct map_session_data *sd = NULL; + if(group->val1) { + sd = map_charid2sd(group->val1); + group->val1 = 0; + if (sd && !map[sd->bl.m].flag.nowarp) + pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); + } + if(group->val2) { + sd = map_charid2sd(group->val2); + group->val2 = 0; + if (sd && !map[sd->bl.m].flag.nowarp) + pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); + } + skill_delunit(unit); + } + break; + + case UNT_REVERBERATION: + if( unit->val1 <= 0 ) { // If it was deactivated. + skill_delunit(unit); + break; + } + clif_changetraplook(bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); + group->limit = DIFF_TICK(tick,group->tick)+1000; + unit->limit = DIFF_TICK(tick,group->tick)+1000; + group->unit_id = UNT_USED_TRAPS; + break; + + case UNT_FEINTBOMB: { + struct block_list *src = map_id2bl(group->src_id); + if( src ) + map_foreachinrange(skill_area_sub, &group->unit->bl, unit->range, splash_target(src), src, SC_FEINTBOMB, group->skill_lv, tick, BCT_ENEMY|SD_ANIMATION|1, skill_castend_damage_id); + skill_delunit(unit); + break; + } + + case UNT_BANDING: + { + struct block_list *src = map_id2bl(group->src_id); + struct status_change *sc; + if( !src || (sc = status_get_sc(src)) == NULL || !sc->data[SC_BANDING] ) + { + skill_delunit(unit); + break; + } + // This unit isn't removed while SC_BANDING is active. + group->limit = DIFF_TICK(tick+group->interval,group->tick); + unit->limit = DIFF_TICK(tick+group->interval,group->tick); + } + break; + + default: + skill_delunit(unit); + } + } + else + {// skill unit is still active + switch( group->unit_id ) + { + case UNT_ICEWALL: + // icewall loses 50 hp every second + unit->val1 -= SKILLUNITTIMER_INTERVAL/20; // trap's hp + if( unit->val1 <= 0 && unit->limit + group->tick > tick + 700 ) + unit->limit = DIFF_TICK(tick+700,group->tick); + break; + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_CLAYMORETRAP: + case UNT_FREEZINGTRAP: + case UNT_TALKIEBOX: + case UNT_ANKLESNARE: + if( unit->val1 <= 0 ) { + if( group->unit_id == UNT_ANKLESNARE && group->val2 > 0 ) + skill_delunit(unit); + else { + clif_changetraplook(bl, group->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); + group->limit = DIFF_TICK(tick, group->tick) + 1500; + group->unit_id = UNT_USED_TRAPS; + } + } + break; + case UNT_REVERBERATION: + if( unit->val1 <= 0 ){ + clif_changetraplook(bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); + group->limit = DIFF_TICK(tick,group->tick)+1000; + unit->limit = DIFF_TICK(tick,group->tick)+1000; + group->unit_id = UNT_USED_TRAPS; + } + break; + case UNT_WALLOFTHORN: + if( unit->val1 <= 0 ) { + group->unit_id = UNT_USED_TRAPS; + group->limit = DIFF_TICK(tick, group->tick) + 1500; + } + break; + } + } + + //Don't continue if unit or even group is expired and has been deleted. + if( !group || !unit->alive ) + return 0; + + dissonance = skill_dance_switch(unit, 0); + + if( unit->range >= 0 && group->interval != -1 ) + { + if( battle_config.skill_wall_check ) + map_foreachinshootrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); + else + map_foreachinrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); + + if(unit->range == -1) //Unit disabled, but it should not be deleted yet. + group->unit_id = UNT_USED_TRAPS; + + if( group->unit_id == UNT_TATAMIGAESHI ) + { + unit->range = -1; //Disable processed cell. + if (--group->val1 <= 0) // number of live cells + { //All tiles were processed, disable skill. + group->target_flag=BCT_NOONE; + group->bl_flag= BL_NUL; + } + } + } + + if( dissonance ) skill_dance_switch(unit, 1); + + return 0; +} +/*========================================== + * Executes on all skill units every SKILLUNITTIMER_INTERVAL miliseconds. + *------------------------------------------*/ +int skill_unit_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + map_freeblock_lock(); + + skillunit_db->foreach(skillunit_db, skill_unit_timer_sub, tick); + + map_freeblock_unlock(); + + return 0; +} + +static int skill_unit_temp[20]; // temporary storage for tracking skill unit skill ids as players move in/out of them +/*========================================== + * + *------------------------------------------*/ +int skill_unit_move_sub (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = (struct skill_unit *)bl; + struct skill_unit_group* group = unit->group; + + struct block_list* target = va_arg(ap,struct block_list*); + unsigned int tick = va_arg(ap,unsigned int); + int flag = va_arg(ap,int); + + bool dissonance; + uint16 skill_id; + int i; + + nullpo_ret(group); + + if( !unit->alive || target->prev == NULL ) + return 0; + + if( flag&1 && ( unit->group->skill_id == PF_SPIDERWEB || unit->group->skill_id == GN_THORNS_TRAP ) ) + return 0; // Fiberlock is never supposed to trigger on skill_unit_move. [Inkfish] + + dissonance = skill_dance_switch(unit, 0); + + //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] + skill_id = unit->group->skill_id; + + if( unit->group->interval != -1 && !(skill_get_unit_flag(skill_id)&UF_DUALMODE) && skill_id != BD_LULLABY ) //Lullaby is the exception, bugreport:411 + { //Non-dualmode unit skills with a timer don't trigger when walking, so just return + if( dissonance ) skill_dance_switch(unit, 1); + return 0; + } + + //Target-type check. + if( !(group->bl_flag&target->type && battle_check_target(&unit->bl,target,group->target_flag) > 0) ) + { + if( group->src_id == target->id && group->state.song_dance&0x2 ) + { //Ensemble check to see if they went out/in of the area [Skotlex] + if( flag&1 ) + { + if( flag&2 ) + { //Clear this skill id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == skill_id ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = 0; + } + } + else + { + if( flag&2 ) + { //Store this skill id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = skill_id; + else + ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); + } + + } + + if( flag&4 ) + skill_unit_onleft(skill_id,target,tick); + } + + if( dissonance ) skill_dance_switch(unit, 1); + + return 0; + } + else + { + if( flag&1 ) + { + int result = skill_unit_onplace(unit,target,tick); + if( flag&2 && result ) + { //Clear skill ids we have stored in onout. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == result ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = 0; + } + } + else + { + int result = skill_unit_onout(unit,target,tick); + if( flag&2 && result ) + { //Store this unit id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = skill_id; + else + ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); + } + } + + //TODO: Normally, this is dangerous since the unit and group could be freed + //inside the onout/onplace functions. Currently it is safe because we know song/dance + //cells do not get deleted within them. [Skotlex] + if( dissonance ) skill_dance_switch(unit, 1); + + if( flag&4 ) + skill_unit_onleft(skill_id,target,tick); + + return 1; + } +} + +/*========================================== + * Invoked when a char has moved and unit cells must be invoked (onplace, onout, onleft) + * Flag values: + * flag&1: invoke skill_unit_onplace (otherwise invoke skill_unit_onout) + * flag&2: this function is being invoked twice as a bl moves, store in memory the affected + * units to figure out when they have left a group. + * flag&4: Force a onleft event (triggered when the bl is killed, for example) + *------------------------------------------*/ +int skill_unit_move (struct block_list *bl, unsigned int tick, int flag) +{ + nullpo_ret(bl); + + if( bl->prev == NULL ) + return 0; + + if( flag&2 && !(flag&1) ) + { //Onout, clear data + memset(skill_unit_temp, 0, sizeof(skill_unit_temp)); + } + + map_foreachincell(skill_unit_move_sub,bl->m,bl->x,bl->y,BL_SKILL,bl,tick,flag); + + if( flag&2 && flag&1 ) + { //Onplace, check any skill units you have left. + int i; + for( i = 0; i < ARRAYLENGTH(skill_unit_temp); i++ ) + if( skill_unit_temp[i] ) + skill_unit_onleft(skill_unit_temp[i], bl, tick); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_move_unit_group (struct skill_unit_group *group, int16 m, int16 dx, int16 dy) +{ + int i,j; + unsigned int tick = gettick(); + int *m_flag; + struct skill_unit *unit1; + struct skill_unit *unit2; + + if (group == NULL) + return 0; + if (group->unit_count<=0) + return 0; + if (group->unit==NULL) + return 0; + + if (skill_get_unit_flag(group->skill_id)&UF_ENSEMBLE) + return 0; //Ensembles may not be moved around. + + if( group->unit_id == UNT_ICEWALL || group->unit_id == UNT_WALLOFTHORN ) + return 0; //Icewalls and Wall of Thorns don't get knocked back + + m_flag = (int *) aCalloc(group->unit_count, sizeof(int)); + // m_flag + // 0: Neither of the following (skill_unit_onplace & skill_unit_onout are needed) + // 1: Unit will move to a slot that had another unit of the same group (skill_unit_onplace not needed) + // 2: Another unit from same group will end up positioned on this unit (skill_unit_onout not needed) + // 3: Both 1+2. + for(i=0;i<group->unit_count;i++){ + unit1=&group->unit[i]; + if (!unit1->alive || unit1->bl.m!=m) + continue; + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if (!unit2->alive) + continue; + if (unit1->bl.x+dx==unit2->bl.x && unit1->bl.y+dy==unit2->bl.y){ + m_flag[i] |= 0x1; + } + if (unit1->bl.x-dx==unit2->bl.x && unit1->bl.y-dy==unit2->bl.y){ + m_flag[i] |= 0x2; + } + } + } + j = 0; + for (i=0;i<group->unit_count;i++) { + unit1=&group->unit[i]; + if (!unit1->alive) + continue; + if (!(m_flag[i]&0x2)) { + if (group->state.song_dance&0x1) //Cancel dissonance effect. + skill_dance_overlap(unit1, 0); + map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,4); + } + //Move Cell using "smart" criteria (avoid useless moving around) + switch(m_flag[i]) + { + case 0: + //Cell moves independently, safely move it. + map_moveblock(&unit1->bl, unit1->bl.x+dx, unit1->bl.y+dy, tick); + break; + case 1: + //Cell moves unto another cell, look for a replacement cell that won't collide + //and has no cell moving into it (flag == 2) + for(;j<group->unit_count;j++) + { + if(m_flag[j]!=2 || !group->unit[j].alive) + continue; + //Move to where this cell would had moved. + unit2 = &group->unit[j]; + map_moveblock(&unit1->bl, unit2->bl.x+dx, unit2->bl.y+dy, tick); + j++; //Skip this cell as we have used it. + break; + } + break; + case 2: + case 3: + break; //Don't move the cell as a cell will end on this tile anyway. + } + if (!(m_flag[i]&0x2)) { //We only moved the cell in 0-1 + if (group->state.song_dance&0x1) //Check for dissonance effect. + skill_dance_overlap(unit1, 1); + clif_skill_setunit(unit1); + map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,1); + } + } + aFree(m_flag); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_can_produce_mix (struct map_session_data *sd, int nameid, int trigger, int qty) +{ + int i,j; + + nullpo_ret(sd); + + if(nameid<=0) + return 0; + + for(i=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if(skill_produce_db[i].nameid == nameid ){ + if((j=skill_produce_db[i].req_skill)>0 && + pc_checkskill(sd,j) < skill_produce_db[i].req_skill_lv) + continue; // must iterate again to check other skills that produce it. [malufett] + if( j > 0 && sd->menuskill_id > 0 && sd->menuskill_id != j ) + continue; // special case + break; + } + } + + if( i >= MAX_SKILL_PRODUCE_DB ) + return 0; + + if( pc_checkadditem(sd, nameid, qty) == ADDITEM_OVERAMOUNT ) + {// cannot carry the produced stuff + return 0; + } + + if(trigger>=0){ + if(trigger>20) { // Non-weapon, non-food item (itemlv must match) + if(skill_produce_db[i].itemlv!=trigger) + return 0; + } else if(trigger>10) { // Food (any item level between 10 and 20 will do) + if(skill_produce_db[i].itemlv<=10 || skill_produce_db[i].itemlv>20) + return 0; + } else { // Weapon (itemlv must be higher or equal) + if(skill_produce_db[i].itemlv>trigger) + return 0; + } + } + + for(j=0;j<MAX_PRODUCE_RESOURCE;j++){ + int id,x,y; + if( (id=skill_produce_db[i].mat_id[j]) <= 0 ) + continue; + if(skill_produce_db[i].mat_amount[j] <= 0) { + if(pc_search_inventory(sd,id) < 0) + return 0; + } + else { + for(y=0,x=0;y<MAX_INVENTORY;y++) + if( sd->status.inventory[y].nameid == id ) + x+=sd->status.inventory[y].amount; + if(x<qty*skill_produce_db[i].mat_amount[j]) + return 0; + } + } + return i+1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_produce_mix (struct map_session_data *sd, uint16 skill_id, int nameid, int slot1, int slot2, int slot3, int qty) +{ + int slot[3]; + int i,sc,ele,idx,equip,wlv,make_per = 0,flag = 0,skill_lv = 0; + int num = -1; // exclude the recipe + struct status_data *status; + struct item_data* data; + + nullpo_ret(sd); + status = status_get_status_data(&sd->bl); + + if( sd->skill_id_old == skill_id ) + skill_lv = sd->skill_lv_old; + + if( !(idx=skill_can_produce_mix(sd,nameid,-1, qty)) ) + return 0; + idx--; + + if (qty < 1) + qty = 1; + + if (!skill_id) //A skill can be specified for some override cases. + skill_id = skill_produce_db[idx].req_skill; + + if( skill_id == GC_RESEARCHNEWPOISON ) + skill_id = GC_CREATENEWPOISON; + + slot[0]=slot1; + slot[1]=slot2; + slot[2]=slot3; + + for(i=0,sc=0,ele=0;i<3;i++){ //Note that qty should always be one if you are using these! + int j; + if( slot[i]<=0 ) + continue; + j = pc_search_inventory(sd,slot[i]); + if(j < 0) + continue; + if(slot[i]==1000){ /* Star Crumb */ + pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); + sc++; + } + if(slot[i]>=994 && slot[i]<=997 && ele==0){ /* Flame Heart . . . Great Nature */ + static const int ele_table[4]={3,1,4,2}; + pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); + ele=ele_table[slot[i]-994]; + } + } + + if( skill_id == RK_RUNEMASTERY ) { + int temp_qty, skill_lv = pc_checkskill(sd,skill_id); + data = itemdb_search(nameid); + + if( skill_lv == 10 ) temp_qty = 1 + rnd()%3; + else if( skill_lv > 5 ) temp_qty = 1 + rnd()%2; + else temp_qty = 1; + + if (data->stack.inventory) { + for( i = 0; i < MAX_INVENTORY; i++ ) { + if( sd->status.inventory[i].nameid == nameid ) { + if( sd->status.inventory[i].amount >= data->stack.amount ) { + clif_msgtable(sd->fd,0x61b); + return 0; + } else { + /** + * the amount fits, say we got temp_qty 4 and 19 runes, we trim temp_qty to 1. + **/ + if( temp_qty + sd->status.inventory[i].amount >= data->stack.amount ) + temp_qty = data->stack.amount - sd->status.inventory[i].amount; + } + break; + } + } + } + qty = temp_qty; + } + + for(i=0;i<MAX_PRODUCE_RESOURCE;i++){ + int j,id,x; + if( (id=skill_produce_db[idx].mat_id[i]) <= 0 ) + continue; + num++; + x=( skill_id == RK_RUNEMASTERY ? 1 : qty)*skill_produce_db[idx].mat_amount[i]; + do{ + int y=0; + j = pc_search_inventory(sd,id); + + if(j >= 0){ + y = sd->status.inventory[j].amount; + if(y>x)y=x; + pc_delitem(sd,j,y,0,0,LOG_TYPE_PRODUCE); + } else + ShowError("skill_produce_mix: material item error\n"); + + x-=y; + }while( j>=0 && x>0 ); + } + + if( (equip = (itemdb_isequip(nameid) && skill_id != GN_CHANGEMATERIAL && skill_id != GN_MAKEBOMB )) ) + wlv = itemdb_wlv(nameid); + if(!equip) { + switch(skill_id){ + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + // Ores & Metals Refining - skill bonuses are straight from kRO website [DracoRPG] + i = pc_checkskill(sd,skill_id); + make_per = sd->status.job_level*20 + status->dex*10 + status->luk*10; //Base chance + switch(nameid){ + case 998: // Iron + make_per += 4000+i*500; // Temper Iron bonus: +26/+32/+38/+44/+50 + break; + case 999: // Steel + make_per += 3000+i*500; // Temper Steel bonus: +35/+40/+45/+50/+55 + break; + case 1000: //Star Crumb + make_per = 100000; // Star Crumbs are 100% success crafting rate? (made 1000% so it succeeds even after penalties) [Skotlex] + break; + default: // Enchanted Stones + make_per += 1000+i*500; // Enchantedstone Craft bonus: +15/+20/+25/+30/+35 + break; + } + break; + case ASC_CDP: + make_per = (2000 + 40*status->dex + 20*status->luk); + break; + case AL_HOLYWATER: + /** + * Arch Bishop + **/ + case AB_ANCILLA: + make_per = 100000; //100% success + break; + case AM_PHARMACY: // Potion Preparation - reviewed with the help of various Ragnainfo sources [DracoRPG] + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + make_per = pc_checkskill(sd,AM_LEARNINGPOTION)*50 + + pc_checkskill(sd,AM_PHARMACY)*300 + sd->status.job_level*20 + + (status->int_/2)*10 + status->dex*10+status->luk*10; + if(merc_is_hom_active(sd->hd)) {//Player got a homun + int skill; + if((skill=merc_hom_checkskill(sd->hd,HVAN_INSTRUCT)) > 0) //His homun is a vanil with instruction change + make_per += skill*100; //+1% bonus per level + } + switch(nameid){ + case 501: // Red Potion + case 503: // Yellow Potion + case 504: // White Potion + make_per += (1+rnd()%100)*10 + 2000; + break; + case 970: // Alcohol + make_per += (1+rnd()%100)*10 + 1000; + break; + case 7135: // Bottle Grenade + case 7136: // Acid Bottle + case 7137: // Plant Bottle + case 7138: // Marine Sphere Bottle + make_per += (1+rnd()%100)*10; + break; + case 546: // Condensed Yellow Potion + make_per -= (1+rnd()%50)*10; + break; + case 547: // Condensed White Potion + case 7139: // Glistening Coat + make_per -= (1+rnd()%100)*10; + break; + //Common items, recieve no bonus or penalty, listed just because they are commonly produced + case 505: // Blue Potion + case 545: // Condensed Red Potion + case 605: // Anodyne + case 606: // Aloevera + default: + break; + } + if(battle_config.pp_rate != 100) + make_per = make_per * battle_config.pp_rate / 100; + break; + case SA_CREATECON: // Elemental Converter Creation + make_per = 100000; // should be 100% success rate + break; + /** + * Rune Knight + **/ + case RK_RUNEMASTERY: + { + int A = 100 * (51 + 2 * pc_checkskill(sd, skill_id)); + int B = 100 * status->dex / 30 + 10 * (status->luk + sd->status.job_level); + int C = 100 * cap_value(sd->itemid,0,100); //itemid depend on makerune() + int D = 0; + switch (nameid) { //rune rank it_diff 9 craftable rune + case ITEMID_BERKANA: + D = -2000; + break; //Rank S + case ITEMID_NAUTHIZ: + case ITEMID_URUZ: + D = -1500; + break; //Rank A + case ITEMID_ISA: + case ITEMID_WYRD: + D = -1000; + break; //Rank B + case ITEMID_RAIDO: + case ITEMID_THURISAZ: + case ITEMID_HAGALAZ: + case ITEMID_OTHILA: + D = -500; + break; //Rank C + default: D = -1500; + break; //not specified =-15% + } + make_per = A + B + C + D; + break; + } + /** + * Guilotine Cross + **/ + case GC_CREATENEWPOISON: + make_per = 3000 + 500 * pc_checkskill(sd,GC_RESEARCHNEWPOISON); + qty = 1+rnd()%pc_checkskill(sd,GC_RESEARCHNEWPOISON); + break; + case GN_CHANGEMATERIAL: + for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) + if( skill_changematerial_db[i].itemid == nameid ){ + make_per = skill_changematerial_db[i].rate * 10; + break; + } + break; + case GN_S_PHARMACY: + { + int difficulty = 0; + + difficulty = (620 - 20 * skill_lv);// (620 - 20 * Skill Level) + + make_per = status->int_ + status->dex/2 + status->luk + sd->status.job_level + (30+rnd()%120) + // (Caster?s INT) + (Caster?s DEX / 2) + (Caster?s LUK) + (Caster?s Job Level) + Random number between (30 ~ 150) + + (sd->status.base_level-100) + pc_checkskill(sd, AM_LEARNINGPOTION) + pc_checkskill(sd, CR_FULLPROTECTION)*(4+rnd()%6); // (Caster?s Base Level - 100) + (Potion Research x 5) + (Full Chemical Protection Skill Level) x (Random number between 4 ~ 10) + + switch(nameid){// difficulty factor + case 12422: case 12425: + case 12428: + difficulty += 10; + break; + case 6212: case 12426: + difficulty += 15; + break; + case 13264: case 12423: + case 12427: case 12436: + difficulty += 20; + break; + case 6210: case 6211: + case 12437: + difficulty += 30; + break; + case 12424: case 12475: + difficulty += 40; + break; + } + + if( make_per >= 400 && make_per > difficulty) + qty = 10; + else if( make_per >= 300 && make_per > difficulty) + qty = 7; + else if( make_per >= 100 && make_per > difficulty) + qty = 6; + else if( make_per >= 1 && make_per > difficulty) + qty = 5; + else + qty = 4; + make_per = 10000; + } + break; + case GN_MAKEBOMB: + case GN_MIX_COOKING: + { + int difficulty = 30 + rnd()%120; // Random number between (30 ~ 150) + + make_per = sd->status.job_level / 4 + status->luk / 2 + status->dex / 3; // (Caster?s Job Level / 4) + (Caster?s LUK / 2) + (Caster?s DEX / 3) + qty = ~(5 + rnd()%5) + 1; + + switch(nameid){// difficulty factor + case 13260: + difficulty += 5; + break; + case 13261: case 13262: + difficulty += 10; + break; + case 12429: case 12430: case 12431: + case 12432: case 12433: case 12434: + case 13263: + difficulty += 15; + break; + case 13264: + difficulty += 20; + break; + } + + if( make_per >= 30 && make_per > difficulty) + qty = 10 + rnd()%2; + else if( make_per >= 10 && make_per > difficulty) + qty = 10; + else if( make_per == 10 && make_per > difficulty) + qty = 8; + else if( (make_per >= 50 || make_per < 30) && make_per < difficulty) + ;// Food/Bomb creation fails. + else if( make_per >= 30 && make_per < difficulty) + qty = 5; + + if( qty < 0 || (skill_lv == 1 && make_per < difficulty)){ + qty = ~qty + 1; + make_per = 0; + }else + make_per = 10000; + qty = (skill_lv > 1 ? qty : 1); + } + break; + default: + if (sd->menuskill_id == AM_PHARMACY && + sd->menuskill_val > 10 && sd->menuskill_val <= 20) + { //Assume Cooking Dish + if (sd->menuskill_val >= 15) //Legendary Cooking Set. + make_per = 10000; //100% Success + else + make_per = 1200 * (sd->menuskill_val - 10) + + 20 * (sd->status.base_level + 1) + + 20 * (status->dex + 1) + + 100 * (rnd()%(30+5*(sd->cook_mastery/400) - (6+sd->cook_mastery/80)) + (6+sd->cook_mastery/80)) + - 400 * (skill_produce_db[idx].itemlv - 11 + 1) + - 10 * (100 - status->luk + 1) + - 500 * (num - 1) + - 100 * (rnd()%4 + 1); + break; + } + make_per = 5000; + break; + } + } else { // Weapon Forging - skill bonuses are straight from kRO website, other things from a jRO calculator [DracoRPG] + make_per = 5000 + sd->status.job_level*20 + status->dex*10 + status->luk*10; // Base + make_per += pc_checkskill(sd,skill_id)*500; // Smithing skills bonus: +5/+10/+15 + make_per += pc_checkskill(sd,BS_WEAPONRESEARCH)*100 +((wlv >= 3)? pc_checkskill(sd,BS_ORIDEOCON)*100:0); // Weaponry Research bonus: +1/+2/+3/+4/+5/+6/+7/+8/+9/+10, Oridecon Research bonus (custom): +1/+2/+3/+4/+5 + make_per -= (ele?2000:0) + sc*1500 + (wlv>1?wlv*1000:0); // Element Stone: -20%, Star Crumb: -15% each, Weapon level malus: -0/-20/-30 + if(pc_search_inventory(sd,989) > 0) make_per+= 1000; // Emperium Anvil: +10 + else if(pc_search_inventory(sd,988) > 0) make_per+= 500; // Golden Anvil: +5 + else if(pc_search_inventory(sd,987) > 0) make_per+= 300; // Oridecon Anvil: +3 + else if(pc_search_inventory(sd,986) > 0) make_per+= 0; // Anvil: +0? + if(battle_config.wp_rate != 100) + make_per = make_per * battle_config.wp_rate / 100; + } + + if (sd->class_&JOBL_BABY) //if it's a Baby Class + make_per = (make_per * 50) / 100; //Baby penalty is 50% (bugreport:4847) + + if(make_per < 1) make_per = 1; + + + if(rnd()%10000 < make_per || qty > 1){ //Success, or crafting multiple items. + struct item tmp_item; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid=nameid; + tmp_item.amount=1; + tmp_item.identify=1; + if(equip){ + tmp_item.card[0]=CARD0_FORGE; + tmp_item.card[1]=((sc*5)<<8)+ele; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } else { + //Flag is only used on the end, so it can be used here. [Skotlex] + switch (skill_id) { + case BS_DAGGER: + case BS_SWORD: + case BS_TWOHANDSWORD: + case BS_AXE: + case BS_MACE: + case BS_KNUCKLE: + case BS_SPEAR: + flag = battle_config.produce_item_name_input&0x1; + break; + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + flag = battle_config.produce_item_name_input&0x2; + break; + case AL_HOLYWATER: + /** + * Arch Bishop + **/ + case AB_ANCILLA: + flag = battle_config.produce_item_name_input&0x8; + break; + case ASC_CDP: + flag = battle_config.produce_item_name_input&0x10; + break; + default: + flag = battle_config.produce_item_name_input&0x80; + break; + } + if (flag) { + tmp_item.card[0]=CARD0_CREATE; + tmp_item.card[1]=0; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } + } + +// if(log_config.produce > 0) +// log_produce(sd,nameid,slot1,slot2,slot3,1); +//TODO update PICKLOG + + if(equip){ + clif_produceeffect(sd,0,nameid); + clif_misceffect(&sd->bl,3); + if(itemdb_wlv(nameid) >= 3 && ((ele? 1 : 0) + sc) >= 3) // Fame point system [DracoRPG] + pc_addfame(sd,10); // Success to forge a lv3 weapon with 3 additional ingredients = +10 fame point + } else { + int fame = 0; + tmp_item.amount = 0; + + for (i=0; i< qty; i++) { //Apply quantity modifiers. + if( (skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY) && make_per > 1){ + tmp_item.amount = qty; + break; + } + if (rnd()%10000 < make_per || qty == 1) { //Success + tmp_item.amount++; + if(nameid < 545 || nameid > 547) + continue; + if( skill_id != AM_PHARMACY && + skill_id != AM_TWILIGHT1 && + skill_id != AM_TWILIGHT2 && + skill_id != AM_TWILIGHT3 ) + continue; + //Add fame as needed. + switch(++sd->potion_success_counter) { + case 3: + fame+=1; // Success to prepare 3 Condensed Potions in a row + break; + case 5: + fame+=3; // Success to prepare 5 Condensed Potions in a row + break; + case 7: + fame+=10; // Success to prepare 7 Condensed Potions in a row + break; + case 10: + fame+=50; // Success to prepare 10 Condensed Potions in a row + sd->potion_success_counter = 0; + break; + } + } else //Failure + sd->potion_success_counter = 0; + } + + if (fame) + pc_addfame(sd,fame); + //Visual effects and the like. + switch (skill_id) { + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + case ASC_CDP: + clif_produceeffect(sd,2,nameid); + clif_misceffect(&sd->bl,5); + break; + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + clif_produceeffect(sd,0,nameid); + clif_misceffect(&sd->bl,3); + break; + case RK_RUNEMASTERY: + case GC_CREATENEWPOISON: + clif_produceeffect(sd,2,nameid); + clif_misceffect(&sd->bl,5); + break; + default: //Those that don't require a skill? + if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20) + { //Cooking items. + clif_specialeffect(&sd->bl, 608, AREA); + if( sd->cook_mastery < 1999 ) + pc_setglobalreg(sd, "COOK_MASTERY",sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5); + } + break; + } + } + if ( skill_id == GN_CHANGEMATERIAL && tmp_item.amount) { //Success + int j, k = 0; + for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) + if( skill_changematerial_db[i].itemid == nameid ){ + for(j=0; j<5; j++){ + if( rnd()%1000 < skill_changematerial_db[i].qty_rate[j] ){ + tmp_item.amount = qty * skill_changematerial_db[i].qty[j]; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + k++; + } + } + break; + } + if( k ){ + clif_msg_skill(sd,skill_id,0x627); + return 1; + } + } else if (tmp_item.amount) { //Success + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + if( skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY ) + clif_msg_skill(sd,skill_id,0x627); + return 1; + } + } + //Failure +// if(log_config.produce) +// log_produce(sd,nameid,slot1,slot2,slot3,0); +//TODO update PICKLOG + + if(equip){ + clif_produceeffect(sd,1,nameid); + clif_misceffect(&sd->bl,2); + } else { + switch (skill_id) { + case ASC_CDP: //25% Damage yourself, and display same effect as failed potion. + status_percent_damage(NULL, &sd->bl, -25, 0, true); + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + clif_produceeffect(sd,3,nameid); + clif_misceffect(&sd->bl,6); + sd->potion_success_counter = 0; // Fame point system [DracoRPG] + break; + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + clif_produceeffect(sd,1,nameid); + clif_misceffect(&sd->bl,2); + break; + case RK_RUNEMASTERY: + case GC_CREATENEWPOISON: + clif_produceeffect(sd,3,nameid); + clif_misceffect(&sd->bl,6); + break; + case GN_MIX_COOKING: { + struct item tmp_item; + const int compensation[5] = {13265, 13266, 13267, 12435, 13268}; + int rate = rnd()%500; + memset(&tmp_item,0,sizeof(tmp_item)); + if( rate < 50) i = 4; + else if( rate < 100) i = 2+rnd()%1; + else if( rate < 250 ) i = 1; + else if( rate < 500 ) i = 0; + tmp_item.nameid = compensation[i]; + tmp_item.amount = qty; + tmp_item.identify = 1; + if( pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE) ) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + clif_msg_skill(sd,skill_id,0x628); + } + break; + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + clif_msg_skill(sd,skill_id,0x628); + break; + default: + if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20 ) + { //Cooking items. + clif_specialeffect(&sd->bl, 609, AREA); + if( sd->cook_mastery > 0 ) + pc_setglobalreg(sd, "COOK_MASTERY", sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 )); + } + } + } + return 0; +} + +int skill_arrow_create (struct map_session_data *sd, int nameid) +{ + int i,j,flag,index=-1; + struct item tmp_item; + + nullpo_ret(sd); + + if(nameid <= 0) + return 1; + + for(i=0;i<MAX_SKILL_ARROW_DB;i++) + if(nameid == skill_arrow_db[i].nameid) { + index = i; + break; + } + + if(index < 0 || (j = pc_search_inventory(sd,nameid)) < 0) + return 1; + + pc_delitem(sd,j,1,0,0,LOG_TYPE_PRODUCE); + for(i=0;i<MAX_ARROW_RESOURCE;i++) { + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.identify = 1; + tmp_item.nameid = skill_arrow_db[index].cre_id[i]; + tmp_item.amount = skill_arrow_db[index].cre_amount[i]; + if(battle_config.produce_item_name_input&0x4) { + tmp_item.card[0]=CARD0_CREATE; + tmp_item.card[1]=0; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } + if(tmp_item.nameid <= 0 || tmp_item.amount <= 0) + continue; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + + return 0; +} +int skill_poisoningweapon( struct map_session_data *sd, int nameid) { + sc_type type; + int chance, i; + nullpo_ret(sd); + if( nameid <= 0 || (i = pc_search_inventory(sd,nameid)) < 0 || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) { + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); + return 0; + } + switch( nameid ) + { // t_lv used to take duration from skill_get_time2 + case PO_PARALYSE: type = SC_PARALYSE; break; + case PO_PYREXIA: type = SC_PYREXIA; break; + case PO_DEATHHURT: type = SC_DEATHHURT; break; + case PO_LEECHESEND: type = SC_LEECHESEND; break; + case PO_VENOMBLEED: type = SC_VENOMBLEED; break; + case PO_TOXIN: type = SC_TOXIN; break; + case PO_MAGICMUSHROOM: type = SC_MAGICMUSHROOM; break; + case PO_OBLIVIONCURSE: type = SC_OBLIVIONCURSE; break; + default: + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); + return 0; + } + + chance = 2 + 2 * sd->menuskill_val; // 2 + 2 * skill_lv + sc_start4(&sd->bl, SC_POISONINGWEAPON, 100, pc_checkskill(sd, GC_RESEARCHNEWPOISON), //in Aegis it store the level of GC_RESEARCHNEWPOISON in val1 + type, chance, 0, skill_get_time(GC_POISONINGWEAPON, sd->menuskill_val)); + + return 0; +} + +static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) +{ + struct status_change *sc = status_get_sc(bl); + + // non-offensive and non-magic skills do not affect the status + if (skill_get_nk(skill_id)&NK_NO_DAMAGE || !(skill_get_type(skill_id)&BF_MAGIC)) + return; + + if (sc && sc->count && sc->data[SC_MAGICPOWER]) + { + if (sc->data[SC_MAGICPOWER]->val4) + { + status_change_end(bl, SC_MAGICPOWER, INVALID_TIMER); + } + else + { + sc->data[SC_MAGICPOWER]->val4 = 1; + status_calc_bl(bl, status_sc2scb_flag(SC_MAGICPOWER)); +#ifndef RENEWAL + if(bl->type == BL_PC){// update current display. + clif_updatestatus(((TBL_PC *)bl),SP_MATK1); + clif_updatestatus(((TBL_PC *)bl),SP_MATK2); + } +#endif + } + } +} + + +int skill_magicdecoy(struct map_session_data *sd, int nameid) { + int x, y, i, class_, skill; + struct mob_data *md; + nullpo_ret(sd); + skill = sd->menuskill_val; + + if( nameid <= 0 || !itemdb_is_element(nameid) || (i = pc_search_inventory(sd,nameid)) < 0 || !skill || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) + { + clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); + return 0; + } + + // Spawn Position + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); + x = sd->sc.comet_x; + y = sd->sc.comet_y; + sd->sc.comet_x = sd->sc.comet_y = 0; + sd->menuskill_val = 0; + + class_ = (nameid == 990 || nameid == 991) ? 2043 + nameid - 990 : (nameid == 992) ? 2046 : 2045; + + + md = mob_once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE); + if( md ) { + md->master_id = sd->bl.id; + md->special_state.ai = AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(NC_MAGICDECOY,skill), mob_timer_delete, md->bl.id, 0); + mob_spawn(md); + md->status.matk_min = md->status.matk_max = 250 + (50 * skill); + } + + return 0; +} + +// Warlock Spellbooks. [LimitLine/3CeAM] +int skill_spellbook (struct map_session_data *sd, int nameid) { + int i, max_preserve, skill_id, point; + struct status_change *sc; + + nullpo_ret(sd); + + sc = status_get_sc(&sd->bl); + status_change_end(&sd->bl, SC_STOP, INVALID_TIMER); + + for(i=SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) if( sc && !sc->data[i] ) break; + if( i > SC_MAXSPELLBOOK ) + { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); + return 0; + } + + ARR_FIND(0,MAX_SKILL_SPELLBOOK_DB,i,skill_spellbook_db[i].nameid == nameid); // Search for information of this item + if( i == MAX_SKILL_SPELLBOOK_DB ) return 0; + + if( !pc_checkskill(sd, (skill_id = skill_spellbook_db[i].skill_id)) ) + { // User don't know the skill + sc_start(&sd->bl, SC_SLEEP, 100, 1, skill_get_time(WL_READING_SB, pc_checkskill(sd,WL_READING_SB))); + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_DIFFICULT_SLEEP, 0); + return 0; + } + + max_preserve = 4 * pc_checkskill(sd, WL_FREEZE_SP) + status_get_int(&sd->bl) / 10 + sd->status.base_level / 10; + point = skill_spellbook_db[i].point; + + if( sc && sc->data[SC_READING_SB] ){ + if( (sc->data[SC_READING_SB]->val2 + point) > max_preserve ) + { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_PRESERVATION_POINT, 0); + return 0; + } + for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--){ // This is how official saves spellbook. [malufett] + if( !sc->data[i] ){ + sc->data[SC_READING_SB]->val2 += point; // increase points + sc_start4(&sd->bl, (sc_type)i, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); + break; + } + } + }else{ + sc_start2(&sd->bl, SC_READING_SB, 100, 0, point, INVALID_TIMER); + sc_start4(&sd->bl, SC_MAXSPELLBOOK, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); + } + + return 1; +} +int skill_select_menu(struct map_session_data *sd,uint16 skill_id) { + int id, lv, prob, aslvl = 0; + nullpo_ret(sd); + + if (sd->sc.data[SC_STOP]) { + aslvl = sd->sc.data[SC_STOP]->val1; + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + } + + if( skill_id >= GS_GLITTERING || skill_get_type(skill_id) != BF_MAGIC || + (id = sd->status.skill[skill_id].id) == 0 || sd->status.skill[skill_id].flag != SKILL_FLAG_PLAGIARIZED ) { + clif_skill_fail(sd,SC_AUTOSHADOWSPELL,0,0); + return 0; + } + + lv = (aslvl + 1) / 2; // The level the skill will be autocasted + lv = min(lv,sd->status.skill[skill_id].lv); + prob = (aslvl == 10) ? 15 : (32 - 2 * aslvl); // Probability at level 10 was increased to 15. + sc_start4(&sd->bl,SC__AUTOSHADOWSPELL,100,id,lv,prob,0,skill_get_time(SC_AUTOSHADOWSPELL,aslvl)); + return 0; +} +int skill_elementalanalysis(struct map_session_data* sd, int n, uint16 skill_lv, unsigned short* item_list) { + int i; + + nullpo_ret(sd); + nullpo_ret(item_list); + + if( n <= 0 ) + return 1; + + for( i = 0; i < n; i++ ) { + int nameid, add_amount, del_amount, idx, product, flag; + struct item tmp_item; + + idx = item_list[i*2+0]-2; + del_amount = item_list[i*2+1]; + + if( skill_lv == 2 ) + del_amount -= (del_amount % 10); + add_amount = (skill_lv == 1) ? del_amount * (5 + rnd()%5) : del_amount / 10 ; + + if( (nameid = sd->status.inventory[idx].nameid) <= 0 || del_amount > sd->status.inventory[idx].amount ) { + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + switch( nameid ) { + // Level 1 + case 994: product = 990; break; // Flame Heart -> Red Blood. + case 995: product = 991; break; // Mystic Frozen -> Crystal Blue. + case 996: product = 992; break; // Rough Wind -> Wind of Verdure. + case 997: product = 993; break; // Great Nature -> Green Live. + // Level 2 + case 990: product = 994; break; // Red Blood -> Flame Heart. + case 991: product = 995; break; // Crystal Blue -> Mystic Frozen. + case 992: product = 996; break; // Wind of Verdure -> Rough Wind. + case 993: product = 997; break; // Green Live -> Great Nature. + default: + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + if( pc_delitem(sd,idx,del_amount,0,1,LOG_TYPE_CONSUME) ) { + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + if( skill_lv == 2 && rnd()%100 < 25 ) { // At level 2 have a fail chance. You loose your items if it fails. + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = product; + tmp_item.amount = add_amount; + tmp_item.identify = 1; + + if( tmp_item.amount ) { + if( (flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_CONSUME)) ) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + + } + + return 0; +} + +int skill_changematerial(struct map_session_data *sd, int n, unsigned short *item_list) { + int i, j, k, c, p = 0, nameid, amount; + + nullpo_ret(sd); + nullpo_ret(item_list); + + // Search for objects that can be created. + for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { + if( skill_produce_db[i].itemlv == 26 ) { + p = 0; + do { + c = 0; + // Verification of overlap between the objects required and the list submitted. + for( j = 0; j < MAX_PRODUCE_RESOURCE; j++ ) { + if( skill_produce_db[i].mat_id[j] > 0 ) { + for( k = 0; k < n; k++ ) { + int idx = item_list[k*2+0]-2; + nameid = sd->status.inventory[idx].nameid; + amount = item_list[k*2+1]; + if( nameid > 0 && sd->status.inventory[idx].identify == 0 ){ + clif_msg_skill(sd,GN_CHANGEMATERIAL,0x62D); + return 0; + } + if( nameid == skill_produce_db[i].mat_id[j] && (amount-p*skill_produce_db[i].mat_amount[j]) >= skill_produce_db[i].mat_amount[j] + && (amount-p*skill_produce_db[i].mat_amount[j])%skill_produce_db[i].mat_amount[j] == 0 ) // must be in exact amount + c++; // match + } + } + else + break; // No more items required + } + p++; + } while(n == j && c == n); + p--; + if ( p > 0 ) { + skill_produce_mix(sd,GN_CHANGEMATERIAL,skill_produce_db[i].nameid,0,0,0,p); + return 1; + } + } + } + + if( p == 0) + clif_msg_skill(sd,GN_CHANGEMATERIAL,0x623); + + return 0; +} +/** + * for Royal Guard's LG_TRAMPLE + **/ +static int skill_destroy_trap( struct block_list *bl, va_list ap ) { + struct skill_unit *su = (struct skill_unit *)bl; + struct skill_unit_group *sg; + unsigned int tick; + + nullpo_ret(su); + tick = va_arg(ap, unsigned int); + + if (su->alive && (sg = su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP) { + switch( sg->unit_id ) { + case UNT_LANDMINE: + case UNT_CLAYMORETRAP: + case UNT_BLASTMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_CLUSTERBOMB: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + map_foreachinrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &su->bl,tick); + break; + } + // Traps aren't recovered. + skill_delunit(su); + } + return 0; +} +/*========================================== + * + *------------------------------------------*/ +int skill_blockpc_end(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = map_id2sd(id); + struct skill_cd * cd = NULL; + + if (data <= 0 || data >= MAX_SKILL) + return 0; + if (!sd) return 0; + if (sd->blockskill[data] != (0x1|(tid&0xFE))) return 0; + + if( ( cd = idb_get(skillcd_db,sd->status.char_id) ) ) { + int i,cursor; + ARR_FIND( 0, cd->cursor+1, cursor, cd->skidx[cursor] == data ); + cd->duration[cursor] = 0; + cd->skidx[cursor] = 0; + cd->nameid[cursor] = 0; + // compact the cool down list + for( i = 0, cursor = 0; i < cd->cursor; i++ ) { + if( cd->duration[i] == 0 ) + continue; + if( cursor != i ) { + cd->duration[cursor] = cd->duration[i]; + cd->skidx[cursor] = cd->skidx[i]; + cd->nameid[cursor] = cd->nameid[i]; + } + cursor++; + } + if( cursor == 0 ) + idb_remove(skillcd_db,sd->status.char_id); + else + cd->cursor = cursor; + } + + sd->blockskill[data] = 0; + return 1; +} + +/** + * flags a singular skill as being blocked from persistent usage. + * @param sd the player the skill delay affects + * @param skill_id the skill which should be delayed + * @param tick the length of time the delay should last + * @param load whether this assignment is being loaded upon player login + * @return 0 if successful, -1 otherwise + */ +int skill_blockpc_start_(struct map_session_data *sd, uint16 skill_id, int tick, bool load) +{ + int oskill_id = skill_id; + struct skill_cd* cd = NULL; + uint16 idx = skill_get_index(skill_id); + + nullpo_retr (-1, sd); + + if (idx == 0) + return -1; + + if (tick < 1) { + sd->blockskill[idx] = 0; + return -1; + } + + if( battle_config.display_status_timers ) + clif_skill_cooldown(sd, idx, tick); + + if( !load ) + {// not being loaded initially so ensure the skill delay is recorded + if( !(cd = idb_get(skillcd_db,sd->status.char_id)) ) + {// create a new skill cooldown object for map storage + CREATE( cd, struct skill_cd, 1 ); + idb_put( skillcd_db, sd->status.char_id, cd ); + } + + // record the skill duration in the database map + cd->duration[cd->cursor] = tick; + cd->skidx[cd->cursor] = idx; + cd->nameid[cd->cursor] = oskill_id; + cd->cursor++; + } + + sd->blockskill[idx] = 0x1|(0xFE&add_timer(gettick()+tick,skill_blockpc_end,sd->bl.id,idx)); + return 0; +} + +int skill_blockhomun_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] +{ + struct homun_data *hd = (TBL_HOM*) map_id2bl(id); + if (data <= 0 || data >= MAX_SKILL) + return 0; + if (hd) hd->blockskill[data] = 0; + + return 1; +} + +int skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int tick) //[orn] +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr (-1, hd); + + + if (idx == 0) + return -1; + + if (tick < 1) { + hd->blockskill[idx] = 0; + return -1; + } + hd->blockskill[idx] = 1; + return add_timer(gettick() + tick, skill_blockhomun_end, hd->bl.id, idx); +} + +int skill_blockmerc_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] +{ + struct mercenary_data *md = (TBL_MER*)map_id2bl(id); + if( data <= 0 || data >= MAX_SKILL ) + return 0; + if( md ) md->blockskill[data] = 0; + + return 1; +} + +int skill_blockmerc_start(struct mercenary_data *md, uint16 skill_id, int tick) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr (-1, md); + + if (idx == 0) + return -1; + if( tick < 1 ) + { + md->blockskill[idx] = 0; + return -1; + } + md->blockskill[idx] = 1; + return add_timer(gettick() + tick, skill_blockmerc_end, md->bl.id, idx); +} +/** + * Adds a new skill unit entry for this player to recast after map load + **/ +void skill_usave_add(struct map_session_data * sd, uint16 skill_id, uint16 skill_lv) { + struct skill_usave * sus = NULL; + + if( idb_exists(skillusave_db,sd->status.char_id) ) { + idb_remove(skillusave_db,sd->status.char_id); + } + + CREATE( sus, struct skill_usave, 1 ); + idb_put( skillusave_db, sd->status.char_id, sus ); + + sus->skill_id = skill_id; + sus->skill_lv = skill_lv; + + return; +} +void skill_usave_trigger(struct map_session_data *sd) { + struct skill_usave * sus = NULL; + + if( ! (sus = idb_get(skillusave_db,sd->status.char_id)) ) { + return; + } + + skill_unitsetting(&sd->bl,sus->skill_id,sus->skill_lv,sd->bl.x,sd->bl.y,0); + + idb_remove(skillusave_db,sd->status.char_id); + + return; +} +/* + * + */ +int skill_split_str (char *str, char **val, int num) +{ + int i; + + for( i = 0; i < num && str; i++ ) + { + val[i] = str; + str = strchr(str,','); + if( str ) + *str++=0; + } + + return i; +} +/* + * + */ +int skill_split_atoi (char *str, int *val) +{ + int i, j, diff, step = 1; + + for (i=0; i<MAX_SKILL_LEVEL; i++) { + if (!str) break; + val[i] = atoi(str); + str = strchr(str,':'); + if (str) + *str++=0; + } + if(i==0) //No data found. + return 0; + if(i==1) + { //Single value, have the whole range have the same value. + for (; i < MAX_SKILL_LEVEL; i++) + val[i] = val[i-1]; + return i; + } + //Check for linear change with increasing steps until we reach half of the data acquired. + for (step = 1; step <= i/2; step++) + { + diff = val[i-1] - val[i-step-1]; + for(j = i-1; j >= step; j--) + if ((val[j]-val[j-step]) != diff) + break; + + if (j>=step) //No match, try next step. + continue; + + for(; i < MAX_SKILL_LEVEL; i++) + { //Apply linear increase + val[i] = val[i-step]+diff; + if (val[i] < 1 && val[i-1] >=0) //Check if we have switched from + to -, cap the decrease to 0 in said cases. + { val[i] = 1; diff = 0; step = 1; } + } + return i; + } + //Okay.. we can't figure this one out, just fill out the stuff with the previous value. + for (;i<MAX_SKILL_LEVEL; i++) + val[i] = val[i-1]; + return i; +} + +/* + * + */ +void skill_init_unit_layout (void) +{ + int i,j,size,pos = 0; + + memset(skill_unit_layout,0,sizeof(skill_unit_layout)); + + // standard square layouts go first + for (i=0; i<=MAX_SQUARE_LAYOUT; i++) { + size = i*2+1; + skill_unit_layout[i].count = size*size; + for (j=0; j<size*size; j++) { + skill_unit_layout[i].dx[j] = (j%size-i); + skill_unit_layout[i].dy[j] = (j/size-i); + } + } + + // afterwards add special ones + pos = i; + for (i=0;i<MAX_SKILL_DB;i++) { + if (!skill_db[i].unit_id[0] || skill_db[i].unit_layout_type[0] != -1) + continue; + if( i >= HM_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { + int skill = i; + + if( i >= EL_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { + skill -= EL_SKILLRANGEMIN; + skill += EL_SKILLBASE; + } + if( skill == EL_FIRE_MANTLE ) { + static const int dx[] = {-1, 0, 1, 1, 1, 0,-1,-1}; + static const int dy[] = { 1, 1, 1, 0,-1,-1,-1, 0}; + skill_unit_layout[pos].count = 8; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + switch (i) { + case MG_FIREWALL: + case WZ_ICEWALL: + case WL_EARTHSTRAIN://Warlock + // these will be handled later + break; + case PR_SANCTUARY: + case NPC_EVILLAND: { + static const int dx[] = { + -1, 0, 1,-2,-1, 0, 1, 2,-2,-1, + 0, 1, 2,-2,-1, 0, 1, 2,-1, 0, 1}; + static const int dy[]={ + -2,-2,-2,-1,-1,-1,-1,-1, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2}; + skill_unit_layout[pos].count = 21; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case PR_MAGNUS: { + static const int dx[] = { + -1, 0, 1,-1, 0, 1,-3,-2,-1, 0, + 1, 2, 3,-3,-2,-1, 0, 1, 2, 3, + -3,-2,-1, 0, 1, 2, 3,-1, 0, 1,-1, 0, 1}; + static const int dy[] = { + -3,-3,-3,-2,-2,-2,-1,-1,-1,-1, + -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3}; + skill_unit_layout[pos].count = 33; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case MH_POISON_MIST: + case AS_VENOMDUST: { + static const int dx[] = {-1, 0, 0, 0, 1}; + static const int dy[] = { 0,-1, 0, 1, 0}; + skill_unit_layout[pos].count = 5; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: { + static const int dx[] = { + 0, 0,-1, 0, 1,-2,-1, 0, 1, 2, + -4,-3,-2,-1, 0, 1, 2, 3, 4,-2, + -1, 0, 1, 2,-1, 0, 1, 0, 0}; + static const int dy[] = { + -4,-3,-2,-2,-2,-1,-1,-1,-1,-1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 2, 2, 2, 3, 4}; + skill_unit_layout[pos].count = 29; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case PF_FOGWALL: { + static const int dx[] = { + -2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; + static const int dy[] = { + -1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; + skill_unit_layout[pos].count = 15; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case PA_GOSPEL: { + static const int dx[] = { + -1, 0, 1,-1, 0, 1,-3,-2,-1, 0, + 1, 2, 3,-3,-2,-1, 0, 1, 2, 3, + -3,-2,-1, 0, 1, 2, 3,-1, 0, 1, + -1, 0, 1}; + static const int dy[] = { + -3,-3,-3,-2,-2,-2,-1,-1,-1,-1, + -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 3, 3, 3}; + skill_unit_layout[pos].count = 33; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case NJ_KAENSIN: { + static const int dx[] = {-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; + static const int dy[] = { 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2}; + skill_unit_layout[pos].count = 24; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case NJ_TATAMIGAESHI: { + //Level 1 (count 4, cross of 3x3) + static const int dx1[] = {-1, 1, 0, 0}; + static const int dy1[] = { 0, 0,-1, 1}; + //Level 2-3 (count 8, cross of 5x5) + static const int dx2[] = {-2,-1, 1, 2, 0, 0, 0, 0}; + static const int dy2[] = { 0, 0, 0, 0,-2,-1, 1, 2}; + //Level 4-5 (count 12, cross of 7x7 + static const int dx3[] = {-3,-2,-1, 1, 2, 3, 0, 0, 0, 0, 0, 0}; + static const int dy3[] = { 0, 0, 0, 0, 0, 0,-3,-2,-1, 1, 2, 3}; + //lv1 + j = 0; + skill_unit_layout[pos].count = 4; + memcpy(skill_unit_layout[pos].dx,dx1,sizeof(dx1)); + memcpy(skill_unit_layout[pos].dy,dy1,sizeof(dy1)); + skill_db[i].unit_layout_type[j] = pos; + //lv2/3 + j++; + pos++; + skill_unit_layout[pos].count = 8; + memcpy(skill_unit_layout[pos].dx,dx2,sizeof(dx2)); + memcpy(skill_unit_layout[pos].dy,dy2,sizeof(dy2)); + skill_db[i].unit_layout_type[j] = pos; + skill_db[i].unit_layout_type[++j] = pos; + //lv4/5 + j++; + pos++; + skill_unit_layout[pos].count = 12; + memcpy(skill_unit_layout[pos].dx,dx3,sizeof(dx3)); + memcpy(skill_unit_layout[pos].dy,dy3,sizeof(dy3)); + skill_db[i].unit_layout_type[j] = pos; + skill_db[i].unit_layout_type[++j] = pos; + //Fill in the rest using lv 5. + for (;j<MAX_SKILL_LEVEL;j++) + skill_db[i].unit_layout_type[j] = pos; + //Skip, this way the check below will fail and continue to the next skill. + pos++; + } + break; + case GN_WALLOFTHORN: { + static const int dx[] = {-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2, 2, 2, 1, 0}; + static const int dy[] = { 2, 2, 1, 0,-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2}; + skill_unit_layout[pos].count = 16; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + default: + ShowError("unknown unit layout at skill %d\n",i); + break; + } + } + if (!skill_unit_layout[pos].count) + continue; + for (j=0;j<MAX_SKILL_LEVEL;j++) + skill_db[i].unit_layout_type[j] = pos; + pos++; + } + + // firewall and icewall have 8 layouts (direction-dependent) + firewall_unit_pos = pos; + for (i=0;i<8;i++) { + if (i&1) { + skill_unit_layout[pos].count = 5; + if (i&0x2) { + int dx[] = {-1,-1, 0, 0, 1}; + int dy[] = { 1, 0, 0,-1,-1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 1, 1 ,0, 0,-1}; + int dy[] = { 1, 0, 0,-1,-1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + skill_unit_layout[pos].count = 3; + if (i%4==0) { + int dx[] = {-1, 0, 1}; + int dy[] = { 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 0, 0, 0}; + int dy[] = {-1, 0, 1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } + pos++; + } + icewall_unit_pos = pos; + for (i=0;i<8;i++) { + skill_unit_layout[pos].count = 5; + if (i&1) { + if (i&0x2) { + int dx[] = {-2,-1, 0, 1, 2}; + int dy[] = { 2, 1, 0,-1,-2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 2, 1 ,0,-1,-2}; + int dy[] = { 2, 1, 0,-1,-2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + if (i%4==0) { + int dx[] = {-2,-1, 0, 1, 2}; + int dy[] = { 0, 0, 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 0, 0, 0, 0, 0}; + int dy[] = {-2,-1, 0, 1, 2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } + pos++; + } + earthstrain_unit_pos = pos; + for( i = 0; i < 8; i++ ) + { // For each Direction + skill_unit_layout[pos].count = 15; + switch( i ) + { + case 0: case 1: case 3: case 4: case 5: case 7: + { + int dx[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; + int dy[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case 2: + case 6: + { + int dx[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int dy[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + } + pos++; + } + +} + +int skill_block_check(struct block_list *bl, sc_type type , uint16 skill_id) { + int inf = 0; + struct status_change *sc = status_get_sc(bl); + + if( !sc || !bl || !skill_id ) + return 0; // Can do it + + switch(type){ + case SC_STASIS: + inf = skill_get_inf2(skill_id); + if( inf == INF2_SONG_DANCE || /*skill_get_inf2(skill_id) == INF2_CHORUS_SKILL ||*/ inf == INF2_SPIRIT_SKILL ) + return 1; // Can't do it. + switch( skill_id ) + { + case NV_FIRSTAID: case TF_HIDING: case AS_CLOAKING: case WZ_SIGHTRASHER: + case RG_STRIPWEAPON: case RG_STRIPSHIELD: case RG_STRIPARMOR: case WZ_METEOR: + case RG_STRIPHELM: case SC_STRIPACCESSARY: case ST_FULLSTRIP: case WZ_SIGHTBLASTER: + case ST_CHASEWALK: case SC_ENERVATION: case SC_GROOMY: case WZ_ICEWALL: + case SC_IGNORANCE: case SC_LAZINESS: case SC_UNLUCKY: case WZ_STORMGUST: + case SC_WEAKNESS: case AL_RUWACH: case AL_PNEUMA: case WZ_JUPITEL: + case AL_HEAL: case AL_BLESSING: case AL_INCAGI: case WZ_VERMILION: + case AL_TELEPORT: case AL_WARP: case AL_HOLYWATER: case WZ_EARTHSPIKE: + case AL_HOLYLIGHT: case PR_IMPOSITIO: case PR_ASPERSIO: case WZ_HEAVENDRIVE: + case PR_SANCTUARY: case PR_STRECOVERY: case PR_MAGNIFICAT: case WZ_QUAGMIRE: + case ALL_RESURRECTION: case PR_LEXDIVINA: case PR_LEXAETERNA: case HW_GRAVITATION: + case PR_MAGNUS: case PR_TURNUNDEAD: case MG_SRECOVERY: case HW_MAGICPOWER: + case MG_SIGHT: case MG_NAPALMBEAT: case MG_SAFETYWALL: case HW_GANBANTEIN: + case MG_SOULSTRIKE: case MG_COLDBOLT: case MG_FROSTDIVER: case WL_DRAINLIFE: + case MG_STONECURSE: case MG_FIREBALL: case MG_FIREWALL: case WL_SOULEXPANSION: + case MG_FIREBOLT: case MG_LIGHTNINGBOLT: case MG_THUNDERSTORM: case MG_ENERGYCOAT: + case WL_WHITEIMPRISON: case WL_SUMMONFB: case WL_SUMMONBL: case WL_SUMMONWB: + case WL_SUMMONSTONE: case WL_SIENNAEXECRATE: case WL_RELEASE: case WL_EARTHSTRAIN: + case WL_RECOGNIZEDSPELL: case WL_READING_SB: case SA_MAGICROD: case SA_SPELLBREAKER: + case SA_DISPELL: case SA_FLAMELAUNCHER: case SA_FROSTWEAPON: case SA_LIGHTNINGLOADER: + case SA_SEISMICWEAPON: case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: case PF_HPCONVERSION: case PF_SOULCHANGE: case PF_SPIDERWEB: + case PF_FOGWALL: case TK_RUN: case TK_HIGHJUMP: case TK_SEVENWIND: + case SL_KAAHI: case SL_KAUPE: case SL_KAITE: + + // Skills that need to be confirmed. + case SO_FIREWALK: case SO_ELECTRICWALK: case SO_SPELLFIST: case SO_EARTHGRAVE: + case SO_DIAMONDDUST: case SO_POISON_BUSTER: case SO_PSYCHIC_WAVE: case SO_CLOUD_KILL: + case SO_STRIKING: case SO_WARMER: case SO_VACUUM_EXTREME: case SO_VARETYR_SPEAR: + case SO_ARRULLO: + return 1; // Can't do it. + } + break; + case SC_KAGEHUMI: + switch(skill_id){ + case TF_HIDING: case AS_CLOAKING: case GC_CLOAKINGEXCEED: case SC_SHADOWFORM: + case MI_HARMONIZE: case CG_MARIONETTE: case AL_TELEPORT: case TF_BACKSLIDING: + case RA_CAMOUFLAGE: case ST_CHASEWALK: case GD_EMERGENCYCALL: + return 1; // needs more info + } + break; + } + + return 0; +} + +int skill_get_elemental_type( uint16 skill_id , uint16 skill_lv ) { + int type = 0; + + switch( skill_id ) { + case SO_SUMMON_AGNI: type = 2114; break; + case SO_SUMMON_AQUA: type = 2117; break; + case SO_SUMMON_VENTUS: type = 2120; break; + case SO_SUMMON_TERA: type = 2123; break; + } + + type += skill_lv - 1; + + return type; +} + +/** + * reload stored skill cooldowns when a player logs in. + * @param sd the affected player structure + */ +void skill_cooldown_load(struct map_session_data * sd) +{ + int i; + struct skill_cd* cd = NULL; + + // always check to make sure the session properly exists + nullpo_retv(sd); + + if( !(cd = idb_get(skillcd_db, sd->status.char_id)) ) + {// no skill cooldown is associated with this character + return; + } + + // process each individual cooldown associated with the character + for( i = 0; i < cd->cursor; i++ ) + { + // block the skill from usage but ensure it is not recorded (load = true) + skill_blockpc_start_( sd, cd->nameid[i], cd->duration[i], true ); + } +} + +/*========================================== + * sub-function of DB reading. + * skill_db.txt + *------------------------------------------*/ + +static bool skill_parse_row_skilldb(char* split[], int columns, int current) +{// id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description + uint16 skill_id = atoi(split[0]); + uint16 idx; + if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) + || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) + || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) + || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) { + ShowWarning("skill_parse_row_skilldb: Skill id %d is forbidden (interferes with guild/homun/mercenary skill mapping)!\n", skill_id); + return false; + } + + idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].range); + skill_db[idx].hit = atoi(split[2]); + skill_db[idx].inf = atoi(split[3]); + skill_split_atoi(split[4],skill_db[idx].element); + skill_db[idx].nk = (int)strtol(split[5], NULL, 0); + skill_split_atoi(split[6],skill_db[idx].splash); + skill_db[idx].max = atoi(split[7]); + skill_split_atoi(split[8],skill_db[idx].num); + + if( strcmpi(split[9],"yes") == 0 ) + skill_db[idx].castcancel = 1; + else + skill_db[idx].castcancel = 0; + skill_db[idx].cast_def_rate = atoi(split[10]); + skill_db[idx].inf2 = (int)strtol(split[11], NULL, 0); + skill_split_atoi(split[12],skill_db[idx].maxcount); + if( strcmpi(split[13],"weapon") == 0 ) + skill_db[idx].skill_type = BF_WEAPON; + else if( strcmpi(split[13],"magic") == 0 ) + skill_db[idx].skill_type = BF_MAGIC; + else if( strcmpi(split[13],"misc") == 0 ) + skill_db[idx].skill_type = BF_MISC; + else + skill_db[idx].skill_type = 0; + skill_split_atoi(split[14],skill_db[idx].blewcount); + safestrncpy(skill_db[idx].name, trim(split[15]), sizeof(skill_db[idx].name)); + safestrncpy(skill_db[idx].desc, trim(split[16]), sizeof(skill_db[idx].desc)); + strdb_iput(skilldb_name2id, skill_db[idx].name, skill_id); + + return true; +} + +static bool skill_parse_row_requiredb(char* split[], int columns, int current) +{// skill_id,HPCost,MaxHPTrigger,SPCost,HPRateCost,SPRateCost,ZenyCost,RequiredWeapons,RequiredAmmoTypes,RequiredAmmoAmount,RequiredState,SpiritSphereCost,RequiredItemID1,RequiredItemAmount1,RequiredItemID2,RequiredItemAmount2,RequiredItemID3,RequiredItemAmount3,RequiredItemID4,RequiredItemAmount4,RequiredItemID5,RequiredItemAmount5,RequiredItemID6,RequiredItemAmount6,RequiredItemID7,RequiredItemAmount7,RequiredItemID8,RequiredItemAmount8,RequiredItemID9,RequiredItemAmount9,RequiredItemID10,RequiredItemAmount10 + char* p; + int j; + + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].hp); + skill_split_atoi(split[2],skill_db[idx].mhp); + skill_split_atoi(split[3],skill_db[idx].sp); + skill_split_atoi(split[4],skill_db[idx].hp_rate); + skill_split_atoi(split[5],skill_db[idx].sp_rate); + skill_split_atoi(split[6],skill_db[idx].zeny); + + //Wich weapon type are required, see doc/item_db for types + p = split[7]; + for( j = 0; j < 32; j++ ) + { + int l = atoi(p); + if( l == 99 ) // Any weapon + { + skill_db[idx].weapon = 0; + break; + } + else + skill_db[idx].weapon |= 1<<l; + p = strchr(p,':'); + if(!p) + break; + p++; + } + + //FIXME: document this + p = split[8]; + for( j = 0; j < 32; j++ ) + { + int l = atoi(p); + if( l == 99 ) // Any ammo type + { + skill_db[idx].ammo = 0xFFFFFFFF; + break; + } + else if( l ) // 0 stands for no requirement + skill_db[idx].ammo |= 1<<l; + p = strchr(p,':'); + if( !p ) + break; + p++; + } + skill_split_atoi(split[9],skill_db[idx].ammo_qty); + + if( strcmpi(split[10],"hiding")==0 ) skill_db[idx].state = ST_HIDING; + else if( strcmpi(split[10],"cloaking")==0 ) skill_db[idx].state = ST_CLOAKING; + else if( strcmpi(split[10],"hidden")==0 ) skill_db[idx].state = ST_HIDDEN; + else if( strcmpi(split[10],"riding")==0 ) skill_db[idx].state = ST_RIDING; + else if( strcmpi(split[10],"falcon")==0 ) skill_db[idx].state = ST_FALCON; + else if( strcmpi(split[10],"cart")==0 ) skill_db[idx].state = ST_CART; + else if( strcmpi(split[10],"shield")==0 ) skill_db[idx].state = ST_SHIELD; + else if( strcmpi(split[10],"sight")==0 ) skill_db[idx].state = ST_SIGHT; + else if( strcmpi(split[10],"explosionspirits")==0 ) skill_db[idx].state = ST_EXPLOSIONSPIRITS; + else if( strcmpi(split[10],"cartboost")==0 ) skill_db[idx].state = ST_CARTBOOST; + else if( strcmpi(split[10],"recover_weight_rate")==0 ) skill_db[idx].state = ST_RECOV_WEIGHT_RATE; + else if( strcmpi(split[10],"move_enable")==0 ) skill_db[idx].state = ST_MOVE_ENABLE; + else if( strcmpi(split[10],"water")==0 ) skill_db[idx].state = ST_WATER; + /** + * New States + **/ + else if( strcmpi(split[10],"dragon")==0 ) skill_db[idx].state = ST_RIDINGDRAGON; + else if( strcmpi(split[10],"warg")==0 ) skill_db[idx].state = ST_WUG; + else if( strcmpi(split[10],"ridingwarg")==0 ) skill_db[idx].state = ST_RIDINGWUG; + else if( strcmpi(split[10],"mado")==0 ) skill_db[idx].state = ST_MADO; + else if( strcmpi(split[10],"elementalspirit")==0 ) skill_db[idx].state = ST_ELEMENTALSPIRIT; + else if (strcmpi(split[10], "poisonweapon") == 0) skill_db[idx].state = ST_POISONINGWEAPON; + else if (strcmpi(split[10], "rollingcutter") == 0) skill_db[idx].state = ST_ROLLINGCUTTER; + else if (strcmpi(split[10], "mh_fighting") == 0) skill_db[idx].state = ST_MH_FIGHTING; + else if (strcmpi(split[10], "mh_grappling") == 0) skill_db[idx].state = ST_MH_GRAPPLING; + + /** + * Unknown or no state + **/ + else skill_db[idx].state = ST_NONE; + + skill_split_atoi(split[11],skill_db[idx].spiritball); + for( j = 0; j < MAX_SKILL_ITEM_REQUIRE; j++ ) { + skill_db[idx].itemid[j] = atoi(split[12+ 2*j]); + skill_db[idx].amount[j] = atoi(split[13+ 2*j]); + } + + return true; +} + +static bool skill_parse_row_castdb(char* split[], int columns, int current) +{// skill_id,CastingTime,AfterCastActDelay,AfterCastWalkDelay,Duration1,Duration2 + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].cast); + skill_split_atoi(split[2],skill_db[idx].delay); + skill_split_atoi(split[3],skill_db[idx].walkdelay); + skill_split_atoi(split[4],skill_db[idx].upkeep_time); + skill_split_atoi(split[5],skill_db[idx].upkeep_time2); + skill_split_atoi(split[6],skill_db[idx].cooldown); +#ifdef RENEWAL_CAST + skill_split_atoi(split[7],skill_db[idx].fixed_cast); +#endif + return true; +} + +static bool skill_parse_row_castnodexdb(char* split[], int columns, int current) +{// Skill id,Cast,Delay (optional) + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].castnodex); + if( split[2] ) // optional column + skill_split_atoi(split[2],skill_db[idx].delaynodex); + + return true; +} + +static bool skill_parse_row_nocastdb(char* split[], int columns, int current) +{// skill_id,Flag + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_db[idx].nocast |= atoi(split[1]); + + return true; +} + +static bool skill_parse_row_unitdb(char* split[], int columns, int current) +{// ID,unit ID,unit ID 2,layout,range,interval,target,flag + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_db[idx].unit_id[0] = strtol(split[1],NULL,16); + skill_db[idx].unit_id[1] = strtol(split[2],NULL,16); + skill_split_atoi(split[3],skill_db[idx].unit_layout_type); + skill_split_atoi(split[4],skill_db[idx].unit_range); + skill_db[idx].unit_interval = atoi(split[5]); + + if( strcmpi(split[6],"noenemy")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; + else if( strcmpi(split[6],"friend")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; + else if( strcmpi(split[6],"party")==0 ) skill_db[idx].unit_target = BCT_PARTY; + else if( strcmpi(split[6],"ally")==0 ) skill_db[idx].unit_target = BCT_PARTY|BCT_GUILD; + else if( strcmpi(split[6],"guild")==0 ) skill_db[idx].unit_target = BCT_GUILD; + else if( strcmpi(split[6],"all")==0 ) skill_db[idx].unit_target = BCT_ALL; + else if( strcmpi(split[6],"enemy")==0 ) skill_db[idx].unit_target = BCT_ENEMY; + else if( strcmpi(split[6],"self")==0 ) skill_db[idx].unit_target = BCT_SELF; + else if( strcmpi(split[6],"noone")==0 ) skill_db[idx].unit_target = BCT_NOONE; + else skill_db[idx].unit_target = strtol(split[6],NULL,16); + + skill_db[idx].unit_flag = strtol(split[7],NULL,16); + + if (skill_db[idx].unit_flag&UF_DEFNOTENEMY && battle_config.defnotenemy) + skill_db[idx].unit_target = BCT_NOENEMY; + + //By default, target just characters. + skill_db[idx].unit_target |= BL_CHAR; + if (skill_db[idx].unit_flag&UF_NOPC) + skill_db[idx].unit_target &= ~BL_PC; + if (skill_db[idx].unit_flag&UF_NOMOB) + skill_db[idx].unit_target &= ~BL_MOB; + if (skill_db[idx].unit_flag&UF_SKILL) + skill_db[idx].unit_target |= BL_SKILL; + + return true; +} + +static bool skill_parse_row_producedb(char* split[], int columns, int current) +{// ProduceItemID,ItemLV,RequireSkill,Requireskill_lv,MaterialID1,MaterialAmount1,...... + int x,y; + + int i = atoi(split[0]); + if( !i ) + return false; + + skill_produce_db[current].nameid = i; + skill_produce_db[current].itemlv = atoi(split[1]); + skill_produce_db[current].req_skill = atoi(split[2]); + skill_produce_db[current].req_skill_lv = atoi(split[3]); + + for( x = 4, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_PRODUCE_RESOURCE; x += 2, y++ ) + { + skill_produce_db[current].mat_id[y] = atoi(split[x]); + skill_produce_db[current].mat_amount[y] = atoi(split[x+1]); + } + + return true; +} + +static bool skill_parse_row_createarrowdb(char* split[], int columns, int current) +{// SourceID,MakeID1,MakeAmount1,...,MakeID5,MakeAmount5 + int x,y; + + int i = atoi(split[0]); + if( !i ) + return false; + + skill_arrow_db[current].nameid = i; + + for( x = 1, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_ARROW_RESOURCE; x += 2, y++ ) + { + skill_arrow_db[current].cre_id[y] = atoi(split[x]); + skill_arrow_db[current].cre_amount[y] = atoi(split[x+1]); + } + + return true; +} +static bool skill_parse_row_spellbookdb(char* split[], int columns, int current) +{// skill_id,PreservePoints + + uint16 skill_id = atoi(split[0]); + int points = atoi(split[1]); + int nameid = atoi(split[2]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + ShowError("spellbook_db: Invalid skill ID %d\n", skill_id); + if ( !skill_get_inf(skill_id) ) + ShowError("spellbook_db: Passive skills cannot be memorized (%d/%s)\n", skill_id, skill_get_name(skill_id)); + if( points < 1 ) + ShowError("spellbook_db: PreservePoints have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); + else + { + skill_spellbook_db[current].skill_id = skill_id; + skill_spellbook_db[current].point = points; + skill_spellbook_db[current].nameid = nameid; + + return true; + } + + return false; +} +static bool skill_parse_row_improvisedb(char* split[], int columns, int current) +{// SkillID,Rate + uint16 skill_id = atoi(split[0]); + short j = atoi(split[1]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) { + ShowError("skill_improvise_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) { + ShowError("skill_improvise_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + if( j < 1 ) { + ShowError("skill_improvise_db: Chances have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + if( current >= MAX_SKILL_IMPROVISE_DB ) { + ShowError("skill_improvise_db: Maximum amount of entries reached (%d), increase MAX_SKILL_IMPROVISE_DB\n",MAX_SKILL_IMPROVISE_DB); + } + skill_improvise_db[current].skill_id = skill_id; + skill_improvise_db[current].per = j; // Still need confirm it. + + return true; +} +static bool skill_parse_row_magicmushroomdb(char* split[], int column, int current) +{// SkillID + uint16 skill_id = atoi(split[0]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + { + ShowError("magicmushroom_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) + { + ShowError("magicmushroom_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + + skill_magicmushroom_db[current].skill_id = skill_id; + + return true; +} + +static bool skill_parse_row_reproducedb(char* split[], int column, int current) { + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) + return false; + + skill_reproduce_db[idx] = true; + + return true; +} + + +static bool skill_parse_row_abradb(char* split[], int columns, int current) +{// skill_id,DummyName,RequiredHocusPocusLevel,Rate + uint16 skill_id = atoi(split[0]); + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + { + ShowError("abra_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) + { + ShowError("abra_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + + skill_abra_db[current].skill_id = skill_id; + skill_abra_db[current].req_lv = atoi(split[2]); + skill_abra_db[current].per = atoi(split[3]); + + return true; +} + +static bool skill_parse_row_changematerialdb(char* split[], int columns, int current) +{// ProductID,BaseRate,MakeAmount1,MakeAmountRate1...,MakeAmount5,MakeAmountRate5 + uint16 skill_id = atoi(split[0]); + short j = atoi(split[1]); + int x,y; + + for(x=0; x<MAX_SKILL_PRODUCE_DB; x++){ + if( skill_produce_db[x].nameid == skill_id ) + if( skill_produce_db[x].req_skill == GN_CHANGEMATERIAL ) + break; + } + + if( x >= MAX_SKILL_PRODUCE_DB ){ + ShowError("changematerial_db: Not supported item ID(%d) for Change Material. \n", skill_id); + return false; + } + + if( current >= MAX_SKILL_PRODUCE_DB ) { + ShowError("skill_changematerial_db: Maximum amount of entries reached (%d), increase MAX_SKILL_PRODUCE_DB\n",MAX_SKILL_PRODUCE_DB); + } + + skill_changematerial_db[current].itemid = skill_id; + skill_changematerial_db[current].rate = j; + + for( x = 2, y = 0; x+1 < columns && split[x] && split[x+1] && y < 5; x += 2, y++ ) + { + skill_changematerial_db[current].qty[y] = atoi(split[x]); + skill_changematerial_db[current].qty_rate[y] = atoi(split[x+1]); + } + + return true; +} + +/*=============================== + * DB reading. + * skill_db.txt + * skill_require_db.txt + * skill_cast_db.txt + * skill_castnodex_db.txt + * skill_nocast_db.txt + * skill_unit_db.txt + * produce_db.txt + * create_arrow_db.txt + * abra_db.txt + *------------------------------*/ +static void skill_readdb(void) +{ + // init skill db structures + db_clear(skilldb_name2id); + memset(skill_db,0,sizeof(skill_db)); + memset(skill_produce_db,0,sizeof(skill_produce_db)); + memset(skill_arrow_db,0,sizeof(skill_arrow_db)); + memset(skill_abra_db,0,sizeof(skill_abra_db)); + memset(skill_spellbook_db,0,sizeof(skill_spellbook_db)); + memset(skill_magicmushroom_db,0,sizeof(skill_magicmushroom_db)); + memset(skill_reproduce_db,0,sizeof(skill_reproduce_db)); + memset(skill_changematerial_db,0,sizeof(skill_changematerial_db)); + + // load skill databases + safestrncpy(skill_db[0].name, "UNKNOWN_SKILL", sizeof(skill_db[0].name)); + safestrncpy(skill_db[0].desc, "Unknown Skill", sizeof(skill_db[0].desc)); + + sv_readdb(db_path, DBPATH"skill_db.txt" , ',', 17, 17, MAX_SKILL_DB, skill_parse_row_skilldb); + sv_readdb(db_path, DBPATH"skill_require_db.txt" , ',', 32, 32, MAX_SKILL_DB, skill_parse_row_requiredb); +#ifdef RENEWAL_CAST + sv_readdb(db_path, "re/skill_cast_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_castdb); +#else + sv_readdb(db_path, "pre-re/skill_cast_db.txt" , ',', 7, 7, MAX_SKILL_DB, skill_parse_row_castdb); +#endif + sv_readdb(db_path, DBPATH"skill_castnodex_db.txt", ',', 2, 3, MAX_SKILL_DB, skill_parse_row_castnodexdb); + sv_readdb(db_path, DBPATH"skill_unit_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_unitdb); + + sv_readdb(db_path, DBPATH"skill_nocast_db.txt" , ',', 2, 2, MAX_SKILL_DB, skill_parse_row_nocastdb); + + skill_init_unit_layout(); + sv_readdb(db_path, "produce_db.txt" , ',', 4, 4+2*MAX_PRODUCE_RESOURCE, MAX_SKILL_PRODUCE_DB, skill_parse_row_producedb); + sv_readdb(db_path, "create_arrow_db.txt" , ',', 1+2, 1+2*MAX_ARROW_RESOURCE, MAX_SKILL_ARROW_DB, skill_parse_row_createarrowdb); + sv_readdb(db_path, "abra_db.txt" , ',', 4, 4, MAX_SKILL_ABRA_DB, skill_parse_row_abradb); + //Warlock + sv_readdb(db_path, "spellbook_db.txt" , ',', 3, 3, MAX_SKILL_SPELLBOOK_DB, skill_parse_row_spellbookdb); + //Guillotine Cross + sv_readdb(db_path, "magicmushroom_db.txt" , ',', 1, 1, MAX_SKILL_MAGICMUSHROOM_DB, skill_parse_row_magicmushroomdb); + sv_readdb(db_path, "skill_reproduce_db.txt", ',', 1, 1, MAX_SKILL_DB, skill_parse_row_reproducedb); + sv_readdb(db_path, "skill_improvise_db.txt" , ',', 2, 2, MAX_SKILL_IMPROVISE_DB, skill_parse_row_improvisedb); + sv_readdb(db_path, "skill_changematerial_db.txt" , ',', 4, 4+2*5, MAX_SKILL_PRODUCE_DB, skill_parse_row_changematerialdb); + +} + +void skill_reload (void) { + struct s_mapiterator *iter; + struct map_session_data *sd; + skill_readdb(); + /* lets update all players skill tree : so that if any skill modes were changed they're properly updated */ + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + clif_skillinfoblock(sd); + mapit_free(iter); + +} + +/*========================================== + * + *------------------------------------------*/ +int do_init_skill (void) +{ + skilldb_name2id = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, 0); + skill_readdb(); + + group_db = idb_alloc(DB_OPT_BASE); + skillunit_db = idb_alloc(DB_OPT_BASE); + skillcd_db = idb_alloc(DB_OPT_RELEASE_DATA); + skillusave_db = idb_alloc(DB_OPT_RELEASE_DATA); + skill_unit_ers = ers_new(sizeof(struct skill_unit_group),"skill.c::skill_unit_ers",ERS_OPT_NONE); + skill_timer_ers = ers_new(sizeof(struct skill_timerskill),"skill.c::skill_timer_ers",ERS_OPT_NONE); + + add_timer_func_list(skill_unit_timer,"skill_unit_timer"); + add_timer_func_list(skill_castend_id,"skill_castend_id"); + add_timer_func_list(skill_castend_pos,"skill_castend_pos"); + add_timer_func_list(skill_timerskill,"skill_timerskill"); + add_timer_func_list(skill_blockpc_end, "skill_blockpc_end"); + + add_timer_interval(gettick()+SKILLUNITTIMER_INTERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INTERVAL); + + return 0; +} + +int do_final_skill(void) +{ + db_destroy(skilldb_name2id); + db_destroy(group_db); + db_destroy(skillunit_db); + db_destroy(skillcd_db); + db_destroy(skillusave_db); + ers_destroy(skill_unit_ers); + ers_destroy(skill_timer_ers); + return 0; +} diff --git a/src/map/skill.h b/src/map/skill.h new file mode 100644 index 000000000..dc7499857 --- /dev/null +++ b/src/map/skill.h @@ -0,0 +1,1863 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SKILL_H_ +#define _SKILL_H_ + +#include "../common/mmo.h" // MAX_SKILL, struct square +#include "map.h" // struct block_list +struct map_session_data; +struct homun_data; +struct skill_unit; +struct skill_unit_group; +struct status_change_entry; + +#define MAX_SKILL_DB MAX_SKILL +#define MAX_SKILL_PRODUCE_DB 270 +#define MAX_PRODUCE_RESOURCE 12 +#define MAX_SKILL_ARROW_DB 150 +#define MAX_ARROW_RESOURCE 5 +#define MAX_SKILL_ABRA_DB 350 +#define MAX_SKILL_IMPROVISE_DB 50 + +#define MAX_SKILL_LEVEL 100 + +//Constants to identify the skill's inf value: +enum e_skill_inf +{ + INF_ATTACK_SKILL = 0x01, + INF_GROUND_SKILL = 0x02, + INF_SELF_SKILL = 0x04, // Skills casted on self where target is automatically chosen + // 0x08 not assigned + INF_SUPPORT_SKILL = 0x10, + INF_TARGET_TRAP = 0x20, +}; + +//Constants to identify a skill's nk value (damage properties) +//The NK value applies only to non INF_GROUND_SKILL skills +//when determining skill castend function to invoke. +enum e_skill_nk +{ + NK_NO_DAMAGE = 0x01, + NK_SPLASH = 0x02|0x04, // 0x4 = splash & split + NK_SPLASHSPLIT = 0x04, + NK_NO_CARDFIX_ATK = 0x08, + NK_NO_ELEFIX = 0x10, + NK_IGNORE_DEF = 0x20, + NK_IGNORE_FLEE = 0x40, + NK_NO_CARDFIX_DEF = 0x80, +}; + +//A skill with 3 would be no damage + splash: area of effect. +//Constants to identify a skill's inf2 value. +enum e_skill_inf2 +{ + INF2_QUEST_SKILL = 0x0001, + INF2_NPC_SKILL = 0x0002, //NPC skills are those that players can't have in their skill tree. + INF2_WEDDING_SKILL = 0x0004, + INF2_SPIRIT_SKILL = 0x0008, + INF2_GUILD_SKILL = 0x0010, + INF2_SONG_DANCE = 0x0020, + INF2_ENSEMBLE_SKILL = 0x0040, + INF2_TRAP = 0x0080, + INF2_TARGET_SELF = 0x0100, //Refers to ground placed skills that will target the caster as well (like Grandcross) + INF2_NO_TARGET_SELF = 0x0200, + INF2_PARTY_ONLY = 0x0400, + INF2_GUILD_ONLY = 0x0800, + INF2_NO_ENEMY = 0x1000, + INF2_NOLP = 0x2000, // Spells that can ignore Land Protector + INF2_CHORUS_SKILL = 0x4000, // Chorus skill +}; + +//Walk intervals at which chase-skills are attempted to be triggered. +#define WALK_SKILL_INTERVAL 5 + +// Flags passed to skill_attack/skill_area_sub +enum e_skill_display +{ + SD_LEVEL = 0x1000, // skill_attack will send -1 instead of skill level (affects display of some skills) + SD_ANIMATION = 0x2000, // skill_attack will use '5' instead of the skill's 'type' (this makes skills show an animation) + SD_SPLASH = 0x4000, // skill_area_sub will count targets in skill_area_temp[2] + SD_PREAMBLE = 0x8000, // skill_area_sub will transmit a 'magic' damage packet (-30000 dmg) for the first target selected +}; + +#define MAX_SKILL_ITEM_REQUIRE 10 +struct skill_condition { + int weapon,ammo,ammo_qty,hp,sp,zeny,spiritball,mhp,state; + int itemid[MAX_SKILL_ITEM_REQUIRE],amount[MAX_SKILL_ITEM_REQUIRE]; +}; + +// Database skills + +struct s_skill_db { + char name[NAME_LENGTH]; + char desc[40]; + int range[MAX_SKILL_LEVEL],hit,inf,element[MAX_SKILL_LEVEL],nk,splash[MAX_SKILL_LEVEL],max; + int num[MAX_SKILL_LEVEL]; + int cast[MAX_SKILL_LEVEL],walkdelay[MAX_SKILL_LEVEL],delay[MAX_SKILL_LEVEL]; +#ifdef RENEWAL_CAST + int fixed_cast[MAX_SKILL_LEVEL]; +#endif + int upkeep_time[MAX_SKILL_LEVEL],upkeep_time2[MAX_SKILL_LEVEL],cooldown[MAX_SKILL_LEVEL]; + int castcancel,cast_def_rate; + int inf2,maxcount[MAX_SKILL_LEVEL],skill_type; + int blewcount[MAX_SKILL_LEVEL]; + int hp[MAX_SKILL_LEVEL],sp[MAX_SKILL_LEVEL],mhp[MAX_SKILL_LEVEL],hp_rate[MAX_SKILL_LEVEL],sp_rate[MAX_SKILL_LEVEL],zeny[MAX_SKILL_LEVEL]; + int weapon,ammo,ammo_qty[MAX_SKILL_LEVEL],state,spiritball[MAX_SKILL_LEVEL]; + int itemid[MAX_SKILL_ITEM_REQUIRE],amount[MAX_SKILL_ITEM_REQUIRE]; + int castnodex[MAX_SKILL_LEVEL], delaynodex[MAX_SKILL_LEVEL]; + int nocast; + int unit_id[2]; + int unit_layout_type[MAX_SKILL_LEVEL]; + int unit_range[MAX_SKILL_LEVEL]; + int unit_interval; + int unit_target; + int unit_flag; +}; +extern struct s_skill_db skill_db[MAX_SKILL_DB]; + +#define MAX_SKILL_UNIT_LAYOUT 50 +#define MAX_SQUARE_LAYOUT 5 // 11*11 Placement of a maximum unit +#define MAX_SKILL_UNIT_COUNT ((MAX_SQUARE_LAYOUT*2+1)*(MAX_SQUARE_LAYOUT*2+1)) +struct s_skill_unit_layout { + int count; + int dx[MAX_SKILL_UNIT_COUNT]; + int dy[MAX_SKILL_UNIT_COUNT]; +}; + +#define MAX_SKILLTIMERSKILL 15 +struct skill_timerskill { + int timer; + int src_id; + int target_id; + int map; + short x,y; + uint16 skill_id,skill_lv; + int type; // a BF_ type (NOTE: some places use this as general-purpose storage...) + int flag; +}; + +#define MAX_SKILLUNITGROUP 25 +struct skill_unit_group { + int src_id; + int party_id; + int guild_id; + int bg_id; + int map; + int target_flag; //Holds BCT_* flag for battle_check_target + int bl_flag; //Holds BL_* flag for map_foreachin* functions + unsigned int tick; + int limit,interval; + + uint16 skill_id,skill_lv; + int val1,val2,val3; + char *valstr; + int unit_id; + int group_id; + int unit_count,alive_count; + int item_id; //store item used. + struct skill_unit *unit; + struct { + unsigned ammo_consume : 1; + unsigned song_dance : 2; //0x1 Song/Dance, 0x2 Ensemble + unsigned guildaura : 1; + } state; +}; + +struct skill_unit { + struct block_list bl; + + struct skill_unit_group *group; + + int limit; + int val1,val2; + short alive,range; +}; + +#define MAX_SKILLUNITGROUPTICKSET 25 +struct skill_unit_group_tickset { + unsigned int tick; + int id; +}; + + +enum { + UF_DEFNOTENEMY = 0x0001, // If 'defunit_not_enemy' is set, the target is changed to 'friend' + UF_NOREITERATION = 0x0002, // Spell cannot be stacked + UF_NOFOOTSET = 0x0004, // Spell cannot be cast near/on targets + UF_NOOVERLAP = 0x0008, // Spell effects do not overlap + UF_PATHCHECK = 0x0010, // Only cells with a shootable path will be placed + UF_NOPC = 0x0020, // May not target players + UF_NOMOB = 0x0040, // May not target mobs + UF_SKILL = 0x0080, // May target skills + UF_DANCE = 0x0100, // Dance + UF_ENSEMBLE = 0x0200, // Duet + UF_SONG = 0x0400, // Song + UF_DUALMODE = 0x0800, // Spells should trigger both ontimer and onplace/onout/onleft effects. + UF_RANGEDSINGLEUNIT = 0x2000 // hack for ranged layout, only display center +}; + +// Create Database item + +struct s_skill_produce_db { + int nameid, trigger; + int req_skill,req_skill_lv,itemlv; + int mat_id[MAX_PRODUCE_RESOURCE],mat_amount[MAX_PRODUCE_RESOURCE]; +}; +extern struct s_skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; + +// Creating database arrow + +struct s_skill_arrow_db { + int nameid, trigger; + int cre_id[MAX_ARROW_RESOURCE],cre_amount[MAX_ARROW_RESOURCE]; +}; +extern struct s_skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; + +// Abracadabra database + +struct s_skill_abra_db { + uint16 skill_id; + int req_lv; + int per; +}; +extern struct s_skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; + +extern int enchant_eff[5]; +extern int deluge_eff[5]; + +int do_init_skill(void); +int do_final_skill(void); + +//Returns the cast type of the skill: ground cast, castend damage, castend no damage +enum { CAST_GROUND, CAST_DAMAGE, CAST_NODAMAGE }; +int skill_get_casttype(uint16 skill_id); //[Skotlex] + +// Accessor to the skills database +// +int skill_get_index( uint16 skill_id ); +int skill_get_type( uint16 skill_id ); +int skill_get_hit( uint16 skill_id ); +int skill_get_inf( uint16 skill_id ); +int skill_get_ele( uint16 skill_id , uint16 skill_lv ); +int skill_get_nk( uint16 skill_id ); +int skill_get_max( uint16 skill_id ); +int skill_get_range( uint16 skill_id , uint16 skill_lv ); +int skill_get_range2(struct block_list *bl, uint16 skill_id, uint16 skill_lv); +int skill_get_splash( uint16 skill_id , uint16 skill_lv ); +int skill_get_hp( uint16 skill_id ,uint16 skill_lv ); +int skill_get_mhp( uint16 skill_id ,uint16 skill_lv ); +int skill_get_sp( uint16 skill_id ,uint16 skill_lv ); +int skill_get_state(uint16 skill_id); +int skill_get_zeny( uint16 skill_id ,uint16 skill_lv ); +int skill_get_num( uint16 skill_id ,uint16 skill_lv ); +int skill_get_cast( uint16 skill_id ,uint16 skill_lv ); +int skill_get_delay( uint16 skill_id ,uint16 skill_lv ); +int skill_get_walkdelay( uint16 skill_id ,uint16 skill_lv ); +int skill_get_time( uint16 skill_id ,uint16 skill_lv ); +int skill_get_time2( uint16 skill_id ,uint16 skill_lv ); +int skill_get_castnodex( uint16 skill_id ,uint16 skill_lv ); +int skill_get_castdef( uint16 skill_id ); +int skill_get_weapontype( uint16 skill_id ); +int skill_get_ammotype( uint16 skill_id ); +int skill_get_ammo_qty( uint16 skill_id, uint16 skill_lv ); +int skill_get_nocast( uint16 skill_id ); +int skill_get_unit_id(uint16 skill_id,int flag); +int skill_get_inf2( uint16 skill_id ); +int skill_get_castcancel( uint16 skill_id ); +int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ); +int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ); +int skill_get_unit_flag( uint16 skill_id ); +int skill_get_unit_target( uint16 skill_id ); +int skill_tree_get_max( uint16 skill_id, int b_class ); // Celest +const char* skill_get_name( uint16 skill_id ); // [Skotlex] +const char* skill_get_desc( uint16 skill_id ); // [Skotlex] + +int skill_name2id(const char* name); + +int skill_isammotype(struct map_session_data *sd, int skill); +int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data); +int skill_castend_pos(int tid, unsigned int tick, int id, intptr_t data); +int skill_castend_map( struct map_session_data *sd,uint16 skill_id, const char *map); + +int skill_cleartimerskill(struct block_list *src); +int skill_addtimerskill(struct block_list *src,unsigned int tick,int target,int x,int y,uint16 skill_id,uint16 skill_lv,int type,int flag); + +// Results? Added +int skill_additional_effect( struct block_list* src, struct block_list *bl,uint16 skill_id,uint16 skill_lv,int attack_type,int dmg_lv,unsigned int tick); +int skill_counter_additional_effect( struct block_list* src, struct block_list *bl,uint16 skill_id,uint16 skill_lv,int attack_type,unsigned int tick); +int skill_blown(struct block_list* src, struct block_list* target, int count, int8 dir, int flag); +int skill_break_equip(struct block_list *bl, unsigned short where, int rate, int flag); +int skill_strip_equip(struct block_list *bl, unsigned short where, int rate, int lv, int time); +// Skills unit +struct skill_unit_group* skill_id2group(int group_id); +struct skill_unit_group *skill_unitsetting(struct block_list* src, uint16 skill_id, uint16 skill_lv, short x, short y, int flag); +struct skill_unit *skill_initunit (struct skill_unit_group *group, int idx, int x, int y, int val1, int val2); +int skill_delunit(struct skill_unit *unit); +struct skill_unit_group *skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, int limit, int interval); +int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func); +#define skill_delunitgroup(group) skill_delunitgroup_(group,__FILE__,__LINE__,__func__) +int skill_clear_unitgroup(struct block_list *src); +int skill_clear_group(struct block_list *bl, int flag); +void ext_skill_unit_onplace(struct skill_unit *src, struct block_list *bl, unsigned int tick); + +int skill_unit_ondamaged(struct skill_unit *src,struct block_list *bl,int damage,unsigned int tick); + +int skill_castfix( struct block_list *bl, uint16 skill_id, uint16 skill_lv); +int skill_castfix_sc( struct block_list *bl, int time); +#ifdef RENEWAL_CAST +int skill_vfcastfix( struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv); +#endif +int skill_delayfix( struct block_list *bl, uint16 skill_id, uint16 skill_lv); + +// Skill conditions check and remove [Inkfish] +int skill_check_condition_castbegin(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); +int skill_check_condition_castend(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); +int skill_consume_requirement(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type); +struct skill_condition skill_get_requirement(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); + +int skill_check_pc_partner(struct map_session_data *sd, uint16 skill_id, short* skill_lv, int range, int cast_flag); +// -- moonsoul (added skill_check_unit_cell) +int skill_check_unit_cell(uint16 skill_id,int16 m,int16 x,int16 y,int unit_id); +int skill_unit_out_all( struct block_list *bl,unsigned int tick,int range); +int skill_unit_move(struct block_list *bl,unsigned int tick,int flag); +int skill_unit_move_unit_group( struct skill_unit_group *group, int16 m,int16 dx,int16 dy); + +struct skill_unit_group *skill_check_dancing( struct block_list *src ); + +// Guild skills [celest] +int skill_guildaura_sub (struct map_session_data* sd, int id, int strvit, int agidex); + +// Chant canceled +int skill_castcancel(struct block_list *bl,int type); + +int skill_sit (struct map_session_data *sd, int type); +void skill_brandishspear(struct block_list* src, struct block_list* bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag); +void skill_repairweapon(struct map_session_data *sd, int idx); +void skill_identify(struct map_session_data *sd,int idx); +void skill_weaponrefine(struct map_session_data *sd,int idx); // [Celest] +int skill_autospell(struct map_session_data *md,uint16 skill_id); + +int skill_calc_heal(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal); + +bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce); + +// Abnormal status +int skill_enchant_elemental_end(struct block_list *bl, int type); +int skillnotok(uint16 skill_id, struct map_session_data *sd); +int skillnotok_hom(uint16 skill_id, struct homun_data *hd); +int skillnotok_mercenary(uint16 skill_id, struct mercenary_data *md); + +int skill_chastle_mob_changetarget(struct block_list *bl,va_list ap); + +// Item creation +int skill_can_produce_mix( struct map_session_data *sd, int nameid, int trigger, int qty); +int skill_produce_mix( struct map_session_data *sd, uint16 skill_id, int nameid, int slot1, int slot2, int slot3, int qty ); + +int skill_arrow_create( struct map_session_data *sd,int nameid); + +// skills for the mob +int skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,uint16 skill_id,uint16 skill_lv,unsigned int tick,int flag ); +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,uint16 skill_id,uint16 skill_lv,unsigned int tick,int flag ); +int skill_castend_pos2( struct block_list *src, int x,int y,uint16 skill_id,uint16 skill_lv,unsigned int tick,int flag); + +int skill_blockpc_start_(struct map_session_data*, uint16 skill_id, int, bool); +int skill_blockhomun_start (struct homun_data*,uint16 skill_id,int); +int skill_blockmerc_start (struct mercenary_data*,uint16 skill_id,int); + +#define skill_blockpc_start(sd, skill_id, tick) skill_blockpc_start_( sd, skill_id, tick, false ) + +// (Epoque:) To-do: replace this macro with some sort of skill tree check (rather than hard-coded skill names) +#define skill_ischangesex(id) ( \ + ((id) >= BD_ADAPTATION && (id) <= DC_SERVICEFORYOU) || ((id) >= CG_ARROWVULCAN && (id) <= CG_MARIONETTE) || \ + ((id) >= CG_LONGINGFREEDOM && (id) <= CG_TAROTCARD) || ((id) >= WA_SWING_DANCE && (id) <= WM_UNLIMITED_HUMMING_VOICE)) + +// Skill action, (return dmg,heal) +int skill_attack( int attack_type, struct block_list* src, struct block_list *dsrc,struct block_list *bl,uint16 skill_id,uint16 skill_lv,unsigned int tick,int flag ); + +void skill_reload(void); + +enum { + ST_NONE, + ST_HIDING, + ST_CLOAKING, + ST_HIDDEN, + ST_RIDING, + ST_FALCON, + ST_CART, + ST_SHIELD, + ST_SIGHT, + ST_EXPLOSIONSPIRITS, + ST_CARTBOOST, + ST_RECOV_WEIGHT_RATE, + ST_MOVE_ENABLE, + ST_WATER, + /** + * 3rd States + **/ + ST_RIDINGDRAGON, + ST_WUG, + ST_RIDINGWUG, + ST_MADO, + ST_ELEMENTALSPIRIT, + ST_POISONINGWEAPON, + ST_ROLLINGCUTTER, + ST_MH_FIGHTING, + ST_MH_GRAPPLING, +}; + +enum e_skill { + NV_BASIC = 1, + + SM_SWORD, + SM_TWOHAND, + SM_RECOVERY, + SM_BASH, + SM_PROVOKE, + SM_MAGNUM, + SM_ENDURE, + + MG_SRECOVERY, + MG_SIGHT, + MG_NAPALMBEAT, + MG_SAFETYWALL, + MG_SOULSTRIKE, + MG_COLDBOLT, + MG_FROSTDIVER, + MG_STONECURSE, + MG_FIREBALL, + MG_FIREWALL, + MG_FIREBOLT, + MG_LIGHTNINGBOLT, + MG_THUNDERSTORM, + + AL_DP, + AL_DEMONBANE, + AL_RUWACH, + AL_PNEUMA, + AL_TELEPORT, + AL_WARP, + AL_HEAL, + AL_INCAGI, + AL_DECAGI, + AL_HOLYWATER, + AL_CRUCIS, + AL_ANGELUS, + AL_BLESSING, + AL_CURE, + + MC_INCCARRY, + MC_DISCOUNT, + MC_OVERCHARGE, + MC_PUSHCART, + MC_IDENTIFY, + MC_VENDING, + MC_MAMMONITE, + + AC_OWL, + AC_VULTURE, + AC_CONCENTRATION, + AC_DOUBLE, + AC_SHOWER, + + TF_DOUBLE, + TF_MISS, + TF_STEAL, + TF_HIDING, + TF_POISON, + TF_DETOXIFY, + + ALL_RESURRECTION, + + KN_SPEARMASTERY, + KN_PIERCE, + KN_BRANDISHSPEAR, + KN_SPEARSTAB, + KN_SPEARBOOMERANG, + KN_TWOHANDQUICKEN, + KN_AUTOCOUNTER, + KN_BOWLINGBASH, + KN_RIDING, + KN_CAVALIERMASTERY, + + PR_MACEMASTERY, + PR_IMPOSITIO, + PR_SUFFRAGIUM, + PR_ASPERSIO, + PR_BENEDICTIO, + PR_SANCTUARY, + PR_SLOWPOISON, + PR_STRECOVERY, + PR_KYRIE, + PR_MAGNIFICAT, + PR_GLORIA, + PR_LEXDIVINA, + PR_TURNUNDEAD, + PR_LEXAETERNA, + PR_MAGNUS, + + WZ_FIREPILLAR, + WZ_SIGHTRASHER, + WZ_FIREIVY, + WZ_METEOR, + WZ_JUPITEL, + WZ_VERMILION, + WZ_WATERBALL, + WZ_ICEWALL, + WZ_FROSTNOVA, + WZ_STORMGUST, + WZ_EARTHSPIKE, + WZ_HEAVENDRIVE, + WZ_QUAGMIRE, + WZ_ESTIMATION, + + BS_IRON, + BS_STEEL, + BS_ENCHANTEDSTONE, + BS_ORIDEOCON, + BS_DAGGER, + BS_SWORD, + BS_TWOHANDSWORD, + BS_AXE, + BS_MACE, + BS_KNUCKLE, + BS_SPEAR, + BS_HILTBINDING, + BS_FINDINGORE, + BS_WEAPONRESEARCH, + BS_REPAIRWEAPON, + BS_SKINTEMPER, + BS_HAMMERFALL, + BS_ADRENALINE, + BS_WEAPONPERFECT, + BS_OVERTHRUST, + BS_MAXIMIZE, + + HT_SKIDTRAP, + HT_LANDMINE, + HT_ANKLESNARE, + HT_SHOCKWAVE, + HT_SANDMAN, + HT_FLASHER, + HT_FREEZINGTRAP, + HT_BLASTMINE, + HT_CLAYMORETRAP, + HT_REMOVETRAP, + HT_TALKIEBOX, + HT_BEASTBANE, + HT_FALCON, + HT_STEELCROW, + HT_BLITZBEAT, + HT_DETECTING, + HT_SPRINGTRAP, + + AS_RIGHT, + AS_LEFT, + AS_KATAR, + AS_CLOAKING, + AS_SONICBLOW, + AS_GRIMTOOTH, + AS_ENCHANTPOISON, + AS_POISONREACT, + AS_VENOMDUST, + AS_SPLASHER, + + NV_FIRSTAID, + NV_TRICKDEAD, + SM_MOVINGRECOVERY, + SM_FATALBLOW, + SM_AUTOBERSERK, + AC_MAKINGARROW, + AC_CHARGEARROW, + TF_SPRINKLESAND, + TF_BACKSLIDING, + TF_PICKSTONE, + TF_THROWSTONE, + MC_CARTREVOLUTION, + MC_CHANGECART, + MC_LOUD, + AL_HOLYLIGHT, + MG_ENERGYCOAT, + + NPC_PIERCINGATT, + NPC_MENTALBREAKER, + NPC_RANGEATTACK, + NPC_ATTRICHANGE, + NPC_CHANGEWATER, + NPC_CHANGEGROUND, + NPC_CHANGEFIRE, + NPC_CHANGEWIND, + NPC_CHANGEPOISON, + NPC_CHANGEHOLY, + NPC_CHANGEDARKNESS, + NPC_CHANGETELEKINESIS, + NPC_CRITICALSLASH, + NPC_COMBOATTACK, + NPC_GUIDEDATTACK, + NPC_SELFDESTRUCTION, + NPC_SPLASHATTACK, + NPC_SUICIDE, + NPC_POISON, + NPC_BLINDATTACK, + NPC_SILENCEATTACK, + NPC_STUNATTACK, + NPC_PETRIFYATTACK, + NPC_CURSEATTACK, + NPC_SLEEPATTACK, + NPC_RANDOMATTACK, + NPC_WATERATTACK, + NPC_GROUNDATTACK, + NPC_FIREATTACK, + NPC_WINDATTACK, + NPC_POISONATTACK, + NPC_HOLYATTACK, + NPC_DARKNESSATTACK, + NPC_TELEKINESISATTACK, + NPC_MAGICALATTACK, + NPC_METAMORPHOSIS, + NPC_PROVOCATION, + NPC_SMOKING, + NPC_SUMMONSLAVE, + NPC_EMOTION, + NPC_TRANSFORMATION, + NPC_BLOODDRAIN, + NPC_ENERGYDRAIN, + NPC_KEEPING, + NPC_DARKBREATH, + NPC_DARKBLESSING, + NPC_BARRIER, + NPC_DEFENDER, + NPC_LICK, + NPC_HALLUCINATION, + NPC_REBIRTH, + NPC_SUMMONMONSTER, + + RG_SNATCHER, + RG_STEALCOIN, + RG_BACKSTAP, + RG_TUNNELDRIVE, + RG_RAID, + RG_STRIPWEAPON, + RG_STRIPSHIELD, + RG_STRIPARMOR, + RG_STRIPHELM, + RG_INTIMIDATE, + RG_GRAFFITI, + RG_FLAGGRAFFITI, + RG_CLEANER, + RG_GANGSTER, + RG_COMPULSION, + RG_PLAGIARISM, + + AM_AXEMASTERY, + AM_LEARNINGPOTION, + AM_PHARMACY, + AM_DEMONSTRATION, + AM_ACIDTERROR, + AM_POTIONPITCHER, + AM_CANNIBALIZE, + AM_SPHEREMINE, + AM_CP_WEAPON, + AM_CP_SHIELD, + AM_CP_ARMOR, + AM_CP_HELM, + AM_BIOETHICS, + AM_BIOTECHNOLOGY, + AM_CREATECREATURE, + AM_CULTIVATION, + AM_FLAMECONTROL, + AM_CALLHOMUN, + AM_REST, + AM_DRILLMASTER, + AM_HEALHOMUN, + AM_RESURRECTHOMUN, + + CR_TRUST, + CR_AUTOGUARD, + CR_SHIELDCHARGE, + CR_SHIELDBOOMERANG, + CR_REFLECTSHIELD, + CR_HOLYCROSS, + CR_GRANDCROSS, + CR_DEVOTION, + CR_PROVIDENCE, + CR_DEFENDER, + CR_SPEARQUICKEN, + + MO_IRONHAND, + MO_SPIRITSRECOVERY, + MO_CALLSPIRITS, + MO_ABSORBSPIRITS, + MO_TRIPLEATTACK, + MO_BODYRELOCATION, + MO_DODGE, + MO_INVESTIGATE, + MO_FINGEROFFENSIVE, + MO_STEELBODY, + MO_BLADESTOP, + MO_EXPLOSIONSPIRITS, + MO_EXTREMITYFIST, + MO_CHAINCOMBO, + MO_COMBOFINISH, + + SA_ADVANCEDBOOK, + SA_CASTCANCEL, + SA_MAGICROD, + SA_SPELLBREAKER, + SA_FREECAST, + SA_AUTOSPELL, + SA_FLAMELAUNCHER, + SA_FROSTWEAPON, + SA_LIGHTNINGLOADER, + SA_SEISMICWEAPON, + SA_DRAGONOLOGY, + SA_VOLCANO, + SA_DELUGE, + SA_VIOLENTGALE, + SA_LANDPROTECTOR, + SA_DISPELL, + SA_ABRACADABRA, + SA_MONOCELL, + SA_CLASSCHANGE, + SA_SUMMONMONSTER, + SA_REVERSEORCISH, + SA_DEATH, + SA_FORTUNE, + SA_TAMINGMONSTER, + SA_QUESTION, + SA_GRAVITY, + SA_LEVELUP, + SA_INSTANTDEATH, + SA_FULLRECOVERY, + SA_COMA, + + BD_ADAPTATION, + BD_ENCORE, + BD_LULLABY, + BD_RICHMANKIM, + BD_ETERNALCHAOS, + BD_DRUMBATTLEFIELD, + BD_RINGNIBELUNGEN, + BD_ROKISWEIL, + BD_INTOABYSS, + BD_SIEGFRIED, + BD_RAGNAROK, + + BA_MUSICALLESSON, + BA_MUSICALSTRIKE, + BA_DISSONANCE, + BA_FROSTJOKER, + BA_WHISTLE, + BA_ASSASSINCROSS, + BA_POEMBRAGI, + BA_APPLEIDUN, + + DC_DANCINGLESSON, + DC_THROWARROW, + DC_UGLYDANCE, + DC_SCREAM, + DC_HUMMING, + DC_DONTFORGETME, + DC_FORTUNEKISS, + DC_SERVICEFORYOU, + + NPC_RANDOMMOVE, + NPC_SPEEDUP, + NPC_REVENGE, + + WE_MALE, + WE_FEMALE, + WE_CALLPARTNER, + + ITM_TOMAHAWK, + + NPC_DARKCROSS, + NPC_GRANDDARKNESS, + NPC_DARKSTRIKE, + NPC_DARKTHUNDER, + NPC_STOP, + NPC_WEAPONBRAKER, + NPC_ARMORBRAKE, + NPC_HELMBRAKE, + NPC_SHIELDBRAKE, + NPC_UNDEADATTACK, + NPC_CHANGEUNDEAD, + NPC_POWERUP, + NPC_AGIUP, + NPC_SIEGEMODE, + NPC_CALLSLAVE, + NPC_INVISIBLE, + NPC_RUN, + + LK_AURABLADE, + LK_PARRYING, + LK_CONCENTRATION, + LK_TENSIONRELAX, + LK_BERSERK, + LK_FURY, + HP_ASSUMPTIO, + HP_BASILICA, + HP_MEDITATIO, + HW_SOULDRAIN, + HW_MAGICCRASHER, + HW_MAGICPOWER, + PA_PRESSURE, + PA_SACRIFICE, + PA_GOSPEL, + CH_PALMSTRIKE, + CH_TIGERFIST, + CH_CHAINCRUSH, + PF_HPCONVERSION, + PF_SOULCHANGE, + PF_SOULBURN, + ASC_KATAR, + ASC_HALLUCINATION, + ASC_EDP, + ASC_BREAKER, + SN_SIGHT, + SN_FALCONASSAULT, + SN_SHARPSHOOTING, + SN_WINDWALK, + WS_MELTDOWN, + WS_CREATECOIN, + WS_CREATENUGGET, + WS_CARTBOOST, + WS_SYSTEMCREATE, + ST_CHASEWALK, + ST_REJECTSWORD, + ST_STEALBACKPACK, + CR_ALCHEMY, + CR_SYNTHESISPOTION, + CG_ARROWVULCAN, + CG_MOONLIT, + CG_MARIONETTE, + LK_SPIRALPIERCE, + LK_HEADCRUSH, + LK_JOINTBEAT, + HW_NAPALMVULCAN, + CH_SOULCOLLECT, + PF_MINDBREAKER, + PF_MEMORIZE, + PF_FOGWALL, + PF_SPIDERWEB, + ASC_METEORASSAULT, + ASC_CDP, + WE_BABY, + WE_CALLPARENT, + WE_CALLBABY, + + TK_RUN, + TK_READYSTORM, + TK_STORMKICK, + TK_READYDOWN, + TK_DOWNKICK, + TK_READYTURN, + TK_TURNKICK, + TK_READYCOUNTER, + TK_COUNTER, + TK_DODGE, + TK_JUMPKICK, + TK_HPTIME, + TK_SPTIME, + TK_POWER, + TK_SEVENWIND, + TK_HIGHJUMP, + + SG_FEEL, + SG_SUN_WARM, + SG_MOON_WARM, + SG_STAR_WARM, + SG_SUN_COMFORT, + SG_MOON_COMFORT, + SG_STAR_COMFORT, + SG_HATE, + SG_SUN_ANGER, + SG_MOON_ANGER, + SG_STAR_ANGER, + SG_SUN_BLESS, + SG_MOON_BLESS, + SG_STAR_BLESS, + SG_DEVIL, + SG_FRIEND, + SG_KNOWLEDGE, + SG_FUSION, + + SL_ALCHEMIST, + AM_BERSERKPITCHER, + SL_MONK, + SL_STAR, + SL_SAGE, + SL_CRUSADER, + SL_SUPERNOVICE, + SL_KNIGHT, + SL_WIZARD, + SL_PRIEST, + SL_BARDDANCER, + SL_ROGUE, + SL_ASSASIN, + SL_BLACKSMITH, + BS_ADRENALINE2, + SL_HUNTER, + SL_SOULLINKER, + SL_KAIZEL, + SL_KAAHI, + SL_KAUPE, + SL_KAITE, + SL_KAINA, + SL_STIN, + SL_STUN, + SL_SMA, + SL_SWOO, + SL_SKE, + SL_SKA, + + SM_SELFPROVOKE, + NPC_EMOTION_ON, + ST_PRESERVE, + ST_FULLSTRIP, + WS_WEAPONREFINE, + CR_SLIMPITCHER, + CR_FULLPROTECTION, + PA_SHIELDCHAIN, + HP_MANARECHARGE, + PF_DOUBLECASTING, + HW_GANBANTEIN, + HW_GRAVITATION, + WS_CARTTERMINATION, + WS_OVERTHRUSTMAX, + CG_LONGINGFREEDOM, + CG_HERMODE, + CG_TAROTCARD, + CR_ACIDDEMONSTRATION, + CR_CULTIVATION, + ITEM_ENCHANTARMS, + TK_MISSION, + SL_HIGH, + KN_ONEHAND, + AM_TWILIGHT1, + AM_TWILIGHT2, + AM_TWILIGHT3, + HT_POWER, + + GS_GLITTERING, + GS_FLING, + GS_TRIPLEACTION, + GS_BULLSEYE, + GS_MADNESSCANCEL, + GS_ADJUSTMENT, + GS_INCREASING, + GS_MAGICALBULLET, + GS_CRACKER, + GS_SINGLEACTION, + GS_SNAKEEYE, + GS_CHAINACTION, + GS_TRACKING, + GS_DISARM, + GS_PIERCINGSHOT, + GS_RAPIDSHOWER, + GS_DESPERADO, + GS_GATLINGFEVER, + GS_DUST, + GS_FULLBUSTER, + GS_SPREADATTACK, + GS_GROUNDDRIFT, + + NJ_TOBIDOUGU, + NJ_SYURIKEN, + NJ_KUNAI, + NJ_HUUMA, + NJ_ZENYNAGE, + NJ_TATAMIGAESHI, + NJ_KASUMIKIRI, + NJ_SHADOWJUMP, + NJ_KIRIKAGE, + NJ_UTSUSEMI, + NJ_BUNSINJYUTSU, + NJ_NINPOU, + NJ_KOUENKA, + NJ_KAENSIN, + NJ_BAKUENRYU, + NJ_HYOUSENSOU, + NJ_SUITON, + NJ_HYOUSYOURAKU, + NJ_HUUJIN, + NJ_RAIGEKISAI, + NJ_KAMAITACHI, + NJ_NEN, + NJ_ISSEN, + + MB_FIGHTING, + MB_NEUTRAL, + MB_TAIMING_PUTI, + MB_WHITEPOTION, + MB_MENTAL, + MB_CARDPITCHER, + MB_PETPITCHER, + MB_BODYSTUDY, + MB_BODYALTER, + MB_PETMEMORY, + MB_M_TELEPORT, + MB_B_GAIN, + MB_M_GAIN, + MB_MISSION, + MB_MUNAKKNOWLEDGE, + MB_MUNAKBALL, + MB_SCROLL, + MB_B_GATHERING, + MB_M_GATHERING, + MB_B_EXCLUDE, + MB_B_DRIFT, + MB_B_WALLRUSH, + MB_M_WALLRUSH, + MB_B_WALLSHIFT, + MB_M_WALLCRASH, + MB_M_REINCARNATION, + MB_B_EQUIP, + + SL_DEATHKNIGHT, + SL_COLLECTOR, + SL_NINJA, + SL_GUNNER, + AM_TWILIGHT4, + DA_RESET, + DE_BERSERKAIZER, + DA_DARKPOWER, + + DE_PASSIVE, + DE_PATTACK, + DE_PSPEED, + DE_PDEFENSE, + DE_PCRITICAL, + DE_PHP, + DE_PSP, + DE_RESET, + DE_RANKING, + DE_PTRIPLE, + DE_ENERGY, + DE_NIGHTMARE, + DE_SLASH, + DE_COIL, + DE_WAVE, + DE_REBIRTH, + DE_AURA, + DE_FREEZER, + DE_CHANGEATTACK, + DE_PUNISH, + DE_POISON, + DE_INSTANT, + DE_WARNING, + DE_RANKEDKNIFE, + DE_RANKEDGRADIUS, + DE_GAUGE, + DE_GTIME, + DE_GPAIN, + DE_GSKILL, + DE_GKILL, + DE_ACCEL, + DE_BLOCKDOUBLE, + DE_BLOCKMELEE, + DE_BLOCKFAR, + DE_FRONTATTACK, + DE_DANGERATTACK, + DE_TWINATTACK, + DE_WINDATTACK, + DE_WATERATTACK, + + DA_ENERGY, + DA_CLOUD, + DA_FIRSTSLOT, + DA_HEADDEF, + DA_SPACE, + DA_TRANSFORM, + DA_EXPLOSION, + DA_REWARD, + DA_CRUSH, + DA_ITEMREBUILD, + DA_ILLUSION, + DA_NUETRALIZE, + DA_RUNNER, + DA_TRANSFER, + DA_WALL, + DA_ZENY, + DA_REVENGE, + DA_EARPLUG, + DA_CONTRACT, + DA_BLACK, + DA_DREAM, + DA_MAGICCART, + DA_COPY, + DA_CRYSTAL, + DA_EXP, + DA_CARTSWING, + DA_REBUILD, + DA_JOBCHANGE, + DA_EDARKNESS, + DA_EGUARDIAN, + DA_TIMEOUT, + ALL_TIMEIN, + DA_ZENYRANK, + DA_ACCESSORYMIX, + + NPC_EARTHQUAKE, + NPC_FIREBREATH, + NPC_ICEBREATH, + NPC_THUNDERBREATH, + NPC_ACIDBREATH, + NPC_DARKNESSBREATH, + NPC_DRAGONFEAR, + NPC_BLEEDING, + NPC_PULSESTRIKE, + NPC_HELLJUDGEMENT, + NPC_WIDESILENCE, + NPC_WIDEFREEZE, + NPC_WIDEBLEEDING, + NPC_WIDESTONE, + NPC_WIDECONFUSE, + NPC_WIDESLEEP, + NPC_WIDESIGHT, + NPC_EVILLAND, + NPC_MAGICMIRROR, + NPC_SLOWCAST, + NPC_CRITICALWOUND, + NPC_EXPULSION, + NPC_STONESKIN, + NPC_ANTIMAGIC, + NPC_WIDECURSE, + NPC_WIDESTUN, + NPC_VAMPIRE_GIFT, + NPC_WIDESOULDRAIN, + + ALL_INCCARRY, + NPC_TALK, + NPC_HELLPOWER, + NPC_WIDEHELLDIGNITY, + NPC_INVINCIBLE, + NPC_INVINCIBLEOFF, + NPC_ALLHEAL, + GM_SANDMAN, + CASH_BLESSING, + CASH_INCAGI, + CASH_ASSUMPTIO, + ALL_CATCRY, + ALL_PARTYFLEE, + ALL_ANGEL_PROTECT, + ALL_DREAM_SUMMERNIGHT, + NPC_CHANGEUNDEAD2, + ALL_REVERSEORCISH, + ALL_WEWISH, + ALL_SONKRAN, + NPC_WIDEHEALTHFEAR, + NPC_WIDEBODYBURNNING, + NPC_WIDEFROSTMISTY, + NPC_WIDECOLD, + NPC_WIDE_DEEP_SLEEP, + NPC_WIDESIREN, + NPC_VENOMFOG, + NPC_MILLENNIUMSHIELD, + NPC_COMET, + + KN_CHARGEATK = 1001, + CR_SHRINK, + AS_SONICACCEL, + AS_VENOMKNIFE, + RG_CLOSECONFINE, + WZ_SIGHTBLASTER, + SA_CREATECON, + SA_ELEMENTWATER, + HT_PHANTASMIC, + BA_PANGVOICE, + DC_WINKCHARM, + BS_UNFAIRLYTRICK, + BS_GREED, + PR_REDEMPTIO, + MO_KITRANSLATION, + MO_BALKYOUNG, + SA_ELEMENTGROUND, + SA_ELEMENTFIRE, + SA_ELEMENTWIND, + + RK_ENCHANTBLADE = 2001, + RK_SONICWAVE, + RK_DEATHBOUND, + RK_HUNDREDSPEAR, + RK_WINDCUTTER, + RK_IGNITIONBREAK, + RK_DRAGONTRAINING, + RK_DRAGONBREATH, + RK_DRAGONHOWLING, + RK_RUNEMASTERY, + RK_MILLENNIUMSHIELD, + RK_CRUSHSTRIKE, + RK_REFRESH, + RK_GIANTGROWTH, + RK_STONEHARDSKIN, + RK_VITALITYACTIVATION, + RK_STORMBLAST, + RK_FIGHTINGSPIRIT, + RK_ABUNDANCE, + RK_PHANTOMTHRUST, + + GC_VENOMIMPRESS, + GC_CROSSIMPACT, + GC_DARKILLUSION, + GC_RESEARCHNEWPOISON, + GC_CREATENEWPOISON, + GC_ANTIDOTE, + GC_POISONINGWEAPON, + GC_WEAPONBLOCKING, + GC_COUNTERSLASH, + GC_WEAPONCRUSH, + GC_VENOMPRESSURE, + GC_POISONSMOKE, + GC_CLOAKINGEXCEED, + GC_PHANTOMMENACE, + GC_HALLUCINATIONWALK, + GC_ROLLINGCUTTER, + GC_CROSSRIPPERSLASHER, + + AB_JUDEX, + AB_ANCILLA, + AB_ADORAMUS, + AB_CLEMENTIA, + AB_CANTO, + AB_CHEAL, + AB_EPICLESIS, + AB_PRAEFATIO, + AB_ORATIO, + AB_LAUDAAGNUS, + AB_LAUDARAMUS, + AB_EUCHARISTICA, + AB_RENOVATIO, + AB_HIGHNESSHEAL, + AB_CLEARANCE, + AB_EXPIATIO, + AB_DUPLELIGHT, + AB_DUPLELIGHT_MELEE, + AB_DUPLELIGHT_MAGIC, + AB_SILENTIUM, + + WL_WHITEIMPRISON = 2201, + WL_SOULEXPANSION, + WL_FROSTMISTY, + WL_JACKFROST, + WL_MARSHOFABYSS, + WL_RECOGNIZEDSPELL, + WL_SIENNAEXECRATE, + WL_RADIUS, + WL_STASIS, + WL_DRAINLIFE, + WL_CRIMSONROCK, + WL_HELLINFERNO, + WL_COMET, + WL_CHAINLIGHTNING, + WL_CHAINLIGHTNING_ATK, + WL_EARTHSTRAIN, + WL_TETRAVORTEX, + WL_TETRAVORTEX_FIRE, + WL_TETRAVORTEX_WATER, + WL_TETRAVORTEX_WIND, + WL_TETRAVORTEX_GROUND, + WL_SUMMONFB, + WL_SUMMONBL, + WL_SUMMONWB, + WL_SUMMON_ATK_FIRE, + WL_SUMMON_ATK_WIND, + WL_SUMMON_ATK_WATER, + WL_SUMMON_ATK_GROUND, + WL_SUMMONSTONE, + WL_RELEASE, + WL_READING_SB, + WL_FREEZE_SP, + + RA_ARROWSTORM, + RA_FEARBREEZE, + RA_RANGERMAIN, + RA_AIMEDBOLT, + RA_DETONATOR, + RA_ELECTRICSHOCKER, + RA_CLUSTERBOMB, + RA_WUGMASTERY, + RA_WUGRIDER, + RA_WUGDASH, + RA_WUGSTRIKE, + RA_WUGBITE, + RA_TOOTHOFWUG, + RA_SENSITIVEKEEN, + RA_CAMOUFLAGE, + RA_RESEARCHTRAP, + RA_MAGENTATRAP, + RA_COBALTTRAP, + RA_MAIZETRAP, + RA_VERDURETRAP, + RA_FIRINGTRAP, + RA_ICEBOUNDTRAP, + + NC_MADOLICENCE, + NC_BOOSTKNUCKLE, + NC_PILEBUNKER, + NC_VULCANARM, + NC_FLAMELAUNCHER, + NC_COLDSLOWER, + NC_ARMSCANNON, + NC_ACCELERATION, + NC_HOVERING, + NC_F_SIDESLIDE, + NC_B_SIDESLIDE, + NC_MAINFRAME, + NC_SELFDESTRUCTION, + NC_SHAPESHIFT, + NC_EMERGENCYCOOL, + NC_INFRAREDSCAN, + NC_ANALYZE, + NC_MAGNETICFIELD, + NC_NEUTRALBARRIER, + NC_STEALTHFIELD, + NC_REPAIR, + NC_TRAININGAXE, + NC_RESEARCHFE, + NC_AXEBOOMERANG, + NC_POWERSWING, + NC_AXETORNADO, + NC_SILVERSNIPER, + NC_MAGICDECOY, + NC_DISJOINT, + + SC_FATALMENACE, + SC_REPRODUCE, + SC_AUTOSHADOWSPELL, + SC_SHADOWFORM, + SC_TRIANGLESHOT, + SC_BODYPAINT, + SC_INVISIBILITY, + SC_DEADLYINFECT, + SC_ENERVATION, + SC_GROOMY, + SC_IGNORANCE, + SC_LAZINESS, + SC_UNLUCKY, + SC_WEAKNESS, + SC_STRIPACCESSARY, + SC_MANHOLE, + SC_DIMENSIONDOOR, + SC_CHAOSPANIC, + SC_MAELSTROM, + SC_BLOODYLUST, + SC_FEINTBOMB, + + LG_CANNONSPEAR = 2307, + LG_BANISHINGPOINT, + LG_TRAMPLE, + LG_SHIELDPRESS, + LG_REFLECTDAMAGE, + LG_PINPOINTATTACK, + LG_FORCEOFVANGUARD, + LG_RAGEBURST, + LG_SHIELDSPELL, + LG_EXEEDBREAK, + LG_OVERBRAND, + LG_PRESTIGE, + LG_BANDING, + LG_MOONSLASHER, + LG_RAYOFGENESIS, + LG_PIETY, + LG_EARTHDRIVE, + LG_HESPERUSLIT, + LG_INSPIRATION, + + SR_DRAGONCOMBO, + SR_SKYNETBLOW, + SR_EARTHSHAKER, + SR_FALLENEMPIRE, + SR_TIGERCANNON, + SR_HELLGATE, + SR_RAMPAGEBLASTER, + SR_CRESCENTELBOW, + SR_CURSEDCIRCLE, + SR_LIGHTNINGWALK, + SR_KNUCKLEARROW, + SR_WINDMILL, + SR_RAISINGDRAGON, + SR_GENTLETOUCH, + SR_ASSIMILATEPOWER, + SR_POWERVELOCITY, + SR_CRESCENTELBOW_AUTOSPELL, + SR_GATEOFHELL, + SR_GENTLETOUCH_QUIET, + SR_GENTLETOUCH_CURE, + SR_GENTLETOUCH_ENERGYGAIN, + SR_GENTLETOUCH_CHANGE, + SR_GENTLETOUCH_REVITALIZE, + + WA_SWING_DANCE = 2350, + WA_SYMPHONY_OF_LOVER, + WA_MOONLIT_SERENADE, + + MI_RUSH_WINDMILL = 2381, + MI_ECHOSONG, + MI_HARMONIZE, + + WM_LESSON = 2412, + WM_METALICSOUND, + WM_REVERBERATION, + WM_REVERBERATION_MELEE, + WM_REVERBERATION_MAGIC, + WM_DOMINION_IMPULSE, + WM_SEVERE_RAINSTORM, + WM_POEMOFNETHERWORLD, + WM_VOICEOFSIREN, + WM_DEADHILLHERE, + WM_LULLABY_DEEPSLEEP, + WM_SIRCLEOFNATURE, + WM_RANDOMIZESPELL, + WM_GLOOMYDAY, + WM_GREAT_ECHO, + WM_SONG_OF_MANA, + WM_DANCE_WITH_WUG, + WM_SOUND_OF_DESTRUCTION, + WM_SATURDAY_NIGHT_FEVER, + WM_LERADS_DEW, + WM_MELODYOFSINK, + WM_BEYOND_OF_WARCRY, + WM_UNLIMITED_HUMMING_VOICE, + + SO_FIREWALK = 2443, + SO_ELECTRICWALK, + SO_SPELLFIST, + SO_EARTHGRAVE, + SO_DIAMONDDUST, + SO_POISON_BUSTER, + SO_PSYCHIC_WAVE, + SO_CLOUD_KILL, + SO_STRIKING, + SO_WARMER, + SO_VACUUM_EXTREME, + SO_VARETYR_SPEAR, + SO_ARRULLO, + SO_EL_CONTROL, + SO_SUMMON_AGNI, + SO_SUMMON_AQUA, + SO_SUMMON_VENTUS, + SO_SUMMON_TERA, + SO_EL_ACTION, + SO_EL_ANALYSIS, + SO_EL_SYMPATHY, + SO_EL_CURE, + SO_FIRE_INSIGNIA, + SO_WATER_INSIGNIA, + SO_WIND_INSIGNIA, + SO_EARTH_INSIGNIA, + + GN_TRAINING_SWORD = 2474, + GN_REMODELING_CART, + GN_CART_TORNADO, + GN_CARTCANNON, + GN_CARTBOOST, + GN_THORNS_TRAP, + GN_BLOOD_SUCKER, + GN_SPORE_EXPLOSION, + GN_WALLOFTHORN, + GN_CRAZYWEED, + GN_CRAZYWEED_ATK, + GN_DEMONIC_FIRE, + GN_FIRE_EXPANSION, + GN_FIRE_EXPANSION_SMOKE_POWDER, + GN_FIRE_EXPANSION_TEAR_GAS, + GN_FIRE_EXPANSION_ACID, + GN_HELLS_PLANT, + GN_HELLS_PLANT_ATK, + GN_MANDRAGORA, + GN_SLINGITEM, + GN_CHANGEMATERIAL, + GN_MIX_COOKING, + GN_MAKEBOMB, + GN_S_PHARMACY, + GN_SLINGITEM_RANGEMELEEATK, + + AB_SECRAMENT = 2515, + WM_SEVERE_RAINSTORM_MELEE, + SR_HOWLINGOFLION, + SR_RIDEINLIGHTNING, + LG_OVERBRAND_BRANDISH, + LG_OVERBRAND_PLUSATK, + + ALL_ODINS_RECALL = 2533, + RETURN_TO_ELDICASTES, + ALL_BUYING_STORE, + ALL_GUARDIAN_RECALL, + ALL_ODINS_POWER, + BEER_BOTTLE_CAP, + NPC_ASSASSINCROSS, + NPC_DISSONANCE, + NPC_UGLYDANCE, + ALL_TETANY, + ALL_RAY_OF_PROTECTION, + MC_CARTDECORATE, + + KO_YAMIKUMO = 3001, + KO_RIGHT, + KO_LEFT, + KO_JYUMONJIKIRI, + KO_SETSUDAN, + KO_BAKURETSU, + KO_HAPPOKUNAI, + KO_MUCHANAGE, + KO_HUUMARANKA, + KO_MAKIBISHI, + KO_MEIKYOUSISUI, + KO_ZANZOU, + KO_KYOUGAKU, + KO_JYUSATSU, + KO_KAHU_ENTEN, + KO_HYOUHU_HUBUKI, + KO_KAZEHU_SEIRAN, + KO_DOHU_KOUKAI, + KO_KAIHOU, + KO_ZENKAI, + KO_GENWAKU, + KO_IZAYOI, + KG_KAGEHUMI, + KG_KYOMU, + KG_KAGEMUSYA, + OB_ZANGETSU, + OB_OBOROGENSOU, + OB_OBOROGENSOU_TRANSITION_ATK, + OB_AKAITSUKI, + + ECL_SNOWFLIP = 3031, + ECL_PEONYMAMY, + ECL_SADAGUI, + ECL_SEQUOIADUST, + ECLAGE_RECALL, + + HLIF_HEAL = 8001, + HLIF_AVOID, + HLIF_BRAIN, + HLIF_CHANGE, + HAMI_CASTLE, + HAMI_DEFENCE, + HAMI_SKIN, + HAMI_BLOODLUST, + HFLI_MOON, + HFLI_FLEET, + HFLI_SPEED, + HFLI_SBR44, + HVAN_CAPRICE, + HVAN_CHAOTIC, + HVAN_INSTRUCT, + HVAN_EXPLOSION, + MUTATION_BASEJOB, + MH_SUMMON_LEGION, + MH_NEEDLE_OF_PARALYZE, + MH_POISON_MIST, + MH_PAIN_KILLER, + MH_LIGHT_OF_REGENE, + MH_OVERED_BOOST, + MH_ERASER_CUTTER, + MH_XENO_SLASHER, + MH_SILENT_BREEZE, + MH_STYLE_CHANGE, + MH_SONIC_CRAW, + MH_SILVERVEIN_RUSH, + MH_MIDNIGHT_FRENZY, + MH_STAHL_HORN, + MH_GOLDENE_FERSE, + MH_STEINWAND, + MH_HEILIGE_STANGE, + MH_ANGRIFFS_MODUS, + MH_TINDER_BREAKER, + MH_CBC, + MH_EQC, + MH_MAGMA_FLOW, + MH_GRANITIC_ARMOR, + MH_LAVA_SLIDE, + MH_PYROCLASTIC, + MH_VOLCANIC_ASH, + + MS_BASH = 8201, + MS_MAGNUM, + MS_BOWLINGBASH, + MS_PARRYING, + MS_REFLECTSHIELD, + MS_BERSERK, + MA_DOUBLE, + MA_SHOWER, + MA_SKIDTRAP, + MA_LANDMINE, + MA_SANDMAN, + MA_FREEZINGTRAP, + MA_REMOVETRAP, + MA_CHARGEARROW, + MA_SHARPSHOOTING, + ML_PIERCE, + ML_BRANDISH, + ML_SPIRALPIERCE, + ML_DEFENDER, + ML_AUTOGUARD, + ML_DEVOTION, + MER_MAGNIFICAT, + MER_QUICKEN, + MER_SIGHT, + MER_CRASH, + MER_REGAIN, + MER_TENDER, + MER_BENEDICTION, + MER_RECUPERATE, + MER_MENTALCURE, + MER_COMPRESS, + MER_PROVOKE, + MER_AUTOBERSERK, + MER_DECAGI, + MER_SCAPEGOAT, + MER_LEXDIVINA, + MER_ESTIMATION, + MER_KYRIE, + MER_BLESSING, + MER_INCAGI, + + EL_CIRCLE_OF_FIRE = 8401, + EL_FIRE_CLOAK, + EL_FIRE_MANTLE, + EL_WATER_SCREEN, + EL_WATER_DROP, + EL_WATER_BARRIER, + EL_WIND_STEP, + EL_WIND_CURTAIN, + EL_ZEPHYR, + EL_SOLID_SKIN, + EL_STONE_SHIELD, + EL_POWER_OF_GAIA, + EL_PYROTECHNIC, + EL_HEATER, + EL_TROPIC, + EL_AQUAPLAY, + EL_COOLER, + EL_CHILLY_AIR, + EL_GUST, + EL_BLAST, + EL_WILD_STORM, + EL_PETROLOGY, + EL_CURSED_SOIL, + EL_UPHEAVAL, + EL_FIRE_ARROW, + EL_FIRE_BOMB, + EL_FIRE_BOMB_ATK, + EL_FIRE_WAVE, + EL_FIRE_WAVE_ATK, + EL_ICE_NEEDLE, + EL_WATER_SCREW, + EL_WATER_SCREW_ATK, + EL_TIDAL_WEAPON, + EL_WIND_SLASH, + EL_HURRICANE, + EL_HURRICANE_ATK, + EL_TYPOON_MIS, + EL_TYPOON_MIS_ATK, + EL_STONE_HAMMER, + EL_ROCK_CRUSHER, + EL_ROCK_CRUSHER_ATK, + EL_STONE_RAIN, +}; + +/// The client view ids for land skills. +enum { + UNT_SAFETYWALL = 0x7e, + UNT_FIREWALL, + UNT_WARP_WAITING, + UNT_WARP_ACTIVE, + UNT_BENEDICTIO, //TODO + UNT_SANCTUARY, + UNT_MAGNUS, + UNT_PNEUMA, + UNT_DUMMYSKILL, //These show no effect on the client + UNT_FIREPILLAR_WAITING, + UNT_FIREPILLAR_ACTIVE, + UNT_HIDDEN_TRAP, //TODO + UNT_TRAP, //TODO + UNT_HIDDEN_WARP_NPC, //TODO + UNT_USED_TRAPS, + UNT_ICEWALL, + UNT_QUAGMIRE, + UNT_BLASTMINE, + UNT_SKIDTRAP, + UNT_ANKLESNARE, + UNT_VENOMDUST, + UNT_LANDMINE, + UNT_SHOCKWAVE, + UNT_SANDMAN, + UNT_FLASHER, + UNT_FREEZINGTRAP, + UNT_CLAYMORETRAP, + UNT_TALKIEBOX, + UNT_VOLCANO, + UNT_DELUGE, + UNT_VIOLENTGALE, + UNT_LANDPROTECTOR, + UNT_LULLABY, + UNT_RICHMANKIM, + UNT_ETERNALCHAOS, + UNT_DRUMBATTLEFIELD, + UNT_RINGNIBELUNGEN, + UNT_ROKISWEIL, + UNT_INTOABYSS, + UNT_SIEGFRIED, + UNT_DISSONANCE, + UNT_WHISTLE, + UNT_ASSASSINCROSS, + UNT_POEMBRAGI, + UNT_APPLEIDUN, + UNT_UGLYDANCE, + UNT_HUMMING, + UNT_DONTFORGETME, + UNT_FORTUNEKISS, + UNT_SERVICEFORYOU, + UNT_GRAFFITI, + UNT_DEMONSTRATION, + UNT_CALLFAMILY, + UNT_GOSPEL, + UNT_BASILICA, + UNT_MOONLIT, + UNT_FOGWALL, + UNT_SPIDERWEB, + UNT_GRAVITATION, + UNT_HERMODE, + UNT_KAENSIN, //TODO + UNT_SUITON, + UNT_TATAMIGAESHI, + UNT_KAEN, + UNT_GROUNDDRIFT_WIND, + UNT_GROUNDDRIFT_DARK, + UNT_GROUNDDRIFT_POISON, + UNT_GROUNDDRIFT_WATER, + UNT_GROUNDDRIFT_FIRE, + UNT_DEATHWAVE, //TODO + UNT_WATERATTACK, //TODO + UNT_WINDATTACK, //TODO + UNT_EARTHQUAKE, //TODO + UNT_EVILLAND, + UNT_DARK_RUNNER, //TODO + UNT_DARK_TRANSFER, //TODO + UNT_EPICLESIS, + UNT_EARTHSTRAIN, + UNT_MANHOLE, + UNT_DIMENSIONDOOR, + UNT_CHAOSPANIC, + UNT_MAELSTROM, + UNT_BLOODYLUST, + UNT_FEINTBOMB, + UNT_MAGENTATRAP, + UNT_COBALTTRAP, + UNT_MAIZETRAP, + UNT_VERDURETRAP, + UNT_FIRINGTRAP, + UNT_ICEBOUNDTRAP, + UNT_ELECTRICSHOCKER, + UNT_CLUSTERBOMB, + UNT_REVERBERATION, + UNT_SEVERE_RAINSTORM, + UNT_FIREWALK, + UNT_ELECTRICWALK, + UNT_NETHERWORLD, + UNT_PSYCHIC_WAVE, + UNT_CLOUD_KILL, + UNT_POISONSMOKE, + UNT_NEUTRALBARRIER, + UNT_STEALTHFIELD, + UNT_WARMER, + UNT_THORNS_TRAP, + UNT_WALLOFTHORN, + UNT_DEMONIC_FIRE, + UNT_FIRE_EXPANSION_SMOKE_POWDER, + UNT_FIRE_EXPANSION_TEAR_GAS, + UNT_HELLS_PLANT, + UNT_VACUUM_EXTREME, + UNT_BANDING, + UNT_FIRE_MANTLE, + UNT_WATER_BARRIER, + UNT_ZEPHYR, + UNT_POWER_OF_GAIA, + UNT_FIRE_INSIGNIA, + UNT_WATER_INSIGNIA, + UNT_WIND_INSIGNIA, + UNT_EARTH_INSIGNIA, + UNT_POISON_MIST, + UNT_LAVA_SLIDE, + UNT_VOLCANIC_ASH, + UNT_ZENKAI_WATER, + UNT_ZENKAI_LAND, + UNT_ZENKAI_FIRE, + UNT_ZENKAI_WIND, + UNT_MAKIBISHI, + UNT_VENOMFOG, + + /** + * Guild Auras + **/ + UNT_GD_LEADERSHIP = 0xc1, + UNT_GD_GLORYWOUNDS = 0xc2, + UNT_GD_SOULCOLD = 0xc3, + UNT_GD_HAWKEYES = 0xc4, + + UNT_MAX = 0x190 +}; +/** + * Skill Unit Save + **/ +void skill_usave_add(struct map_session_data * sd, uint16 skill_id, uint16 skill_lv); +void skill_usave_trigger(struct map_session_data *sd); +/** + * Skill Cool Downs - load from pc.c when the character logs in + **/ +void skill_cooldown_load(struct map_session_data * sd); +/** + * Warlock + **/ +#define MAX_SKILL_SPELLBOOK_DB 17 +enum wl_spheres { + WLS_FIRE = 0x44, + WLS_WIND, + WLS_WATER, + WLS_STONE, +}; +int skill_spellbook (struct map_session_data *sd, int nameid); +int skill_block_check(struct block_list *bl, enum sc_type type, uint16 skill_id); +/** + * Guilottine Cross + **/ +#define MAX_SKILL_MAGICMUSHROOM_DB 23 +struct s_skill_magicmushroom_db { + uint16 skill_id; +}; +extern struct s_skill_magicmushroom_db skill_magicmushroom_db[MAX_SKILL_MAGICMUSHROOM_DB]; +/** + * Ranger + **/ +int skill_detonator(struct block_list *bl, va_list ap); +bool skill_check_camouflage(struct block_list *bl, struct status_change_entry *sce); +/** + * Mechanic + **/ +int skill_magicdecoy(struct map_session_data *sd, int nameid); +/** + * Guiltoine Cross + **/ +int skill_poisoningweapon( struct map_session_data *sd, int nameid); +enum gx_poison { + PO_PARALYSE = 12717, + PO_LEECHESEND, + PO_OBLIVIONCURSE, + PO_DEATHHURT, + PO_TOXIN, + PO_PYREXIA, + PO_MAGICMUSHROOM, + PO_VENOMBLEED +}; +/** + * Auto Shadow Spell (Shadow Chaser) + **/ +int skill_select_menu(struct map_session_data *sd,uint16 skill_id); + +int skill_elementalanalysis(struct map_session_data *sd, int n, uint16 skill_lv, unsigned short *item_list); // Sorcerer Four Elemental Analisys. +int skill_changematerial(struct map_session_data *sd, int n, unsigned short *item_list); // Genetic Change Material. +int skill_get_elemental_type(uint16 skill_id, uint16 skill_lv); + +#endif /* _SKILL_H_ */ diff --git a/src/map/sql/CMakeLists.txt b/src/map/sql/CMakeLists.txt new file mode 100644 index 000000000..47c8e495c --- /dev/null +++ b/src/map/sql/CMakeLists.txt @@ -0,0 +1,112 @@ + +# +# map sql +# +if( BUILD_SQL_SERVERS ) +message( STATUS "Creating target map-server_sql" ) +set( SQL_MAP_HEADERS + "${SQL_MAP_SOURCE_DIR}/atcommand.h" + "${SQL_MAP_SOURCE_DIR}/battle.h" + "${SQL_MAP_SOURCE_DIR}/battleground.h" + "${SQL_MAP_SOURCE_DIR}/buyingstore.h" + "${SQL_MAP_SOURCE_DIR}/chat.h" + "${SQL_MAP_SOURCE_DIR}/chrif.h" + "${SQL_MAP_SOURCE_DIR}/clif.h" + "${SQL_MAP_SOURCE_DIR}/date.h" + "${SQL_MAP_SOURCE_DIR}/duel.h" + "${SQL_MAP_SOURCE_DIR}/elemental.h" + "${SQL_MAP_SOURCE_DIR}/guild.h" + "${SQL_MAP_SOURCE_DIR}/homunculus.h" + "${SQL_MAP_SOURCE_DIR}/instance.h" + "${SQL_MAP_SOURCE_DIR}/intif.h" + "${SQL_MAP_SOURCE_DIR}/itemdb.h" + "${SQL_MAP_SOURCE_DIR}/log.h" + "${SQL_MAP_SOURCE_DIR}/mail.h" + "${SQL_MAP_SOURCE_DIR}/map.h" + "${SQL_MAP_SOURCE_DIR}/mapreg.h" + "${SQL_MAP_SOURCE_DIR}/mercenary.h" + "${SQL_MAP_SOURCE_DIR}/mob.h" + "${SQL_MAP_SOURCE_DIR}/npc.h" + "${SQL_MAP_SOURCE_DIR}/party.h" + "${SQL_MAP_SOURCE_DIR}/path.h" + "${SQL_MAP_SOURCE_DIR}/pc.h" + "${SQL_MAP_SOURCE_DIR}/pc_groups.h" + "${SQL_MAP_SOURCE_DIR}/pet.h" + "${SQL_MAP_SOURCE_DIR}/quest.h" + "${SQL_MAP_SOURCE_DIR}/script.h" + "${SQL_MAP_SOURCE_DIR}/searchstore.h" + "${SQL_MAP_SOURCE_DIR}/skill.h" + "${SQL_MAP_SOURCE_DIR}/status.h" + "${SQL_MAP_SOURCE_DIR}/storage.h" + "${SQL_MAP_SOURCE_DIR}/trade.h" + "${SQL_MAP_SOURCE_DIR}/unit.h" + "${SQL_MAP_SOURCE_DIR}/vending.h" + ) +set( SQL_MAP_SOURCES + "${SQL_MAP_SOURCE_DIR}/atcommand.c" + "${SQL_MAP_SOURCE_DIR}/battle.c" + "${SQL_MAP_SOURCE_DIR}/battleground.c" + "${SQL_MAP_SOURCE_DIR}/buyingstore.c" + "${SQL_MAP_SOURCE_DIR}/chat.c" + "${SQL_MAP_SOURCE_DIR}/chrif.c" + "${SQL_MAP_SOURCE_DIR}/clif.c" + "${SQL_MAP_SOURCE_DIR}/date.c" + "${SQL_MAP_SOURCE_DIR}/duel.c" + "${SQL_MAP_SOURCE_DIR}/elemental.c" + "${SQL_MAP_SOURCE_DIR}/guild.c" + "${SQL_MAP_SOURCE_DIR}/homunculus.c" + "${SQL_MAP_SOURCE_DIR}/instance.c" + "${SQL_MAP_SOURCE_DIR}/intif.c" + "${SQL_MAP_SOURCE_DIR}/itemdb.c" + "${SQL_MAP_SOURCE_DIR}/log.c" + "${SQL_MAP_SOURCE_DIR}/mail.c" + "${SQL_MAP_SOURCE_DIR}/map.c" + "${SQL_MAP_SOURCE_DIR}/mapreg_sql.c" + "${SQL_MAP_SOURCE_DIR}/mercenary.c" + "${SQL_MAP_SOURCE_DIR}/mob.c" + "${SQL_MAP_SOURCE_DIR}/npc.c" + "${SQL_MAP_SOURCE_DIR}/npc_chat.c" + "${SQL_MAP_SOURCE_DIR}/party.c" + "${SQL_MAP_SOURCE_DIR}/path.c" + "${SQL_MAP_SOURCE_DIR}/pc.c" + "${SQL_MAP_SOURCE_DIR}/pc_groups.c" + "${SQL_MAP_SOURCE_DIR}/pet.c" + "${SQL_MAP_SOURCE_DIR}/quest.c" + "${SQL_MAP_SOURCE_DIR}/script.c" + "${SQL_MAP_SOURCE_DIR}/searchstore.c" + "${SQL_MAP_SOURCE_DIR}/skill.c" + "${SQL_MAP_SOURCE_DIR}/status.c" + "${SQL_MAP_SOURCE_DIR}/storage.c" + "${SQL_MAP_SOURCE_DIR}/trade.c" + "${SQL_MAP_SOURCE_DIR}/unit.c" + "${SQL_MAP_SOURCE_DIR}/vending.c" + ) +set( DEPENDENCIES common_sql ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS}" ) +if( WITH_PCRE ) + message( STATUS "Enabled PCRE code" ) + set( LIBRARIES ${LIBRARIES} ${PCRE_LIBRARIES} ) + set( INCLUDE_DIRS ${INCLUDE_DIRS} ${PCRE_INCLUDE_DIRS} ) + set( DEFINITIONS "${DEFINITIONS} -DPCRE_SUPPORT" ) +else() + message( STATUS "Disabled PCRE code" ) +endif() +set( SOURCE_FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ${SQL_MAP_HEADERS} ${SQL_MAP_SOURCES} ) +source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_SQL_HEADERS} ) +source_group( map FILES ${SQL_MAP_HEADERS} ${SQL_MAP_SOURCES} ) +include_directories( ${INCLUDE_DIRS} ) +add_executable( map-server_sql ${SOURCE_FILES} ) +add_dependencies( map-server_sql ${DEPENDENCIES} ) +target_link_libraries( map-server_sql ${LIBRARIES} ${DEPENDENCIES} ) +set_target_properties( map-server_sql PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +if( INSTALL_COMPONENT_RUNTIME ) + cpack_add_component( Runtime_mapserver_sql DESCRIPTION "map-server (sql version)" DISPLAY_NAME "map-server_sql" GROUP Runtime ) + install( TARGETS map-server_sql + DESTINATION "." + COMPONENT Runtime_mapserver_sql ) +endif( INSTALL_COMPONENT_RUNTIME ) +set( TARGET_LIST ${TARGET_LIST} map-server_sql CACHE INTERNAL "" ) +message( STATUS "Creating target map-server_sql - done" ) +endif( BUILD_SQL_SERVERS ) diff --git a/src/map/status.c b/src/map/status.c new file mode 100644 index 000000000..649cfa1ae --- /dev/null +++ b/src/map/status.c @@ -0,0 +1,11294 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../common/utils.h" +#include "../common/ers.h" +#include "../common/strlib.h" + +#include "map.h" +#include "path.h" +#include "pc.h" +#include "pet.h" +#include "npc.h" +#include "mob.h" +#include "clif.h" +#include "guild.h" +#include "skill.h" +#include "itemdb.h" +#include "battle.h" +#include "chrif.h" +#include "skill.h" +#include "status.h" +#include "script.h" +#include "unit.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "vending.h" + +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <string.h> +#include <math.h> + +//Regen related flags. +enum e_regen +{ + RGN_HP = 0x01, + RGN_SP = 0x02, + RGN_SHP = 0x04, + RGN_SSP = 0x08, +}; + +static int max_weight_base[CLASS_COUNT]; +static int hp_coefficient[CLASS_COUNT]; +static int hp_coefficient2[CLASS_COUNT]; +static int hp_sigma_val[CLASS_COUNT][MAX_LEVEL+1]; +static int sp_coefficient[CLASS_COUNT]; +#ifdef RENEWAL_ASPD +static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE+1]; +#else +static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE]; //[blackhole89] +#endif + +// bonus values and upgrade chances for refining equipment +static struct { + int chance[MAX_REFINE]; // success chance + int bonus[MAX_REFINE]; // cumulative fixed bonus damage + int randombonus_max[MAX_REFINE]; // cumulative maximum random bonus damage +} refine_info[REFINE_TYPE_MAX]; + +static int atkmods[3][MAX_WEAPON_TYPE]; //ATK weapon modification for size (size_fix.txt) +static char job_bonus[CLASS_COUNT][MAX_LEVEL]; + +static struct eri *sc_data_ers; //For sc_data entries +static struct status_data dummy_status; + +int current_equip_item_index; //Contains inventory index of an equipped item. To pass it into the EQUP_SCRIPT [Lupus] +int current_equip_card_id; //To prevent card-stacking (from jA) [Skotlex] +//we need it for new cards 15 Feb 2005, to check if the combo cards are insrerted into the CURRENT weapon only +//to avoid cards exploits + +static sc_type SkillStatusChangeTable[MAX_SKILL]; // skill -> status +static int StatusIconChangeTable[SC_MAX]; // status -> "icon" (icon is a bit of a misnomer, since there exist values with no icon associated) +static unsigned int StatusChangeFlagTable[SC_MAX]; // status -> flags +static int StatusSkillChangeTable[SC_MAX]; // status -> skill +static int StatusRelevantBLTypes[SI_MAX]; // "icon" -> enum bl_type (for clif_status_change to identify for which bl types to send packets) +static unsigned int StatusChangeStateTable[SC_MAX]; // status -> flags + + +/** + * Returns the status change associated with a skill. + * @param skill The skill to look up + * @return The status registered for this skill + **/ +sc_type status_skill2sc(int skill) +{ + int idx = skill_get_index(skill); + if( idx == 0 ) { + ShowError("status_skill2sc: Unsupported skill id %d\n", skill); + return SC_NONE; + } + return SkillStatusChangeTable[idx]; +} + +/** + * Returns the FIRST skill (in order of definition in initChangeTables) to use a given status change. + * Utilized for various duration lookups. Use with caution! + * @param sc The status to look up + * @return A skill associated with the status + **/ +int status_sc2skill(sc_type sc) +{ + if( sc < 0 || sc >= SC_MAX ) { + ShowError("status_sc2skill: Unsupported status change id %d\n", sc); + return 0; + } + + return StatusSkillChangeTable[sc]; +} + +/** + * Returns the status calculation flag associated with a given status change. + * @param sc The status to look up + * @return The scb_flag registered for this status (see enum scb_flag) + **/ +unsigned int status_sc2scb_flag(sc_type sc) +{ + if( sc < 0 || sc >= SC_MAX ) { + ShowError("status_sc2scb_flag: Unsupported status change id %d\n", sc); + return SCB_NONE; + } + + return StatusChangeFlagTable[sc]; +} + +/** + * Returns the bl types which require a status change packet to be sent for a given client status identifier. + * @param type The client-side status identifier to look up (see enum si_type) + * @return The bl types relevant to the type (see enum bl_type) + **/ +int status_type2relevant_bl_types(int type) +{ + if( type < 0 || type >= SI_MAX ) { + ShowError("status_type2relevant_bl_types: Unsupported type %d\n", type); + return SI_BLANK; + } + + return StatusRelevantBLTypes[type]; +} + +#define add_sc(skill,sc) set_sc(skill,sc,SI_BLANK,SCB_NONE) +// indicates that the status displays a visual effect for the affected unit, and should be sent to the client for all supported units +#define set_sc_with_vfx(skill, sc, icon, flag) set_sc((skill), (sc), (icon), (flag)); if((icon) < SI_MAX) StatusRelevantBLTypes[(icon)] |= BL_SCEFFECT + +static void set_sc(uint16 skill_id, sc_type sc, int icon, unsigned int flag) +{ + uint16 idx = skill_get_index(skill_id); + if( idx == 0 ) { + ShowError("set_sc: Unsupported skill id %d\n", skill_id); + return; + } + if( sc < 0 || sc >= SC_MAX ) { + ShowError("set_sc: Unsupported status change id %d\n", sc); + return; + } + + if( StatusSkillChangeTable[sc] == 0 ) + StatusSkillChangeTable[sc] = skill_id; + if( StatusIconChangeTable[sc] == SI_BLANK ) + StatusIconChangeTable[sc] = icon; + StatusChangeFlagTable[sc] |= flag; + + if( SkillStatusChangeTable[idx] == SC_NONE ) + SkillStatusChangeTable[idx] = sc; +} + +void initChangeTables(void) { + int i; + + for (i = 0; i < SC_MAX; i++) + StatusIconChangeTable[i] = SI_BLANK; + + for (i = 0; i < MAX_SKILL; i++) + SkillStatusChangeTable[i] = SC_NONE; + + for (i = 0; i < SI_MAX; i++) + StatusRelevantBLTypes[i] = BL_PC; + + memset(StatusSkillChangeTable, 0, sizeof(StatusSkillChangeTable)); + memset(StatusChangeFlagTable, 0, sizeof(StatusChangeFlagTable)); + memset(StatusChangeStateTable, 0, sizeof(StatusChangeStateTable)); + + + //First we define the skill for common ailments. These are used in skill_additional_effect through sc cards. [Skotlex] + set_sc( NPC_PETRIFYATTACK , SC_STONE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); + set_sc( NPC_WIDEFREEZE , SC_FREEZE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); + set_sc( NPC_STUNATTACK , SC_STUN , SI_BLANK , SCB_NONE ); + set_sc( NPC_SLEEPATTACK , SC_SLEEP , SI_BLANK , SCB_NONE ); + set_sc( NPC_POISON , SC_POISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); + set_sc( NPC_CURSEATTACK , SC_CURSE , SI_BLANK , SCB_LUK|SCB_BATK|SCB_WATK|SCB_SPEED ); + set_sc( NPC_SILENCEATTACK , SC_SILENCE , SI_BLANK , SCB_NONE ); + set_sc( NPC_WIDECONFUSE , SC_CONFUSION , SI_BLANK , SCB_NONE ); + set_sc( NPC_BLINDATTACK , SC_BLIND , SI_BLANK , SCB_HIT|SCB_FLEE ); + set_sc( NPC_BLEEDING , SC_BLEEDING , SI_BLEEDING , SCB_REGEN ); + set_sc( NPC_POISON , SC_DPOISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); + + //The main status definitions + add_sc( SM_BASH , SC_STUN ); + set_sc( SM_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + add_sc( SM_MAGNUM , SC_WATK_ELEMENT ); + set_sc( SM_ENDURE , SC_ENDURE , SI_ENDURE , SCB_MDEF|SCB_DSPD ); + add_sc( MG_SIGHT , SC_SIGHT ); + add_sc( MG_SAFETYWALL , SC_SAFETYWALL ); + add_sc( MG_FROSTDIVER , SC_FREEZE ); + add_sc( MG_STONECURSE , SC_STONE ); + add_sc( AL_RUWACH , SC_RUWACH ); + add_sc( AL_PNEUMA , SC_PNEUMA ); + set_sc( AL_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( AL_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( AL_CRUCIS , SC_SIGNUMCRUCIS , SI_SIGNUMCRUCIS , SCB_DEF ); + set_sc( AL_ANGELUS , SC_ANGELUS , SI_ANGELUS , SCB_DEF2 ); + set_sc( AL_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( AC_CONCENTRATION , SC_CONCENTRATE , SI_CONCENTRATE , SCB_AGI|SCB_DEX ); + set_sc( TF_HIDING , SC_HIDING , SI_HIDING , SCB_SPEED ); + add_sc( TF_POISON , SC_POISON ); + set_sc( KN_TWOHANDQUICKEN , SC_TWOHANDQUICKEN , SI_TWOHANDQUICKEN , SCB_ASPD ); + add_sc( KN_AUTOCOUNTER , SC_AUTOCOUNTER ); + set_sc( PR_IMPOSITIO , SC_IMPOSITIO , SI_IMPOSITIO , SCB_WATK ); + set_sc( PR_SUFFRAGIUM , SC_SUFFRAGIUM , SI_SUFFRAGIUM , SCB_NONE ); + set_sc( PR_ASPERSIO , SC_ASPERSIO , SI_ASPERSIO , SCB_ATK_ELE ); + set_sc( PR_BENEDICTIO , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); + set_sc( PR_SLOWPOISON , SC_SLOWPOISON , SI_SLOWPOISON , SCB_REGEN ); + set_sc( PR_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); + set_sc( PR_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); + set_sc( PR_GLORIA , SC_GLORIA , SI_GLORIA , SCB_LUK ); + add_sc( PR_LEXDIVINA , SC_SILENCE ); + set_sc( PR_LEXAETERNA , SC_AETERNA , SI_AETERNA , SCB_NONE ); + add_sc( WZ_METEOR , SC_STUN ); + add_sc( WZ_VERMILION , SC_BLIND ); + add_sc( WZ_FROSTNOVA , SC_FREEZE ); + add_sc( WZ_STORMGUST , SC_FREEZE ); + set_sc( WZ_QUAGMIRE , SC_QUAGMIRE , SI_QUAGMIRE , SCB_AGI|SCB_DEX|SCB_ASPD|SCB_SPEED ); + set_sc( BS_ADRENALINE , SC_ADRENALINE , SI_ADRENALINE , SCB_ASPD ); + set_sc( BS_WEAPONPERFECT , SC_WEAPONPERFECTION, SI_WEAPONPERFECTION, SCB_NONE ); + set_sc( BS_OVERTHRUST , SC_OVERTHRUST , SI_OVERTHRUST , SCB_NONE ); + set_sc( BS_MAXIMIZE , SC_MAXIMIZEPOWER , SI_MAXIMIZEPOWER , SCB_REGEN ); + add_sc( HT_LANDMINE , SC_STUN ); + add_sc( HT_ANKLESNARE , SC_ANKLE ); + add_sc( HT_SANDMAN , SC_SLEEP ); + add_sc( HT_FLASHER , SC_BLIND ); + add_sc( HT_FREEZINGTRAP , SC_FREEZE ); + set_sc( AS_CLOAKING , SC_CLOAKING , SI_CLOAKING , SCB_CRI|SCB_SPEED ); + add_sc( AS_SONICBLOW , SC_STUN ); + set_sc( AS_ENCHANTPOISON , SC_ENCPOISON , SI_ENCPOISON , SCB_ATK_ELE ); + set_sc( AS_POISONREACT , SC_POISONREACT , SI_POISONREACT , SCB_NONE ); + add_sc( AS_VENOMDUST , SC_POISON ); + add_sc( AS_SPLASHER , SC_SPLASHER ); + set_sc( NV_TRICKDEAD , SC_TRICKDEAD , SI_TRICKDEAD , SCB_REGEN ); + set_sc( SM_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); + add_sc( TF_SPRINKLESAND , SC_BLIND ); + add_sc( TF_THROWSTONE , SC_STUN ); + set_sc( MC_LOUD , SC_LOUD , SI_LOUD , SCB_STR ); + set_sc( MG_ENERGYCOAT , SC_ENERGYCOAT , SI_ENERGYCOAT , SCB_NONE ); + set_sc( NPC_EMOTION , SC_MODECHANGE , SI_BLANK , SCB_MODE ); + add_sc( NPC_EMOTION_ON , SC_MODECHANGE ); + set_sc( NPC_ATTRICHANGE , SC_ELEMENTALCHANGE , SI_ARMOR_PROPERTY , SCB_DEF_ELE ); + add_sc( NPC_CHANGEWATER , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEGROUND , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEFIRE , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEWIND , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEPOISON , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEHOLY , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEDARKNESS , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGETELEKINESIS, SC_ELEMENTALCHANGE ); + add_sc( NPC_POISON , SC_POISON ); + add_sc( NPC_BLINDATTACK , SC_BLIND ); + add_sc( NPC_SILENCEATTACK , SC_SILENCE ); + add_sc( NPC_STUNATTACK , SC_STUN ); + add_sc( NPC_PETRIFYATTACK , SC_STONE ); + add_sc( NPC_CURSEATTACK , SC_CURSE ); + add_sc( NPC_SLEEPATTACK , SC_SLEEP ); + add_sc( NPC_MAGICALATTACK , SC_MAGICALATTACK ); + set_sc( NPC_KEEPING , SC_KEEPING , SI_BLANK , SCB_DEF ); + add_sc( NPC_DARKBLESSING , SC_COMA ); + set_sc( NPC_BARRIER , SC_BARRIER , SI_BLANK , SCB_MDEF|SCB_DEF ); + add_sc( NPC_DEFENDER , SC_ARMOR ); + add_sc( NPC_LICK , SC_STUN ); + set_sc( NPC_HALLUCINATION , SC_HALLUCINATION , SI_HALLUCINATION , SCB_NONE ); + add_sc( NPC_REBIRTH , SC_REBIRTH ); + add_sc( RG_RAID , SC_STUN ); +#ifdef RENEWAL + add_sc( RG_RAID , SC_RAID ); + add_sc( RG_BACKSTAP , SC_STUN ); +#endif + set_sc( RG_STRIPWEAPON , SC_STRIPWEAPON , SI_STRIPWEAPON , SCB_WATK ); + set_sc( RG_STRIPSHIELD , SC_STRIPSHIELD , SI_STRIPSHIELD , SCB_DEF ); + set_sc( RG_STRIPARMOR , SC_STRIPARMOR , SI_STRIPARMOR , SCB_VIT ); + set_sc( RG_STRIPHELM , SC_STRIPHELM , SI_STRIPHELM , SCB_INT ); + add_sc( AM_ACIDTERROR , SC_BLEEDING ); + set_sc( AM_CP_WEAPON , SC_CP_WEAPON , SI_CP_WEAPON , SCB_NONE ); + set_sc( AM_CP_SHIELD , SC_CP_SHIELD , SI_CP_SHIELD , SCB_NONE ); + set_sc( AM_CP_ARMOR , SC_CP_ARMOR , SI_CP_ARMOR , SCB_NONE ); + set_sc( AM_CP_HELM , SC_CP_HELM , SI_CP_HELM , SCB_NONE ); + set_sc( CR_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); + add_sc( CR_SHIELDCHARGE , SC_STUN ); + set_sc( CR_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); + add_sc( CR_HOLYCROSS , SC_BLIND ); + add_sc( CR_GRANDCROSS , SC_BLIND ); + add_sc( CR_DEVOTION , SC_DEVOTION ); + set_sc( CR_PROVIDENCE , SC_PROVIDENCE , SI_PROVIDENCE , SCB_ALL ); + set_sc( CR_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); + set_sc( CR_SPEARQUICKEN , SC_SPEARQUICKEN , SI_SPEARQUICKEN , SCB_ASPD|SCB_CRI|SCB_FLEE ); + set_sc( MO_STEELBODY , SC_STEELBODY , SI_STEELBODY , SCB_DEF|SCB_MDEF|SCB_ASPD|SCB_SPEED ); + add_sc( MO_BLADESTOP , SC_BLADESTOP_WAIT ); + add_sc( MO_BLADESTOP , SC_BLADESTOP ); + set_sc( MO_EXPLOSIONSPIRITS , SC_EXPLOSIONSPIRITS, SI_EXPLOSIONSPIRITS, SCB_CRI|SCB_REGEN ); + set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST , SI_BLANK , SCB_REGEN ); +#ifdef RENEWAL + set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST2 , SI_EXTREMITYFIST , SCB_NONE ); +#endif + add_sc( SA_MAGICROD , SC_MAGICROD ); + set_sc( SA_AUTOSPELL , SC_AUTOSPELL , SI_AUTOSPELL , SCB_NONE ); + set_sc( SA_FLAMELAUNCHER , SC_FIREWEAPON , SI_FIREWEAPON , SCB_ATK_ELE ); + set_sc( SA_FROSTWEAPON , SC_WATERWEAPON , SI_WATERWEAPON , SCB_ATK_ELE ); + set_sc( SA_LIGHTNINGLOADER , SC_WINDWEAPON , SI_WINDWEAPON , SCB_ATK_ELE ); + set_sc( SA_SEISMICWEAPON , SC_EARTHWEAPON , SI_EARTHWEAPON , SCB_ATK_ELE ); + set_sc( SA_VOLCANO , SC_VOLCANO , SI_LANDENDOW , SCB_WATK ); + set_sc( SA_DELUGE , SC_DELUGE , SI_LANDENDOW , SCB_MAXHP ); + set_sc( SA_VIOLENTGALE , SC_VIOLENTGALE , SI_LANDENDOW , SCB_FLEE ); + add_sc( SA_REVERSEORCISH , SC_ORCISH ); + add_sc( SA_COMA , SC_COMA ); + set_sc( BD_ENCORE , SC_DANCING , SI_BLANK , SCB_SPEED|SCB_REGEN ); + add_sc( BD_RICHMANKIM , SC_RICHMANKIM ); + set_sc( BD_ETERNALCHAOS , SC_ETERNALCHAOS , SI_BLANK , SCB_DEF2 ); + set_sc( BD_DRUMBATTLEFIELD , SC_DRUMBATTLE , SI_BLANK , SCB_WATK|SCB_DEF ); + set_sc( BD_RINGNIBELUNGEN , SC_NIBELUNGEN , SI_BLANK , SCB_WATK ); + add_sc( BD_ROKISWEIL , SC_ROKISWEIL ); + add_sc( BD_INTOABYSS , SC_INTOABYSS ); + set_sc( BD_SIEGFRIED , SC_SIEGFRIED , SI_BLANK , SCB_ALL ); + add_sc( BA_FROSTJOKER , SC_FREEZE ); + set_sc( BA_WHISTLE , SC_WHISTLE , SI_BLANK , SCB_FLEE|SCB_FLEE2 ); + set_sc( BA_ASSASSINCROSS , SC_ASSNCROS , SI_BLANK , SCB_ASPD ); + add_sc( BA_POEMBRAGI , SC_POEMBRAGI ); + set_sc( BA_APPLEIDUN , SC_APPLEIDUN , SI_BLANK , SCB_MAXHP ); + add_sc( DC_SCREAM , SC_STUN ); + set_sc( DC_HUMMING , SC_HUMMING , SI_BLANK , SCB_HIT ); + set_sc( DC_DONTFORGETME , SC_DONTFORGETME , SI_BLANK , SCB_SPEED|SCB_ASPD ); + set_sc( DC_FORTUNEKISS , SC_FORTUNE , SI_BLANK , SCB_CRI ); + set_sc( DC_SERVICEFORYOU , SC_SERVICE4U , SI_BLANK , SCB_ALL ); + add_sc( NPC_DARKCROSS , SC_BLIND ); + add_sc( NPC_GRANDDARKNESS , SC_BLIND ); + set_sc( NPC_STOP , SC_STOP , SI_STOP , SCB_NONE ); + set_sc( NPC_WEAPONBRAKER , SC_BROKENWEAPON , SI_BROKENWEAPON , SCB_NONE ); + set_sc( NPC_ARMORBRAKE , SC_BROKENARMOR , SI_BROKENARMOR , SCB_NONE ); + set_sc( NPC_CHANGEUNDEAD , SC_CHANGEUNDEAD , SI_UNDEAD , SCB_DEF_ELE ); + set_sc( NPC_POWERUP , SC_INCHITRATE , SI_BLANK , SCB_HIT ); + set_sc( NPC_AGIUP , SC_INCFLEERATE , SI_BLANK , SCB_FLEE ); + add_sc( NPC_INVISIBLE , SC_CLOAKING ); + set_sc( LK_AURABLADE , SC_AURABLADE , SI_AURABLADE , SCB_NONE ); + set_sc( LK_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); + set_sc( LK_CONCENTRATION , SC_CONCENTRATION , SI_CONCENTRATION , SCB_BATK|SCB_WATK|SCB_HIT|SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_DSPD ); + set_sc( LK_TENSIONRELAX , SC_TENSIONRELAX , SI_TENSIONRELAX , SCB_REGEN ); + set_sc( LK_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); + set_sc( HP_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); + add_sc( HP_BASILICA , SC_BASILICA ); + set_sc( HW_MAGICPOWER , SC_MAGICPOWER , SI_MAGICPOWER , SCB_MATK ); + add_sc( PA_SACRIFICE , SC_SACRIFICE ); + set_sc( PA_GOSPEL , SC_GOSPEL , SI_BLANK , SCB_SPEED|SCB_ASPD ); + add_sc( PA_GOSPEL , SC_SCRESIST ); + add_sc( CH_TIGERFIST , SC_STOP ); + set_sc( ASC_EDP , SC_EDP , SI_EDP , SCB_NONE ); + set_sc( SN_SIGHT , SC_TRUESIGHT , SI_TRUESIGHT , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK|SCB_CRI|SCB_HIT ); + set_sc( SN_WINDWALK , SC_WINDWALK , SI_WINDWALK , SCB_FLEE|SCB_SPEED ); + set_sc( WS_MELTDOWN , SC_MELTDOWN , SI_MELTDOWN , SCB_NONE ); + set_sc( WS_CARTBOOST , SC_CARTBOOST , SI_CARTBOOST , SCB_SPEED ); + set_sc( ST_CHASEWALK , SC_CHASEWALK , SI_BLANK , SCB_SPEED ); + set_sc( ST_REJECTSWORD , SC_REJECTSWORD , SI_REJECTSWORD , SCB_NONE ); + add_sc( ST_REJECTSWORD , SC_AUTOCOUNTER ); + set_sc( CG_MARIONETTE , SC_MARIONETTE , SI_MARIONETTE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + set_sc( CG_MARIONETTE , SC_MARIONETTE2 , SI_MARIONETTE2 , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + add_sc( LK_SPIRALPIERCE , SC_STOP ); + add_sc( LK_HEADCRUSH , SC_BLEEDING ); + set_sc( LK_JOINTBEAT , SC_JOINTBEAT , SI_JOINTBEAT , SCB_BATK|SCB_DEF2|SCB_SPEED|SCB_ASPD ); + add_sc( HW_NAPALMVULCAN , SC_CURSE ); + set_sc( PF_MINDBREAKER , SC_MINDBREAKER , SI_BLANK , SCB_MATK|SCB_MDEF2 ); + add_sc( PF_MEMORIZE , SC_MEMORIZE ); + add_sc( PF_FOGWALL , SC_FOGWALL ); + set_sc( PF_SPIDERWEB , SC_SPIDERWEB , SI_BLANK , SCB_FLEE ); + set_sc( WE_BABY , SC_BABY , SI_BABY , SCB_NONE ); + set_sc( TK_RUN , SC_RUN , SI_RUN , SCB_SPEED|SCB_DSPD ); + set_sc( TK_RUN , SC_SPURT , SI_SPURT , SCB_STR ); + set_sc( TK_READYSTORM , SC_READYSTORM , SI_READYSTORM , SCB_NONE ); + set_sc( TK_READYDOWN , SC_READYDOWN , SI_READYDOWN , SCB_NONE ); + add_sc( TK_DOWNKICK , SC_STUN ); + set_sc( TK_READYTURN , SC_READYTURN , SI_READYTURN , SCB_NONE ); + set_sc( TK_READYCOUNTER , SC_READYCOUNTER , SI_READYCOUNTER , SCB_NONE ); + set_sc( TK_DODGE , SC_DODGE , SI_DODGE , SCB_NONE ); + set_sc( TK_SPTIME , SC_EARTHSCROLL , SI_EARTHSCROLL , SCB_NONE ); + add_sc( TK_SEVENWIND , SC_SEVENWIND ); + set_sc( TK_SEVENWIND , SC_GHOSTWEAPON , SI_GHOSTWEAPON , SCB_ATK_ELE ); + set_sc( TK_SEVENWIND , SC_SHADOWWEAPON , SI_SHADOWWEAPON , SCB_ATK_ELE ); + set_sc( SG_SUN_WARM , SC_WARM , SI_WARM , SCB_NONE ); + add_sc( SG_MOON_WARM , SC_WARM ); + add_sc( SG_STAR_WARM , SC_WARM ); + set_sc( SG_SUN_COMFORT , SC_SUN_COMFORT , SI_SUN_COMFORT , SCB_DEF2 ); + set_sc( SG_MOON_COMFORT , SC_MOON_COMFORT , SI_MOON_COMFORT , SCB_FLEE ); + set_sc( SG_STAR_COMFORT , SC_STAR_COMFORT , SI_STAR_COMFORT , SCB_ASPD ); + add_sc( SG_FRIEND , SC_SKILLRATE_UP ); + set_sc( SG_KNOWLEDGE , SC_KNOWLEDGE , SI_BLANK , SCB_ALL ); + set_sc( SG_FUSION , SC_FUSION , SI_BLANK , SCB_SPEED ); + set_sc( BS_ADRENALINE2 , SC_ADRENALINE2 , SI_ADRENALINE2 , SCB_ASPD ); + set_sc( SL_KAIZEL , SC_KAIZEL , SI_KAIZEL , SCB_NONE ); + set_sc( SL_KAAHI , SC_KAAHI , SI_KAAHI , SCB_NONE ); + set_sc( SL_KAUPE , SC_KAUPE , SI_KAUPE , SCB_NONE ); + set_sc( SL_KAITE , SC_KAITE , SI_KAITE , SCB_NONE ); + add_sc( SL_STUN , SC_STUN ); + set_sc( SL_SWOO , SC_SWOO , SI_BLANK , SCB_SPEED ); + set_sc( SL_SKE , SC_SKE , SI_BLANK , SCB_BATK|SCB_WATK|SCB_DEF|SCB_DEF2 ); + set_sc( SL_SKA , SC_SKA , SI_BLANK , SCB_DEF|SCB_MDEF|SCB_ASPD ); + set_sc( SL_SMA , SC_SMA , SI_SMA , SCB_NONE ); + set_sc( SM_SELFPROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + set_sc( ST_PRESERVE , SC_PRESERVE , SI_PRESERVE , SCB_NONE ); + set_sc( PF_DOUBLECASTING , SC_DOUBLECAST , SI_DOUBLECAST , SCB_NONE ); + set_sc( HW_GRAVITATION , SC_GRAVITATION , SI_BLANK , SCB_ASPD ); + add_sc( WS_CARTTERMINATION , SC_STUN ); + set_sc( WS_OVERTHRUSTMAX , SC_MAXOVERTHRUST , SI_MAXOVERTHRUST , SCB_NONE ); + set_sc( CG_LONGINGFREEDOM , SC_LONGING , SI_BLANK , SCB_SPEED|SCB_ASPD ); + add_sc( CG_HERMODE , SC_HERMODE ); + set_sc( ITEM_ENCHANTARMS , SC_ENCHANTARMS , SI_BLANK , SCB_ATK_ELE ); + set_sc( SL_HIGH , SC_SPIRIT , SI_SPIRIT , SCB_ALL ); + set_sc( KN_ONEHAND , SC_ONEHAND , SI_ONEHAND , SCB_ASPD ); + set_sc( GS_FLING , SC_FLING , SI_BLANK , SCB_DEF|SCB_DEF2 ); + add_sc( GS_CRACKER , SC_STUN ); + add_sc( GS_DISARM , SC_STRIPWEAPON ); + add_sc( GS_PIERCINGSHOT , SC_BLEEDING ); + set_sc( GS_MADNESSCANCEL , SC_MADNESSCANCEL , SI_MADNESSCANCEL , SCB_BATK|SCB_ASPD ); + set_sc( GS_ADJUSTMENT , SC_ADJUSTMENT , SI_ADJUSTMENT , SCB_HIT|SCB_FLEE ); + set_sc( GS_INCREASING , SC_INCREASING , SI_ACCURACY , SCB_AGI|SCB_DEX|SCB_HIT ); + set_sc( GS_GATLINGFEVER , SC_GATLINGFEVER , SI_GATLINGFEVER , SCB_BATK|SCB_FLEE|SCB_SPEED|SCB_ASPD ); + set_sc( NJ_TATAMIGAESHI , SC_TATAMIGAESHI , SI_BLANK , SCB_NONE ); + set_sc( NJ_SUITON , SC_SUITON , SI_BLANK , SCB_AGI|SCB_SPEED ); + add_sc( NJ_HYOUSYOURAKU , SC_FREEZE ); + set_sc( NJ_NEN , SC_NEN , SI_NEN , SCB_STR|SCB_INT ); + set_sc( NJ_UTSUSEMI , SC_UTSUSEMI , SI_UTSUSEMI , SCB_NONE ); + set_sc( NJ_BUNSINJYUTSU , SC_BUNSINJYUTSU , SI_BUNSINJYUTSU , SCB_DYE ); + + add_sc( NPC_ICEBREATH , SC_FREEZE ); + add_sc( NPC_ACIDBREATH , SC_POISON ); + add_sc( NPC_HELLJUDGEMENT , SC_CURSE ); + add_sc( NPC_WIDESILENCE , SC_SILENCE ); + add_sc( NPC_WIDEFREEZE , SC_FREEZE ); + add_sc( NPC_WIDEBLEEDING , SC_BLEEDING ); + add_sc( NPC_WIDESTONE , SC_STONE ); + add_sc( NPC_WIDECONFUSE , SC_CONFUSION ); + add_sc( NPC_WIDESLEEP , SC_SLEEP ); + add_sc( NPC_WIDESIGHT , SC_SIGHT ); + add_sc( NPC_EVILLAND , SC_BLIND ); + add_sc( NPC_MAGICMIRROR , SC_MAGICMIRROR ); + set_sc( NPC_SLOWCAST , SC_SLOWCAST , SI_SLOWCAST , SCB_NONE ); + set_sc( NPC_CRITICALWOUND , SC_CRITICALWOUND , SI_CRITICALWOUND , SCB_NONE ); + set_sc( NPC_STONESKIN , SC_ARMORCHANGE , SI_BLANK , SCB_DEF|SCB_MDEF ); + add_sc( NPC_ANTIMAGIC , SC_ARMORCHANGE ); + add_sc( NPC_WIDECURSE , SC_CURSE ); + add_sc( NPC_WIDESTUN , SC_STUN ); + + set_sc( NPC_HELLPOWER , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); + set_sc( NPC_WIDEHELLDIGNITY , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); + set_sc( NPC_INVINCIBLE , SC_INVINCIBLE , SI_INVINCIBLE , SCB_SPEED ); + set_sc( NPC_INVINCIBLEOFF , SC_INVINCIBLEOFF , SI_BLANK , SCB_SPEED ); + + set_sc( CASH_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( CASH_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( CASH_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); + + set_sc( ALL_PARTYFLEE , SC_PARTYFLEE , SI_PARTYFLEE , SCB_NONE ); + set_sc( ALL_ODINS_POWER , SC_ODINS_POWER , SI_ODINS_POWER , SCB_MATK|SCB_BATK|SCB_MDEF|SCB_DEF ); + + set_sc( CR_SHRINK , SC_SHRINK , SI_SHRINK , SCB_NONE ); + set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); + set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); + set_sc( WZ_SIGHTBLASTER , SC_SIGHTBLASTER , SI_SIGHTBLASTER , SCB_NONE ); + set_sc( DC_WINKCHARM , SC_WINKCHARM , SI_WINKCHARM , SCB_NONE ); + add_sc( MO_BALKYOUNG , SC_STUN ); + add_sc( SA_ELEMENTWATER , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTFIRE , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTGROUND , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTWIND , SC_ELEMENTALCHANGE ); + + set_sc( HLIF_AVOID , SC_AVOID , SI_BLANK , SCB_SPEED ); + set_sc( HLIF_CHANGE , SC_CHANGE , SI_BLANK , SCB_VIT|SCB_INT ); + set_sc( HFLI_FLEET , SC_FLEET , SI_BLANK , SCB_ASPD|SCB_BATK|SCB_WATK ); + set_sc( HFLI_SPEED , SC_SPEED , SI_BLANK , SCB_FLEE ); + set_sc( HAMI_DEFENCE , SC_DEFENCE , SI_BLANK , SCB_DEF ); + set_sc( HAMI_BLOODLUST , SC_BLOODLUST , SI_BLANK , SCB_BATK|SCB_WATK ); + + // Homunculus S + add_sc(MH_STAHL_HORN, SC_STUN); + set_sc(MH_ANGRIFFS_MODUS, SC_ANGRIFFS_MODUS, SI_ANGRIFFS_MODUS, SCB_BATK | SCB_DEF | SCB_FLEE | SCB_MAXHP); + set_sc(MH_GOLDENE_FERSE, SC_GOLDENE_FERSE, SI_GOLDENE_FERSE, SCB_ASPD|SCB_MAXHP); + add_sc( MH_STEINWAND, SC_SAFETYWALL ); + add_sc(MH_ERASER_CUTTER, SC_ERASER_CUTTER); + set_sc(MH_OVERED_BOOST, SC_OVERED_BOOST, SI_BLANK, SCB_FLEE|SCB_ASPD); + add_sc(MH_LIGHT_OF_REGENE, SC_LIGHT_OF_REGENE); + set_sc(MH_VOLCANIC_ASH, SC_ASH, SI_VOLCANIC_ASH, SCB_DEF|SCB_DEF2|SCB_HIT|SCB_BATK|SCB_FLEE); + set_sc(MH_GRANITIC_ARMOR, SC_GRANITIC_ARMOR, SI_GRANITIC_ARMOR, SCB_NONE); + set_sc(MH_MAGMA_FLOW, SC_MAGMA_FLOW, SI_MAGMA_FLOW, SCB_NONE); + set_sc(MH_PYROCLASTIC, SC_PYROCLASTIC, SI_PYROCLASTIC, SCB_BATK|SCB_ATK_ELE); + add_sc(MH_LAVA_SLIDE, SC_BURNING); + set_sc(MH_NEEDLE_OF_PARALYZE, SC_PARALYSIS, SI_NEEDLE_OF_PARALYZE, SCB_DEF2); + add_sc(MH_POISON_MIST, SC_BLIND); + set_sc(MH_PAIN_KILLER, SC_PAIN_KILLER, SI_PAIN_KILLER, SCB_ASPD); + + add_sc(MH_STYLE_CHANGE, SC_STYLE_CHANGE); + set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); + set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); + + + add_sc( MER_CRASH , SC_STUN ); + set_sc( MER_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + add_sc( MS_MAGNUM , SC_WATK_ELEMENT ); + add_sc( MER_SIGHT , SC_SIGHT ); + set_sc( MER_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( MER_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); + add_sc( MER_LEXDIVINA , SC_SILENCE ); + add_sc( MA_LANDMINE , SC_STUN ); + add_sc( MA_SANDMAN , SC_SLEEP ); + add_sc( MA_FREEZINGTRAP , SC_FREEZE ); + set_sc( MER_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); + set_sc( ML_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); + set_sc( MS_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); + set_sc( ML_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); + set_sc( MS_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); + set_sc( MS_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); + add_sc( ML_SPIRALPIERCE , SC_STOP ); + set_sc( MER_QUICKEN , SC_MERC_QUICKEN , SI_BLANK , SCB_ASPD ); + add_sc( ML_DEVOTION , SC_DEVOTION ); + set_sc( MER_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); + set_sc( MER_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( MER_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + + set_sc( GD_LEADERSHIP , SC_LEADERSHIP , SI_BLANK , SCB_STR ); + set_sc( GD_GLORYWOUNDS , SC_GLORYWOUNDS , SI_BLANK , SCB_VIT ); + set_sc( GD_SOULCOLD , SC_SOULCOLD , SI_BLANK , SCB_AGI ); + set_sc( GD_HAWKEYES , SC_HAWKEYES , SI_BLANK , SCB_DEX ); + + set_sc( GD_BATTLEORDER , SC_BATTLEORDERS , SI_BLANK , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( GD_REGENERATION , SC_REGENERATION , SI_BLANK , SCB_REGEN ); + + /** + * Rune Knight + **/ + set_sc( RK_ENCHANTBLADE , SC_ENCHANTBLADE , SI_ENCHANTBLADE , SCB_NONE ); + set_sc( RK_DRAGONHOWLING , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); + set_sc( RK_DEATHBOUND , SC_DEATHBOUND , SI_DEATHBOUND , SCB_NONE ); + set_sc( RK_WINDCUTTER , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); + add_sc( RK_DRAGONBREATH , SC_BURNING ); + set_sc( RK_MILLENNIUMSHIELD , SC_MILLENNIUMSHIELD , SI_REUSE_MILLENNIUMSHIELD , SCB_NONE ); + set_sc( RK_REFRESH , SC_REFRESH , SI_REFRESH , SCB_NONE ); + set_sc( RK_GIANTGROWTH , SC_GIANTGROWTH , SI_GIANTGROWTH , SCB_STR ); + set_sc( RK_STONEHARDSKIN , SC_STONEHARDSKIN , SI_STONEHARDSKIN , SCB_NONE ); + set_sc( RK_VITALITYACTIVATION, SC_VITALITYACTIVATION, SI_VITALITYACTIVATION, SCB_REGEN ); + set_sc( RK_FIGHTINGSPIRIT , SC_FIGHTINGSPIRIT , SI_FIGHTINGSPIRIT , SCB_WATK|SCB_ASPD ); + set_sc( RK_ABUNDANCE , SC_ABUNDANCE , SI_ABUNDANCE , SCB_NONE ); + set_sc( RK_CRUSHSTRIKE , SC_CRUSHSTRIKE , SI_CRUSHSTRIKE , SCB_NONE ); + /** + * GC Guillotine Cross + **/ + set_sc_with_vfx( GC_VENOMIMPRESS , SC_VENOMIMPRESS , SI_VENOMIMPRESS , SCB_NONE ); + set_sc( GC_POISONINGWEAPON , SC_POISONINGWEAPON , SI_POISONINGWEAPON , SCB_NONE ); + set_sc( GC_WEAPONBLOCKING , SC_WEAPONBLOCKING , SI_WEAPONBLOCKING , SCB_NONE ); + set_sc( GC_CLOAKINGEXCEED , SC_CLOAKINGEXCEED , SI_CLOAKINGEXCEED , SCB_SPEED ); + set_sc( GC_HALLUCINATIONWALK , SC_HALLUCINATIONWALK, SI_HALLUCINATIONWALK, SCB_FLEE ); + set_sc( GC_ROLLINGCUTTER , SC_ROLLINGCUTTER , SI_ROLLINGCUTTER , SCB_NONE ); + /** + * Arch Bishop + **/ + set_sc( AB_ADORAMUS , SC_ADORAMUS , SI_ADORAMUS , SCB_AGI|SCB_SPEED ); + add_sc( AB_CLEMENTIA , SC_BLESSING ); + add_sc( AB_CANTO , SC_INCREASEAGI ); + set_sc( AB_EPICLESIS , SC_EPICLESIS , SI_EPICLESIS , SCB_MAXHP ); + add_sc( AB_PRAEFATIO , SC_KYRIE ); + set_sc_with_vfx( AB_ORATIO , SC_ORATIO , SI_ORATIO , SCB_NONE ); + set_sc( AB_LAUDAAGNUS , SC_LAUDAAGNUS , SI_LAUDAAGNUS , SCB_VIT ); + set_sc( AB_LAUDARAMUS , SC_LAUDARAMUS , SI_LAUDARAMUS , SCB_LUK ); + set_sc( AB_RENOVATIO , SC_RENOVATIO , SI_RENOVATIO , SCB_REGEN ); + set_sc( AB_EXPIATIO , SC_EXPIATIO , SI_EXPIATIO , SCB_ATK_ELE ); + set_sc( AB_DUPLELIGHT , SC_DUPLELIGHT , SI_DUPLELIGHT , SCB_NONE ); + set_sc( AB_SECRAMENT , SC_SECRAMENT , SI_SECRAMENT , SCB_NONE ); + /** + * Warlock + **/ + add_sc( WL_WHITEIMPRISON , SC_WHITEIMPRISON ); + set_sc_with_vfx( WL_FROSTMISTY , SC_FREEZING , SI_FROSTMISTY , SCB_ASPD|SCB_SPEED|SCB_DEF|SCB_DEF2 ); + set_sc( WL_MARSHOFABYSS , SC_MARSHOFABYSS , SI_MARSHOFABYSS , SCB_SPEED|SCB_FLEE|SCB_DEF|SCB_MDEF ); + set_sc(WL_RECOGNIZEDSPELL , SC_RECOGNIZEDSPELL , SI_RECOGNIZEDSPELL , SCB_MATK); + set_sc( WL_STASIS , SC_STASIS , SI_STASIS , SCB_NONE ); + /** + * Ranger + **/ + set_sc( RA_FEARBREEZE , SC_FEARBREEZE , SI_FEARBREEZE , SCB_NONE ); + set_sc( RA_ELECTRICSHOCKER , SC_ELECTRICSHOCKER , SI_ELECTRICSHOCKER , SCB_NONE ); + set_sc( RA_WUGDASH , SC_WUGDASH , SI_WUGDASH , SCB_SPEED ); + set_sc( RA_CAMOUFLAGE , SC_CAMOUFLAGE , SI_CAMOUFLAGE , SCB_SPEED ); + add_sc( RA_MAGENTATRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_COBALTTRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_MAIZETRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_VERDURETRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_FIRINGTRAP , SC_BURNING ); + set_sc_with_vfx( RA_ICEBOUNDTRAP , SC_FREEZING , SI_FROSTMISTY , SCB_NONE ); + /** + * Mechanic + **/ + set_sc( NC_ACCELERATION , SC_ACCELERATION , SI_ACCELERATION , SCB_SPEED ); + set_sc( NC_HOVERING , SC_HOVERING , SI_HOVERING , SCB_SPEED ); + set_sc( NC_SHAPESHIFT , SC_SHAPESHIFT , SI_SHAPESHIFT , SCB_DEF_ELE ); + set_sc( NC_INFRAREDSCAN , SC_INFRAREDSCAN , SI_INFRAREDSCAN , SCB_FLEE ); + set_sc( NC_ANALYZE , SC_ANALYZE , SI_ANALYZE , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2 ); + set_sc( NC_MAGNETICFIELD , SC_MAGNETICFIELD , SI_MAGNETICFIELD , SCB_NONE ); + set_sc( NC_NEUTRALBARRIER , SC_NEUTRALBARRIER , SI_NEUTRALBARRIER , SCB_NONE ); + set_sc( NC_STEALTHFIELD , SC_STEALTHFIELD , SI_STEALTHFIELD , SCB_NONE ); + /** + * Royal Guard + **/ + set_sc( LG_REFLECTDAMAGE , SC_REFLECTDAMAGE , SI_LG_REFLECTDAMAGE, SCB_NONE ); + set_sc( LG_FORCEOFVANGUARD , SC_FORCEOFVANGUARD , SI_FORCEOFVANGUARD , SCB_MAXHP|SCB_DEF ); + set_sc( LG_EXEEDBREAK , SC_EXEEDBREAK , SI_EXEEDBREAK , SCB_NONE ); + set_sc( LG_PRESTIGE , SC_PRESTIGE , SI_PRESTIGE , SCB_DEF ); + set_sc( LG_BANDING , SC_BANDING , SI_BANDING , SCB_DEF2|SCB_WATK );// Renewal: atk2 & def2 + set_sc( LG_PIETY , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); + set_sc( LG_EARTHDRIVE , SC_EARTHDRIVE , SI_EARTHDRIVE , SCB_DEF|SCB_ASPD ); + set_sc( LG_INSPIRATION , SC_INSPIRATION , SI_INSPIRATION , SCB_MAXHP|SCB_WATK|SCB_HIT|SCB_VIT|SCB_AGI|SCB_STR|SCB_DEX|SCB_INT|SCB_LUK); + set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_DEF , SI_SHIELDSPELL_DEF , SCB_WATK ); + set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_REF , SI_SHIELDSPELL_REF , SCB_DEF ); + /** + * Shadow Chaser + **/ + set_sc( SC_REPRODUCE , SC__REPRODUCE , SI_REPRODUCE , SCB_NONE ); + set_sc( SC_AUTOSHADOWSPELL , SC__AUTOSHADOWSPELL, SI_AUTOSHADOWSPELL , SCB_NONE ); + set_sc( SC_SHADOWFORM , SC__SHADOWFORM , SI_SHADOWFORM , SCB_NONE ); + set_sc( SC_BODYPAINT , SC__BODYPAINT , SI_BODYPAINT , SCB_ASPD ); + set_sc( SC_INVISIBILITY , SC__INVISIBILITY , SI_INVISIBILITY , SCB_ASPD|SCB_CRI|SCB_ATK_ELE ); + set_sc( SC_DEADLYINFECT , SC__DEADLYINFECT , SI_DEADLYINFECT , SCB_NONE ); + set_sc( SC_ENERVATION , SC__ENERVATION , SI_ENERVATION , SCB_BATK ); + set_sc( SC_GROOMY , SC__GROOMY , SI_GROOMY , SCB_ASPD|SCB_HIT|SCB_SPEED ); + set_sc( SC_IGNORANCE , SC__IGNORANCE , SI_IGNORANCE , SCB_NONE ); + set_sc( SC_LAZINESS , SC__LAZINESS , SI_LAZINESS , SCB_FLEE ); + set_sc( SC_UNLUCKY , SC__UNLUCKY , SI_UNLUCKY , SCB_CRI|SCB_FLEE2 ); + set_sc( SC_WEAKNESS , SC__WEAKNESS , SI_WEAKNESS , SCB_FLEE2|SCB_MAXHP ); + set_sc( SC_STRIPACCESSARY , SC__STRIPACCESSORY , SI_STRIPACCESSARY , SCB_DEX|SCB_INT|SCB_LUK ); + set_sc_with_vfx( SC_MANHOLE , SC__MANHOLE , SI_MANHOLE , SCB_NONE ); + add_sc( SC_CHAOSPANIC , SC_CONFUSION ); + set_sc_with_vfx( SC_BLOODYLUST , SC__BLOODYLUST , SI_BLOODYLUST , SCB_DEF | SCB_DEF2 | SCB_MDEF | SCB_MDEF2 | SCB_FLEE | SCB_SPEED | SCB_ASPD | SCB_MAXHP | SCB_REGEN ); + /** + * Sura + **/ + add_sc( SR_DRAGONCOMBO , SC_STUN ); + add_sc( SR_EARTHSHAKER , SC_STUN ); + set_sc( SR_CRESCENTELBOW , SC_CRESCENTELBOW , SI_CRESCENTELBOW , SCB_NONE ); + set_sc_with_vfx( SR_CURSEDCIRCLE , SC_CURSEDCIRCLE_TARGET, SI_CURSEDCIRCLE_TARGET , SCB_NONE ); + set_sc( SR_LIGHTNINGWALK , SC_LIGHTNINGWALK , SI_LIGHTNINGWALK , SCB_NONE ); + set_sc( SR_RAISINGDRAGON , SC_RAISINGDRAGON , SI_RAISINGDRAGON , SCB_REGEN|SCB_MAXHP|SCB_MAXSP ); + set_sc( SR_GENTLETOUCH_ENERGYGAIN, SC_GT_ENERGYGAIN , SI_GENTLETOUCH_ENERGYGAIN, SCB_NONE ); + set_sc( SR_GENTLETOUCH_CHANGE , SC_GT_CHANGE , SI_GENTLETOUCH_CHANGE , SCB_ASPD|SCB_MDEF|SCB_MAXHP ); + set_sc( SR_GENTLETOUCH_REVITALIZE, SC_GT_REVITALIZE , SI_GENTLETOUCH_REVITALIZE, SCB_MAXHP|SCB_REGEN ); + /** + * Wanderer / Minstrel + **/ + set_sc( WA_SWING_DANCE , SC_SWINGDANCE , SI_SWINGDANCE , SCB_SPEED|SCB_ASPD ); + set_sc( WA_SYMPHONY_OF_LOVER , SC_SYMPHONYOFLOVER , SI_SYMPHONYOFLOVERS , SCB_MDEF ); + set_sc( WA_MOONLIT_SERENADE , SC_MOONLITSERENADE , SI_MOONLITSERENADE , SCB_MATK ); + set_sc( MI_RUSH_WINDMILL , SC_RUSHWINDMILL , SI_RUSHWINDMILL , SCB_BATK ); + set_sc( MI_ECHOSONG , SC_ECHOSONG , SI_ECHOSONG , SCB_DEF2 ); + set_sc( MI_HARMONIZE , SC_HARMONIZE , SI_HARMONIZE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + set_sc_with_vfx( WM_POEMOFNETHERWORLD , SC_NETHERWORLD , SI_NETHERWORLD , SCB_NONE ); + set_sc_with_vfx( WM_VOICEOFSIREN , SC_VOICEOFSIREN , SI_VOICEOFSIREN , SCB_NONE ); + set_sc_with_vfx( WM_LULLABY_DEEPSLEEP , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); + set_sc( WM_SIRCLEOFNATURE , SC_SIRCLEOFNATURE , SI_SIRCLEOFNATURE , SCB_NONE ); + set_sc( WM_GLOOMYDAY , SC_GLOOMYDAY , SI_GLOOMYDAY , SCB_FLEE|SCB_ASPD ); + set_sc( WM_SONG_OF_MANA , SC_SONGOFMANA , SI_SONGOFMANA , SCB_NONE ); + set_sc( WM_DANCE_WITH_WUG , SC_DANCEWITHWUG , SI_DANCEWITHWUG , SCB_ASPD ); + set_sc( WM_SATURDAY_NIGHT_FEVER , SC_SATURDAYNIGHTFEVER , SI_SATURDAYNIGHTFEVER , SCB_BATK|SCB_DEF|SCB_FLEE|SCB_REGEN ); + set_sc( WM_LERADS_DEW , SC_LERADSDEW , SI_LERADSDEW , SCB_MAXHP ); + set_sc( WM_MELODYOFSINK , SC_MELODYOFSINK , SI_MELODYOFSINK , SCB_BATK|SCB_MATK ); + set_sc( WM_BEYOND_OF_WARCRY , SC_BEYONDOFWARCRY , SI_WARCRYOFBEYOND , SCB_BATK|SCB_MATK ); + set_sc( WM_UNLIMITED_HUMMING_VOICE, SC_UNLIMITEDHUMMINGVOICE, SI_UNLIMITEDHUMMINGVOICE, SCB_NONE ); + /** + * Sorcerer + **/ + set_sc( SO_FIREWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); + set_sc( SO_ELECTRICWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); + set_sc( SO_SPELLFIST , SC_SPELLFIST , SI_SPELLFIST , SCB_NONE ); + set_sc_with_vfx( SO_DIAMONDDUST , SC_CRYSTALIZE , SI_COLD , SCB_NONE ); // it does show the snow icon on mobs but doesn't affect it. + add_sc( SO_CLOUD_KILL , SC_POISON ); + set_sc( SO_STRIKING , SC_STRIKING , SI_STRIKING , SCB_WATK|SCB_CRI ); + set_sc( SO_WARMER , SC_WARMER , SI_WARMER , SCB_NONE ); + set_sc( SO_VACUUM_EXTREME , SC_VACUUM_EXTREME , SI_VACUUM_EXTREME , SCB_NONE ); + set_sc( SO_ARRULLO , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); + set_sc( SO_FIRE_INSIGNIA , SC_FIRE_INSIGNIA , SI_FIRE_INSIGNIA , SCB_MATK | SCB_BATK | SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_WATER_INSIGNIA , SC_WATER_INSIGNIA , SI_WATER_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_WIND_INSIGNIA , SC_WIND_INSIGNIA , SI_WIND_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_EARTH_INSIGNIA , SC_EARTH_INSIGNIA , SI_EARTH_INSIGNIA , SCB_MDEF|SCB_DEF|SCB_MAXHP|SCB_MAXSP|SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + /** + * Genetic + **/ + set_sc( GN_CARTBOOST , SC_GN_CARTBOOST, SI_CARTSBOOST , SCB_SPEED ); + set_sc( GN_THORNS_TRAP , SC_THORNSTRAP , SI_THORNTRAP , SCB_NONE ); + set_sc_with_vfx( GN_BLOOD_SUCKER , SC_BLOODSUCKER , SI_BLOODSUCKER , SCB_NONE ); + set_sc( GN_WALLOFTHORN , SC_STOP , SI_BLANK , SCB_NONE ); + set_sc( GN_FIRE_EXPANSION_SMOKE_POWDER, SC_SMOKEPOWDER , SI_FIRE_EXPANSION_SMOKE_POWDER, SCB_NONE ); + set_sc( GN_FIRE_EXPANSION_TEAR_GAS , SC_TEARGAS , SI_FIRE_EXPANSION_TEAR_GAS , SCB_NONE ); + set_sc( GN_MANDRAGORA , SC_MANDRAGORA , SI_MANDRAGORA , SCB_INT ); + + // Elemental Spirit summoner's 'side' status changes. + set_sc( EL_CIRCLE_OF_FIRE , SC_CIRCLE_OF_FIRE_OPTION, SI_CIRCLE_OF_FIRE_OPTION, SCB_NONE ); + set_sc( EL_FIRE_CLOAK , SC_FIRE_CLOAK_OPTION , SI_FIRE_CLOAK_OPTION , SCB_ALL ); + set_sc( EL_WATER_SCREEN , SC_WATER_SCREEN_OPTION , SI_WATER_SCREEN_OPTION , SCB_NONE ); + set_sc( EL_WATER_DROP , SC_WATER_DROP_OPTION , SI_WATER_DROP_OPTION , SCB_ALL ); + set_sc( EL_WATER_BARRIER , SC_WATER_BARRIER , SI_WATER_BARRIER , SCB_MDEF|SCB_WATK|SCB_MATK|SCB_FLEE ); + set_sc( EL_WIND_STEP , SC_WIND_STEP_OPTION , SI_WIND_STEP_OPTION , SCB_SPEED|SCB_FLEE ); + set_sc( EL_WIND_CURTAIN , SC_WIND_CURTAIN_OPTION , SI_WIND_CURTAIN_OPTION , SCB_ALL ); + set_sc( EL_ZEPHYR , SC_ZEPHYR , SI_ZEPHYR , SCB_FLEE ); + set_sc( EL_SOLID_SKIN , SC_SOLID_SKIN_OPTION , SI_SOLID_SKIN_OPTION , SCB_DEF|SCB_MAXHP ); + set_sc( EL_STONE_SHIELD , SC_STONE_SHIELD_OPTION , SI_STONE_SHIELD_OPTION , SCB_ALL ); + set_sc( EL_POWER_OF_GAIA , SC_POWER_OF_GAIA , SI_POWER_OF_GAIA , SCB_MAXHP|SCB_DEF|SCB_SPEED ); + set_sc( EL_PYROTECHNIC , SC_PYROTECHNIC_OPTION , SI_PYROTECHNIC_OPTION , SCB_WATK ); + set_sc( EL_HEATER , SC_HEATER_OPTION , SI_HEATER_OPTION , SCB_WATK ); + set_sc( EL_TROPIC , SC_TROPIC_OPTION , SI_TROPIC_OPTION , SCB_WATK ); + set_sc( EL_AQUAPLAY , SC_AQUAPLAY_OPTION , SI_AQUAPLAY_OPTION , SCB_MATK ); + set_sc( EL_COOLER , SC_COOLER_OPTION , SI_COOLER_OPTION , SCB_MATK ); + set_sc( EL_CHILLY_AIR , SC_CHILLY_AIR_OPTION , SI_CHILLY_AIR_OPTION , SCB_MATK ); + set_sc( EL_GUST , SC_GUST_OPTION , SI_GUST_OPTION , SCB_ASPD ); + set_sc( EL_BLAST , SC_BLAST_OPTION , SI_BLAST_OPTION , SCB_ASPD ); + set_sc( EL_WILD_STORM , SC_WILD_STORM_OPTION , SI_WILD_STORM_OPTION , SCB_ASPD ); + set_sc( EL_PETROLOGY , SC_PETROLOGY_OPTION , SI_PETROLOGY_OPTION , SCB_MAXHP ); + set_sc( EL_CURSED_SOIL , SC_CURSED_SOIL_OPTION , SI_CURSED_SOIL_OPTION , SCB_NONE ); + set_sc( EL_UPHEAVAL , SC_UPHEAVAL_OPTION , SI_UPHEAVAL_OPTION , SCB_NONE ); + set_sc( EL_TIDAL_WEAPON , SC_TIDAL_WEAPON_OPTION , SI_TIDAL_WEAPON_OPTION , SCB_ALL ); + set_sc( EL_ROCK_CRUSHER , SC_ROCK_CRUSHER , SI_ROCK_CRUSHER , SCB_DEF ); + set_sc( EL_ROCK_CRUSHER_ATK, SC_ROCK_CRUSHER_ATK , SI_ROCK_CRUSHER_ATK , SCB_SPEED ); + + add_sc( KO_YAMIKUMO , SC_HIDING ); + set_sc_with_vfx( KO_JYUMONJIKIRI , SC_JYUMONJIKIRI , SI_KO_JYUMONJIKIRI , SCB_NONE ); + add_sc( KO_MAKIBISHI , SC_STUN ); + set_sc( KO_MEIKYOUSISUI , SC_MEIKYOUSISUI , SI_MEIKYOUSISUI , SCB_NONE ); + set_sc( KO_KYOUGAKU , SC_KYOUGAKU , SI_KYOUGAKU , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + add_sc( KO_JYUSATSU , SC_CURSE ); + set_sc( KO_ZENKAI , SC_ZENKAI , SI_ZENKAI , SCB_NONE ); + set_sc( KO_IZAYOI , SC_IZAYOI , SI_IZAYOI , SCB_MATK ); + set_sc( KG_KYOMU , SC_KYOMU , SI_KYOMU , SCB_NONE ); + set_sc( KG_KAGEMUSYA , SC_KAGEMUSYA , SI_KAGEMUSYA , SCB_NONE ); + set_sc( KG_KAGEHUMI , SC_KAGEHUMI , SI_KG_KAGEHUMI , SCB_NONE ); + set_sc( OB_ZANGETSU , SC_ZANGETSU , SI_ZANGETSU , SCB_MATK|SCB_BATK ); + set_sc_with_vfx( OB_AKAITSUKI , SC_AKAITSUKI , SI_AKAITSUKI , SCB_NONE ); + set_sc( OB_OBOROGENSOU , SC_GENSOU , SI_GENSOU , SCB_NONE ); + + // Storing the target job rather than simply SC_SPIRIT simplifies code later on. + SkillStatusChangeTable[SL_ALCHEMIST] = (sc_type)MAPID_ALCHEMIST, + SkillStatusChangeTable[SL_MONK] = (sc_type)MAPID_MONK, + SkillStatusChangeTable[SL_STAR] = (sc_type)MAPID_STAR_GLADIATOR, + SkillStatusChangeTable[SL_SAGE] = (sc_type)MAPID_SAGE, + SkillStatusChangeTable[SL_CRUSADER] = (sc_type)MAPID_CRUSADER, + SkillStatusChangeTable[SL_SUPERNOVICE] = (sc_type)MAPID_SUPER_NOVICE, + SkillStatusChangeTable[SL_KNIGHT] = (sc_type)MAPID_KNIGHT, + SkillStatusChangeTable[SL_WIZARD] = (sc_type)MAPID_WIZARD, + SkillStatusChangeTable[SL_PRIEST] = (sc_type)MAPID_PRIEST, + SkillStatusChangeTable[SL_BARDDANCER] = (sc_type)MAPID_BARDDANCER, + SkillStatusChangeTable[SL_ROGUE] = (sc_type)MAPID_ROGUE, + SkillStatusChangeTable[SL_ASSASIN] = (sc_type)MAPID_ASSASSIN, + SkillStatusChangeTable[SL_BLACKSMITH] = (sc_type)MAPID_BLACKSMITH, + SkillStatusChangeTable[SL_HUNTER] = (sc_type)MAPID_HUNTER, + SkillStatusChangeTable[SL_SOULLINKER] = (sc_type)MAPID_SOUL_LINKER, + + //Status that don't have a skill associated. + StatusIconChangeTable[SC_WEIGHT50] = SI_WEIGHT50; + StatusIconChangeTable[SC_WEIGHT90] = SI_WEIGHT90; + StatusIconChangeTable[SC_ASPDPOTION0] = SI_ASPDPOTION0; + StatusIconChangeTable[SC_ASPDPOTION1] = SI_ASPDPOTION1; + StatusIconChangeTable[SC_ASPDPOTION2] = SI_ASPDPOTION2; + StatusIconChangeTable[SC_ASPDPOTION3] = SI_ASPDPOTIONINFINITY; + StatusIconChangeTable[SC_SPEEDUP0] = SI_MOVHASTE_HORSE; + StatusIconChangeTable[SC_SPEEDUP1] = SI_SPEEDPOTION1; + StatusIconChangeTable[SC_INCSTR] = SI_INCSTR; + StatusIconChangeTable[SC_MIRACLE] = SI_SPIRIT; + StatusIconChangeTable[SC_INTRAVISION] = SI_INTRAVISION; + StatusIconChangeTable[SC_STRFOOD] = SI_FOODSTR; + StatusIconChangeTable[SC_AGIFOOD] = SI_FOODAGI; + StatusIconChangeTable[SC_VITFOOD] = SI_FOODVIT; + StatusIconChangeTable[SC_INTFOOD] = SI_FOODINT; + StatusIconChangeTable[SC_DEXFOOD] = SI_FOODDEX; + StatusIconChangeTable[SC_LUKFOOD] = SI_FOODLUK; + StatusIconChangeTable[SC_FLEEFOOD]= SI_FOODFLEE; + StatusIconChangeTable[SC_HITFOOD] = SI_FOODHIT; + StatusIconChangeTable[SC_MANU_ATK] = SI_MANU_ATK; + StatusIconChangeTable[SC_MANU_DEF] = SI_MANU_DEF; + StatusIconChangeTable[SC_SPL_ATK] = SI_SPL_ATK; + StatusIconChangeTable[SC_SPL_DEF] = SI_SPL_DEF; + StatusIconChangeTable[SC_MANU_MATK] = SI_MANU_MATK; + StatusIconChangeTable[SC_SPL_MATK] = SI_SPL_MATK; + StatusIconChangeTable[SC_ATKPOTION] = SI_PLUSATTACKPOWER; + StatusIconChangeTable[SC_MATKPOTION] = SI_PLUSMAGICPOWER; + //Cash Items + StatusIconChangeTable[SC_FOOD_STR_CASH] = SI_FOOD_STR_CASH; + StatusIconChangeTable[SC_FOOD_AGI_CASH] = SI_FOOD_AGI_CASH; + StatusIconChangeTable[SC_FOOD_VIT_CASH] = SI_FOOD_VIT_CASH; + StatusIconChangeTable[SC_FOOD_DEX_CASH] = SI_FOOD_DEX_CASH; + StatusIconChangeTable[SC_FOOD_INT_CASH] = SI_FOOD_INT_CASH; + StatusIconChangeTable[SC_FOOD_LUK_CASH] = SI_FOOD_LUK_CASH; + StatusIconChangeTable[SC_EXPBOOST] = SI_EXPBOOST; + StatusIconChangeTable[SC_ITEMBOOST] = SI_ITEMBOOST; + StatusIconChangeTable[SC_JEXPBOOST] = SI_CASH_PLUSONLYJOBEXP; + StatusIconChangeTable[SC_LIFEINSURANCE] = SI_LIFEINSURANCE; + StatusIconChangeTable[SC_BOSSMAPINFO] = SI_BOSSMAPINFO; + StatusIconChangeTable[SC_DEF_RATE] = SI_DEF_RATE; + StatusIconChangeTable[SC_MDEF_RATE] = SI_MDEF_RATE; + StatusIconChangeTable[SC_INCCRI] = SI_INCCRI; + StatusIconChangeTable[SC_INCFLEE2] = SI_PLUSAVOIDVALUE; + StatusIconChangeTable[SC_INCHEALRATE] = SI_INCHEALRATE; + StatusIconChangeTable[SC_S_LIFEPOTION] = SI_S_LIFEPOTION; + StatusIconChangeTable[SC_L_LIFEPOTION] = SI_L_LIFEPOTION; + StatusIconChangeTable[SC_SPCOST_RATE] = SI_ATKER_BLOOD; + StatusIconChangeTable[SC_COMMONSC_RESIST] = SI_TARGET_BLOOD; + // Mercenary Bonus Effects + StatusIconChangeTable[SC_MERC_FLEEUP] = SI_MERC_FLEEUP; + StatusIconChangeTable[SC_MERC_ATKUP] = SI_MERC_ATKUP; + StatusIconChangeTable[SC_MERC_HPUP] = SI_MERC_HPUP; + StatusIconChangeTable[SC_MERC_SPUP] = SI_MERC_SPUP; + StatusIconChangeTable[SC_MERC_HITUP] = SI_MERC_HITUP; + // Warlock Spheres + StatusIconChangeTable[SC_SPHERE_1] = SI_SPHERE_1; + StatusIconChangeTable[SC_SPHERE_2] = SI_SPHERE_2; + StatusIconChangeTable[SC_SPHERE_3] = SI_SPHERE_3; + StatusIconChangeTable[SC_SPHERE_4] = SI_SPHERE_4; + StatusIconChangeTable[SC_SPHERE_5] = SI_SPHERE_5; + // Warlock Preserved spells + StatusIconChangeTable[SC_SPELLBOOK1] = SI_SPELLBOOK1; + StatusIconChangeTable[SC_SPELLBOOK2] = SI_SPELLBOOK2; + StatusIconChangeTable[SC_SPELLBOOK3] = SI_SPELLBOOK3; + StatusIconChangeTable[SC_SPELLBOOK4] = SI_SPELLBOOK4; + StatusIconChangeTable[SC_SPELLBOOK5] = SI_SPELLBOOK5; + StatusIconChangeTable[SC_SPELLBOOK6] = SI_SPELLBOOK6; + StatusIconChangeTable[SC_MAXSPELLBOOK] = SI_SPELLBOOK7; + + StatusIconChangeTable[SC_NEUTRALBARRIER_MASTER] = SI_NEUTRALBARRIER_MASTER; + StatusIconChangeTable[SC_STEALTHFIELD_MASTER] = SI_STEALTHFIELD_MASTER; + StatusIconChangeTable[SC_OVERHEAT] = SI_OVERHEAT; + StatusIconChangeTable[SC_OVERHEAT_LIMITPOINT] = SI_OVERHEAT_LIMITPOINT; + + StatusIconChangeTable[SC_HALLUCINATIONWALK_POSTDELAY] = SI_HALLUCINATIONWALK_POSTDELAY; + StatusIconChangeTable[SC_TOXIN] = SI_TOXIN; + StatusIconChangeTable[SC_PARALYSE] = SI_PARALYSE; + StatusIconChangeTable[SC_VENOMBLEED] = SI_VENOMBLEED; + StatusIconChangeTable[SC_MAGICMUSHROOM] = SI_MAGICMUSHROOM; + StatusIconChangeTable[SC_DEATHHURT] = SI_DEATHHURT; + StatusIconChangeTable[SC_PYREXIA] = SI_PYREXIA; + StatusIconChangeTable[SC_OBLIVIONCURSE] = SI_OBLIVIONCURSE; + StatusIconChangeTable[SC_LEECHESEND] = SI_LEECHESEND; + + StatusIconChangeTable[SC_SHIELDSPELL_DEF] = SI_SHIELDSPELL_DEF; + StatusIconChangeTable[SC_SHIELDSPELL_MDEF] = SI_SHIELDSPELL_MDEF; + StatusIconChangeTable[SC_SHIELDSPELL_REF] = SI_SHIELDSPELL_REF; + StatusIconChangeTable[SC_BANDING_DEFENCE] = SI_BANDING_DEFENCE; + + StatusIconChangeTable[SC_GLOOMYDAY_SK] = SI_GLOOMYDAY; + + StatusIconChangeTable[SC_CURSEDCIRCLE_ATKER] = SI_CURSEDCIRCLE_ATKER; + + StatusIconChangeTable[SC_STOMACHACHE] = SI_STOMACHACHE; + StatusIconChangeTable[SC_MYSTERIOUS_POWDER] = SI_MYSTERIOUS_POWDER; + StatusIconChangeTable[SC_MELON_BOMB] = SI_MELON_BOMB; + StatusIconChangeTable[SC_BANANA_BOMB] = SI_BANANA_BOMB; + StatusIconChangeTable[SC_BANANA_BOMB_SITDOWN] = SI_BANANA_BOMB_SITDOWN_POSTDELAY; + + //Genetics New Food Items Status Icons + StatusIconChangeTable[SC_SAVAGE_STEAK] = SI_SAVAGE_STEAK; + StatusIconChangeTable[SC_COCKTAIL_WARG_BLOOD] = SI_COCKTAIL_WARG_BLOOD; + StatusIconChangeTable[SC_MINOR_BBQ] = SI_MINOR_BBQ; + StatusIconChangeTable[SC_SIROMA_ICE_TEA] = SI_SIROMA_ICE_TEA; + StatusIconChangeTable[SC_DROCERA_HERB_STEAMED] = SI_DROCERA_HERB_STEAMED; + StatusIconChangeTable[SC_PUTTI_TAILS_NOODLES] = SI_PUTTI_TAILS_NOODLES; + + StatusIconChangeTable[SC_BOOST500] |= SI_BOOST500; + StatusIconChangeTable[SC_FULL_SWING_K] |= SI_FULL_SWING_K; + StatusIconChangeTable[SC_MANA_PLUS] |= SI_MANA_PLUS; + StatusIconChangeTable[SC_MUSTLE_M] |= SI_MUSTLE_M; + StatusIconChangeTable[SC_LIFE_FORCE_F] |= SI_LIFE_FORCE_F; + StatusIconChangeTable[SC_EXTRACT_WHITE_POTION_Z] |= SI_EXTRACT_WHITE_POTION_Z; + StatusIconChangeTable[SC_VITATA_500] |= SI_VITATA_500; + StatusIconChangeTable[SC_EXTRACT_SALAMINE_JUICE] |= SI_EXTRACT_SALAMINE_JUICE; + + // Elemental Spirit's 'side' status change icons. + StatusIconChangeTable[SC_CIRCLE_OF_FIRE] = SI_CIRCLE_OF_FIRE; + StatusIconChangeTable[SC_FIRE_CLOAK] = SI_FIRE_CLOAK; + StatusIconChangeTable[SC_WATER_SCREEN] = SI_WATER_SCREEN; + StatusIconChangeTable[SC_WATER_DROP] = SI_WATER_DROP; + StatusIconChangeTable[SC_WIND_STEP] = SI_WIND_STEP; + StatusIconChangeTable[SC_WIND_CURTAIN] = SI_WIND_CURTAIN; + StatusIconChangeTable[SC_SOLID_SKIN] = SI_SOLID_SKIN; + StatusIconChangeTable[SC_STONE_SHIELD] = SI_STONE_SHIELD; + StatusIconChangeTable[SC_PYROTECHNIC] = SI_PYROTECHNIC; + StatusIconChangeTable[SC_HEATER] = SI_HEATER; + StatusIconChangeTable[SC_TROPIC] = SI_TROPIC; + StatusIconChangeTable[SC_AQUAPLAY] = SI_AQUAPLAY; + StatusIconChangeTable[SC_COOLER] = SI_COOLER; + StatusIconChangeTable[SC_CHILLY_AIR] = SI_CHILLY_AIR; + StatusIconChangeTable[SC_GUST] = SI_GUST; + StatusIconChangeTable[SC_BLAST] = SI_BLAST; + StatusIconChangeTable[SC_WILD_STORM] = SI_WILD_STORM; + StatusIconChangeTable[SC_PETROLOGY] = SI_PETROLOGY; + StatusIconChangeTable[SC_CURSED_SOIL] = SI_CURSED_SOIL; + StatusIconChangeTable[SC_UPHEAVAL] = SI_UPHEAVAL; + StatusIconChangeTable[SC_PUSH_CART] = SI_ON_PUSH_CART; + + //Other SC which are not necessarily associated to skills. + StatusChangeFlagTable[SC_ASPDPOTION0] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION1] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION2] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION3] = SCB_ASPD; + StatusChangeFlagTable[SC_SPEEDUP0] = SCB_SPEED; + StatusChangeFlagTable[SC_SPEEDUP1] = SCB_SPEED; + StatusChangeFlagTable[SC_ATKPOTION] = SCB_BATK; + StatusChangeFlagTable[SC_MATKPOTION] = SCB_MATK; + StatusChangeFlagTable[SC_INCALLSTATUS] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK; + StatusChangeFlagTable[SC_INCSTR] |= SCB_STR; + StatusChangeFlagTable[SC_INCAGI] |= SCB_AGI; + StatusChangeFlagTable[SC_INCVIT] |= SCB_VIT; + StatusChangeFlagTable[SC_INCINT] |= SCB_INT; + StatusChangeFlagTable[SC_INCDEX] |= SCB_DEX; + StatusChangeFlagTable[SC_INCLUK] |= SCB_LUK; + StatusChangeFlagTable[SC_INCHIT] |= SCB_HIT; + StatusChangeFlagTable[SC_INCHITRATE] |= SCB_HIT; + StatusChangeFlagTable[SC_INCFLEE] |= SCB_FLEE; + StatusChangeFlagTable[SC_INCFLEERATE] |= SCB_FLEE; + StatusChangeFlagTable[SC_INCCRI] |= SCB_CRI; + StatusChangeFlagTable[SC_INCASPDRATE] |= SCB_ASPD; + StatusChangeFlagTable[SC_INCFLEE2] |= SCB_FLEE2; + StatusChangeFlagTable[SC_INCMHPRATE] |= SCB_MAXHP; + StatusChangeFlagTable[SC_INCMSPRATE] |= SCB_MAXSP; + StatusChangeFlagTable[SC_INCMHP] |= SCB_MAXHP; + StatusChangeFlagTable[SC_INCMSP] |= SCB_MAXSP; + StatusChangeFlagTable[SC_INCATKRATE] |= SCB_BATK|SCB_WATK; + StatusChangeFlagTable[SC_INCMATKRATE] |= SCB_MATK; + StatusChangeFlagTable[SC_INCDEFRATE] |= SCB_DEF; + StatusChangeFlagTable[SC_STRFOOD] |= SCB_STR; + StatusChangeFlagTable[SC_AGIFOOD] |= SCB_AGI; + StatusChangeFlagTable[SC_VITFOOD] |= SCB_VIT; + StatusChangeFlagTable[SC_INTFOOD] |= SCB_INT; + StatusChangeFlagTable[SC_DEXFOOD] |= SCB_DEX; + StatusChangeFlagTable[SC_LUKFOOD] |= SCB_LUK; + StatusChangeFlagTable[SC_HITFOOD] |= SCB_HIT; + StatusChangeFlagTable[SC_FLEEFOOD] |= SCB_FLEE; + StatusChangeFlagTable[SC_BATKFOOD] |= SCB_BATK; + StatusChangeFlagTable[SC_WATKFOOD] |= SCB_WATK; + StatusChangeFlagTable[SC_MATKFOOD] |= SCB_MATK; + StatusChangeFlagTable[SC_ARMOR_ELEMENT] |= SCB_ALL; + StatusChangeFlagTable[SC_ARMOR_RESIST] |= SCB_ALL; + StatusChangeFlagTable[SC_SPCOST_RATE] |= SCB_ALL; + StatusChangeFlagTable[SC_WALKSPEED] |= SCB_SPEED; + StatusChangeFlagTable[SC_ITEMSCRIPT] |= SCB_ALL; + // Cash Items + StatusChangeFlagTable[SC_FOOD_STR_CASH] = SCB_STR; + StatusChangeFlagTable[SC_FOOD_AGI_CASH] = SCB_AGI; + StatusChangeFlagTable[SC_FOOD_VIT_CASH] = SCB_VIT; + StatusChangeFlagTable[SC_FOOD_DEX_CASH] = SCB_DEX; + StatusChangeFlagTable[SC_FOOD_INT_CASH] = SCB_INT; + StatusChangeFlagTable[SC_FOOD_LUK_CASH] = SCB_LUK; + // Mercenary Bonus Effects + StatusChangeFlagTable[SC_MERC_FLEEUP] |= SCB_FLEE; + StatusChangeFlagTable[SC_MERC_ATKUP] |= SCB_WATK; + StatusChangeFlagTable[SC_MERC_HPUP] |= SCB_MAXHP; + StatusChangeFlagTable[SC_MERC_SPUP] |= SCB_MAXSP; + StatusChangeFlagTable[SC_MERC_HITUP] |= SCB_HIT; + // Guillotine Cross Poison Effects + StatusChangeFlagTable[SC_PARALYSE] |= SCB_ASPD|SCB_FLEE|SCB_SPEED; + StatusChangeFlagTable[SC_DEATHHURT] |= SCB_REGEN; + StatusChangeFlagTable[SC_VENOMBLEED] |= SCB_MAXHP; + StatusChangeFlagTable[SC_OBLIVIONCURSE] |= SCB_REGEN; + + StatusChangeFlagTable[SC_SAVAGE_STEAK] |= SCB_STR; + StatusChangeFlagTable[SC_COCKTAIL_WARG_BLOOD] |= SCB_INT; + StatusChangeFlagTable[SC_MINOR_BBQ] |= SCB_VIT; + StatusChangeFlagTable[SC_SIROMA_ICE_TEA] |= SCB_DEX; + StatusChangeFlagTable[SC_DROCERA_HERB_STEAMED] |= SCB_AGI; + StatusChangeFlagTable[SC_PUTTI_TAILS_NOODLES] |= SCB_LUK; + StatusChangeFlagTable[SC_BOOST500] |= SCB_ASPD; + StatusChangeFlagTable[SC_FULL_SWING_K] |= SCB_BATK; + StatusChangeFlagTable[SC_MANA_PLUS] |= SCB_MATK; + StatusChangeFlagTable[SC_MUSTLE_M] |= SCB_MAXHP; + StatusChangeFlagTable[SC_LIFE_FORCE_F] |= SCB_MAXSP; + StatusChangeFlagTable[SC_EXTRACT_WHITE_POTION_Z] |= SCB_REGEN; + StatusChangeFlagTable[SC_VITATA_500] |= SCB_REGEN; + StatusChangeFlagTable[SC_EXTRACT_SALAMINE_JUICE] |= SCB_ASPD; + +#ifdef RENEWAL_EDP + // renewal EDP increases your weapon atk + StatusChangeFlagTable[SC_EDP] |= SCB_WATK; +#endif + + if( !battle_config.display_hallucination ) //Disable Hallucination. + StatusIconChangeTable[SC_HALLUCINATION] = SI_BLANK; + + /* StatusChangeState (SCS_) NOMOVE */ + StatusChangeStateTable[SC_ANKLE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BLADESTOP_WAIT] |= SCS_NOMOVE; + StatusChangeStateTable[SC_GOSPEL] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_BASILICA] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_STOP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CLOSECONFINE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CLOSECONFINE2] |= SCS_NOMOVE; + StatusChangeStateTable[SC_MADNESSCANCEL] |= SCS_NOMOVE; + StatusChangeStateTable[SC_GRAVITATION] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOMOVE; + StatusChangeStateTable[SC_ELECTRICSHOCKER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BITE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_THORNSTRAP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_MAGNETICFIELD] |= SCS_NOMOVE; + StatusChangeStateTable[SC__MANHOLE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CURSEDCIRCLE_ATKER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_NETHERWORLD] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CAMOUFLAGE] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_MEIKYOUSISUI] |= SCS_NOMOVE; + StatusChangeStateTable[SC_KAGEHUMI] |= SCS_NOMOVE; + StatusChangeStateTable[SC_KYOUGAKU] |= SCS_NOMOVE; + + /* StatusChangeState (SCS_) NOPICKUPITEMS */ + StatusChangeStateTable[SC_HIDING] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_CLOAKING] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_CLOAKINGEXCEED] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_NOCHAT] |= SCS_NOPICKITEM|SCS_NOPICKITEMCOND; + + /* StatusChangeState (SCS_) NODROPITEMS */ + StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NODROPITEM; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NODROPITEM; + StatusChangeStateTable[SC_NOCHAT] |= SCS_NODROPITEM|SCS_NODROPITEMCOND; + + /* StatusChangeState (SCS_) NOCAST (skills) */ + StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; + StatusChangeStateTable[SC_STEELBODY] |= SCS_NOCAST; + StatusChangeStateTable[SC_BERSERK] |= SCS_NOCAST; + StatusChangeStateTable[SC__BLOODYLUST] |= SCS_NOCAST; + StatusChangeStateTable[SC_OBLIVIONCURSE] |= SCS_NOCAST; + StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOCAST; + StatusChangeStateTable[SC__INVISIBILITY] |= SCS_NOCAST; + StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOCAST|SCS_NOCASTCOND; + StatusChangeStateTable[SC__IGNORANCE] |= SCS_NOCAST; + StatusChangeStateTable[SC_DEEPSLEEP] |= SCS_NOCAST; + StatusChangeStateTable[SC_SATURDAYNIGHTFEVER] |= SCS_NOCAST; + StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOCAST; + StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; + + //Homon S + StatusChangeStateTable[SC_PARALYSIS] |= SCS_NOMOVE; + +} + +static void initDummyData(void) +{ + memset(&dummy_status, 0, sizeof(dummy_status)); + dummy_status.hp = + dummy_status.max_hp = + dummy_status.max_sp = + dummy_status.str = + dummy_status.agi = + dummy_status.vit = + dummy_status.int_ = + dummy_status.dex = + dummy_status.luk = + dummy_status.hit = 1; + dummy_status.speed = 2000; + dummy_status.adelay = 4000; + dummy_status.amotion = 2000; + dummy_status.dmotion = 2000; + dummy_status.ele_lv = 1; //Min elemental level. + dummy_status.mode = MD_CANMOVE; +} + + +//For copying a status_data structure from b to a, without overwriting current Hp and Sp +static inline void status_cpy(struct status_data* a, const struct status_data* b) +{ + memcpy((void*)&a->max_hp, (const void*)&b->max_hp, sizeof(struct status_data)-(sizeof(a->hp)+sizeof(a->sp))); +} + +//Sets HP to given value. Flag is the flag passed to status_heal in case +//final value is higher than current (use 2 to make a healing effect display +//on players) It will always succeed (overrides Berserk block), but it can't kill. +int status_set_hp(struct block_list *bl, unsigned int hp, int flag) +{ + struct status_data *status; + if (hp < 1) return 0; + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; + + if (hp > status->max_hp) hp = status->max_hp; + if (hp == status->hp) return 0; + if (hp > status->hp) + return status_heal(bl, hp - status->hp, 0, 1|flag); + return status_zap(bl, status->hp - hp, 0); +} + +//Sets SP to given value. Flag is the flag passed to status_heal in case +//final value is higher than current (use 2 to make a healing effect display +//on players) +int status_set_sp(struct block_list *bl, unsigned int sp, int flag) +{ + struct status_data *status; + + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; + + if (sp > status->max_sp) sp = status->max_sp; + if (sp == status->sp) return 0; + if (sp > status->sp) + return status_heal(bl, 0, sp - status->sp, 1|flag); + return status_zap(bl, 0, status->sp - sp); +} + +int status_charge(struct block_list* bl, int hp, int sp) +{ + if(!(bl->type&BL_CONSUME)) + return hp+sp; //Assume all was charged so there are no 'not enough' fails. + return status_damage(NULL, bl, hp, sp, 0, 3); +} + +//Inflicts damage on the target with the according walkdelay. +//If flag&1, damage is passive and does not triggers cancelling status changes. +//If flag&2, fail if target does not has enough to substract. +//If flag&4, if killed, mob must not give exp/loot. +//flag will be set to &8 when damaging sp of a dead character +int status_damage(struct block_list *src,struct block_list *target,int hp, int sp, int walkdelay, int flag) +{ + struct status_data *status; + struct status_change *sc; + + if(sp && !(target->type&BL_CONSUME)) + sp = 0; //Not a valid SP target. + + if (hp < 0) { //Assume absorbed damage. + status_heal(target, -hp, 0, 1); + hp = 0; + } + + if (sp < 0) { + status_heal(target, 0, -sp, 1); + sp = 0; + } + + if (target->type == BL_SKILL) + return skill_unit_ondamaged((struct skill_unit *)target, src, hp, gettick()); + + status = status_get_status_data(target); + if( status == &dummy_status ) + return 0; + + if ((unsigned int)hp >= status->hp) { + if (flag&2) return 0; + hp = status->hp; + } + + if ((unsigned int)sp > status->sp) { + if (flag&2) return 0; + sp = status->sp; + } + + if (!hp && !sp) + return 0; + + if( !status->hp ) + flag |= 8; + +// Let through. battle.c/skill.c have the whole logic of when it's possible or +// not to hurt someone (and this check breaks pet catching) [Skotlex] +// if (!target->prev && !(flag&2)) +// return 0; //Cannot damage a bl not on a map, except when "charging" hp/sp + + sc = status_get_sc(target); + if( hp && battle_config.invincible_nodamage && src && sc && sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + hp = 1; + + if( hp && !(flag&1) ) { + if( sc ) { + struct status_change_entry *sce; + if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + status_change_end(target, SC_STONE, INVALID_TIMER); + status_change_end(target, SC_FREEZE, INVALID_TIMER); + status_change_end(target, SC_SLEEP, INVALID_TIMER); + status_change_end(target, SC_WINKCHARM, INVALID_TIMER); + status_change_end(target, SC_CONFUSION, INVALID_TIMER); + status_change_end(target, SC_TRICKDEAD, INVALID_TIMER); + status_change_end(target, SC_HIDING, INVALID_TIMER); + status_change_end(target, SC_CLOAKING, INVALID_TIMER); + status_change_end(target, SC_CHASEWALK, INVALID_TIMER); + status_change_end(target, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(target, SC__INVISIBILITY, INVALID_TIMER); + status_change_end(target, SC_DEEPSLEEP, INVALID_TIMER); + if ((sce=sc->data[SC_ENDURE]) && !sce->val4) { + //Endure count is only reduced by non-players on non-gvg maps. + //val4 signals infinite endure. [Skotlex] + if (src && src->type != BL_PC && !map_flag_gvg(target->m) && !map[target->m].flag.battleground && --(sce->val2) < 0) + status_change_end(target, SC_ENDURE, INVALID_TIMER); + } + if ((sce=sc->data[SC_GRAVITATION]) && sce->val3 == BCT_SELF) { + struct skill_unit_group* sg = skill_id2group(sce->val4); + if (sg) { + skill_delunitgroup(sg); + sce->val4 = 0; + status_change_end(target, SC_GRAVITATION, INVALID_TIMER); + } + } + if(sc->data[SC_DANCING] && (unsigned int)hp > status->max_hp>>2) + status_change_end(target, SC_DANCING, INVALID_TIMER); + if(sc->data[SC_CLOAKINGEXCEED] && --(sc->data[SC_CLOAKINGEXCEED]->val2) <= 0) + status_change_end(target, SC_CLOAKINGEXCEED, INVALID_TIMER); + if(sc->data[SC_KAGEMUSYA] && --(sc->data[SC_KAGEMUSYA]->val3) <= 0) + status_change_end(target, SC_KAGEMUSYA, INVALID_TIMER); + } + unit_skillcastcancel(target, 2); + } + + status->hp-= hp; + status->sp-= sp; + + if (sc && hp && status->hp) { + if (sc->data[SC_AUTOBERSERK] && + (!sc->data[SC_PROVOKE] || !sc->data[SC_PROVOKE]->val2) && + status->hp < status->max_hp>>2) + sc_start4(target,SC_PROVOKE,100,10,1,0,0,0); + if (sc->data[SC_BERSERK] && status->hp <= 100) + status_change_end(target, SC_BERSERK, INVALID_TIMER); + if( sc->data[SC_RAISINGDRAGON] && status->hp <= 1000 ) + status_change_end(target, SC_RAISINGDRAGON, INVALID_TIMER); + if (sc->data[SC_SATURDAYNIGHTFEVER] && status->hp <= 100) + status_change_end(target, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + if (sc->data[SC__BLOODYLUST] && status->hp <= 100) + status_change_end(target, SC__BLOODYLUST, INVALID_TIMER); + } + + switch (target->type) { + case BL_PC: pc_damage((TBL_PC*)target,src,hp,sp); break; + case BL_MOB: mob_damage((TBL_MOB*)target, src, hp); break; + case BL_HOM: merc_damage((TBL_HOM*)target); break; + case BL_MER: mercenary_heal((TBL_MER*)target,hp,sp); break; + case BL_ELEM: elemental_heal((TBL_ELEM*)target,hp,sp); break; + } + + if( src && target->type == BL_PC && ((TBL_PC*)target)->disguise ) {// stop walking when attacked in disguise to prevent walk-delay bug + unit_stop_walking( target, 1 ); + } + + if( status->hp || (flag&8) ) + { //Still lives or has been dead before this damage. + if (walkdelay) + unit_set_walkdelay(target, gettick(), walkdelay, 0); + return hp+sp; + } + + status->hp = 1; //To let the dead function cast skills and all that. + //NOTE: These dead functions should return: [Skotlex] + //0: Death cancelled, auto-revived. + //Non-zero: Standard death. Clear status, cancel move/attack, etc + //&2: Also remove object from map. + //&4: Also delete object from memory. + switch (target->type) { + case BL_PC: flag = pc_dead((TBL_PC*)target,src); break; + case BL_MOB: flag = mob_dead((TBL_MOB*)target, src, flag&4?3:0); break; + case BL_HOM: flag = merc_hom_dead((TBL_HOM*)target); break; + case BL_MER: flag = mercenary_dead((TBL_MER*)target); break; + case BL_ELEM: flag = elemental_dead((TBL_ELEM*)target); break; + default: //Unhandled case, do nothing to object. + flag = 0; + break; + } + + if(!flag) //Death cancelled. + return hp+sp; + + //Normal death + status->hp = 0; + if (battle_config.clear_unit_ondeath && + battle_config.clear_unit_ondeath&target->type) + skill_clear_unitgroup(target); + + if(target->type&BL_REGEN) + { //Reset regen ticks. + struct regen_data *regen = status_get_regen_data(target); + if (regen) { + memset(®en->tick, 0, sizeof(regen->tick)); + if (regen->sregen) + memset(®en->sregen->tick, 0, sizeof(regen->sregen->tick)); + if (regen->ssregen) + memset(®en->ssregen->tick, 0, sizeof(regen->ssregen->tick)); + } + } + + if( sc && sc->data[SC_KAIZEL] && !map_flag_gvg(target->m) ) + { //flag&8 = disable Kaizel + int time = skill_get_time2(SL_KAIZEL,sc->data[SC_KAIZEL]->val1); + //Look for Osiris Card's bonus effect on the character and revive 100% or revive normally + if ( target->type == BL_PC && BL_CAST(BL_PC,target)->special_state.restart_full_recover ) + status_revive(target, 100, 100); + else + status_revive(target, sc->data[SC_KAIZEL]->val2, 0); + status_change_clear(target,0); + clif_skill_nodamage(target,target,ALL_RESURRECTION,1,1); + sc_start(target,status_skill2sc(PR_KYRIE),100,10,time); + + if( target->type == BL_MOB ) + ((TBL_MOB*)target)->state.rebirth = 1; + + return hp+sp; + } + if(target->type == BL_PC){ + TBL_PC *sd = BL_CAST(BL_PC,target); + TBL_HOM *hd = sd->hd; + if(hd && hd->sc.data[SC_LIGHT_OF_REGENE]){ + clif_skillcasting(&hd->bl, hd->bl.id, target->id, 0,0, MH_LIGHT_OF_REGENE, skill_get_ele(MH_LIGHT_OF_REGENE, 1), 10); //just to display usage + clif_skill_nodamage(&sd->bl, target, ALL_RESURRECTION, 1, status_revive(&sd->bl,10*hd->sc.data[SC_LIGHT_OF_REGENE]->val1,0)); + status_change_end(&sd->hd->bl,SC_LIGHT_OF_REGENE,INVALID_TIMER); + return hp + sp; + } + } + if (target->type == BL_MOB && sc && sc->data[SC_REBIRTH] && !((TBL_MOB*) target)->state.rebirth) {// Ensure the monster has not already rebirthed before doing so. + status_revive(target, sc->data[SC_REBIRTH]->val2, 0); + status_change_clear(target,0); + ((TBL_MOB*)target)->state.rebirth = 1; + + return hp+sp; + } + + status_change_clear(target,0); + + if(flag&4) //Delete from memory. (also invokes map removal code) + unit_free(target,CLR_DEAD); + else + if(flag&2) //remove from map + unit_remove_map(target,CLR_DEAD); + else + { //Some death states that would normally be handled by unit_remove_map + unit_stop_attack(target); + unit_stop_walking(target,1); + unit_skillcastcancel(target,0); + clif_clearunit_area(target,CLR_DEAD); + skill_unit_move(target,gettick(),4); + skill_cleartimerskill(target); + } + + return hp+sp; +} + +//Heals a character. If flag&1, this is forced healing (otherwise stuff like Berserk can block it) +//If flag&2, when the player is healed, show the HP/SP heal effect. +int status_heal(struct block_list *bl,int hp,int sp, int flag) +{ + struct status_data *status; + struct status_change *sc; + + status = status_get_status_data(bl); + + if (status == &dummy_status || !status->hp) + return 0; + + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + + if (hp < 0) { + if (hp == INT_MIN) hp++; //-INT_MIN == INT_MIN in some architectures! + status_damage(NULL, bl, -hp, 0, 0, 1); + hp = 0; + } + + if(hp) { + if( sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) ) { + if( flag&1 ) + flag &= ~2; + else + hp = 0; + } + + if((unsigned int)hp > status->max_hp - status->hp) + hp = status->max_hp - status->hp; + } + + if(sp < 0) { + if (sp==INT_MIN) sp++; + status_damage(NULL, bl, 0, -sp, 0, 1); + sp = 0; + } + + if(sp) { + if((unsigned int)sp > status->max_sp - status->sp) + sp = status->max_sp - status->sp; + } + + if(!sp && !hp) return 0; + + status->hp+= hp; + status->sp+= sp; + + if(hp && sc && + sc->data[SC_AUTOBERSERK] && + sc->data[SC_PROVOKE] && + sc->data[SC_PROVOKE]->val2==1 && + status->hp>=status->max_hp>>2 + ) //End auto berserk. + status_change_end(bl, SC_PROVOKE, INVALID_TIMER); + + // send hp update to client + switch(bl->type) { + case BL_PC: pc_heal((TBL_PC*)bl,hp,sp,flag&2?1:0); break; + case BL_MOB: mob_heal((TBL_MOB*)bl,hp); break; + case BL_HOM: merc_hom_heal((TBL_HOM*)bl); break; + case BL_MER: mercenary_heal((TBL_MER*)bl,hp,sp); break; + case BL_ELEM: elemental_heal((TBL_ELEM*)bl,hp,sp); break; + } + + return hp+sp; +} + +//Does percentual non-flinching damage/heal. If mob is killed this way, +//no exp/drops will be awarded if there is no src (or src is target) +//If rates are > 0, percent is of current HP/SP +//If rates are < 0, percent is of max HP/SP +//If !flag, this is heal, otherwise it is damage. +//Furthermore, if flag==2, then the target must not die from the substraction. +int status_percent_change(struct block_list *src,struct block_list *target,signed char hp_rate, signed char sp_rate, int flag) +{ + struct status_data *status; + unsigned int hp =0, sp = 0; + + status = status_get_status_data(target); + + + //It's safe now [MarkZD] + if (hp_rate > 99) + hp = status->hp; + else if (hp_rate > 0) + hp = status->hp>10000? + hp_rate*(status->hp/100): + ((int64)hp_rate*status->hp)/100; + else if (hp_rate < -99) + hp = status->max_hp; + else if (hp_rate < 0) + hp = status->max_hp>10000? + (-hp_rate)*(status->max_hp/100): + ((int64)-hp_rate*status->max_hp)/100; + if (hp_rate && !hp) + hp = 1; + + if (flag == 2 && hp >= status->hp) + hp = status->hp-1; //Must not kill target. + + if (sp_rate > 99) + sp = status->sp; + else if (sp_rate > 0) + sp = ((int64)sp_rate*status->sp)/100; + else if (sp_rate < -99) + sp = status->max_sp; + else if (sp_rate < 0) + sp = ((int64)-sp_rate)*status->max_sp/100; + if (sp_rate && !sp) + sp = 1; + + //Ugly check in case damage dealt is too much for the received args of + //status_heal / status_damage. [Skotlex] + if (hp > INT_MAX) { + hp -= INT_MAX; + if (flag) + status_damage(src, target, INT_MAX, 0, 0, (!src||src==target?5:1)); + else + status_heal(target, INT_MAX, 0, 0); + } + if (sp > INT_MAX) { + sp -= INT_MAX; + if (flag) + status_damage(src, target, 0, INT_MAX, 0, (!src||src==target?5:1)); + else + status_heal(target, 0, INT_MAX, 0); + } + if (flag) + return status_damage(src, target, hp, sp, 0, (!src||src==target?5:1)); + return status_heal(target, hp, sp, 0); +} + +int status_revive(struct block_list *bl, unsigned char per_hp, unsigned char per_sp) +{ + struct status_data *status; + unsigned int hp, sp; + if (!status_isdead(bl)) return 0; + + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; //Invalid target. + + hp = (int64)status->max_hp * per_hp/100; + sp = (int64)status->max_sp * per_sp/100; + + if(hp > status->max_hp - status->hp) + hp = status->max_hp - status->hp; + else if (per_hp && !hp) + hp = 1; + + if(sp > status->max_sp - status->sp) + sp = status->max_sp - status->sp; + else if (per_sp && !sp) + sp = 1; + + status->hp += hp; + status->sp += sp; + + if (bl->prev) //Animation only if character is already on a map. + clif_resurrection(bl, 1); + switch (bl->type) { + case BL_PC: pc_revive((TBL_PC*)bl, hp, sp); break; + case BL_MOB: mob_revive((TBL_MOB*)bl, hp); break; + case BL_HOM: merc_hom_revive((TBL_HOM*)bl, hp, sp); break; + } + return 1; +} + +/*========================================== + * Checks whether the src can use the skill on the target, + * taking into account status/option of both source/target. [Skotlex] + * flag: + * 0 - Trying to use skill on target. + * 1 - Cast bar is done. + * 2 - Skill already pulled off, check is due to ground-based skills or splash-damage ones. + * src MAY be null to indicate we shouldn't check it, this is a ground-based skill attack. + * target MAY Be null, in which case the checks are only to see + * whether the source can cast or not the skill on the ground. + *------------------------------------------*/ +int status_check_skilluse(struct block_list *src, struct block_list *target, uint16 skill_id, int flag) +{ + struct status_data *status; + struct status_change *sc=NULL, *tsc; + int hide_flag; + + status = src?status_get_status_data(src):&dummy_status; + + if (src && src->type != BL_PC && status_isdead(src)) + return 0; + + if (!skill_id) { //Normal attack checks. + if (!(status->mode&MD_CANATTACK)) + return 0; //This mode is only needed for melee attacking. + //Dead state is not checked for skills as some skills can be used + //on dead characters, said checks are left to skill.c [Skotlex] + if (target && status_isdead(target)) + return 0; + if( src && (sc = status_get_sc(src)) && sc->data[SC_CRYSTALIZE] && src->type != BL_MOB) + return 0; + } + + switch( skill_id ) { + case PA_PRESSURE: + if( flag && target ) { + //Gloria Avoids pretty much everything.... + tsc = status_get_sc(target); + if(tsc && tsc->option&OPTION_HIDE) + return 0; + } + break; + case GN_WALLOFTHORN: + if( target && status_isdead(target) ) + return 0; + break; + case AL_TELEPORT: + //Should fail when used on top of Land Protector [Skotlex] + if (src && map_getcell(src->m, src->x, src->y, CELL_CHKLANDPROTECTOR) + && !(status->mode&MD_BOSS) + && (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id)) + return 0; + break; + default: + break; + } + + if ( src ) sc = status_get_sc(src); + + if( sc && sc->count ) { + + if (skill_id != RK_REFRESH && sc->opt1 >0 && (sc->opt1 != OPT1_CRYSTALIZE && src->type != BL_MOB) && sc->opt1 != OPT1_BURNING && skill_id != SR_GENTLETOUCH_CURE) { //Stuned/Frozen/etc + if (flag != 1) //Can't cast, casted stuff can't damage. + return 0; + if (!(skill_get_inf(skill_id)&INF_GROUND_SKILL)) + return 0; //Targetted spells can't come off. + } + + if ( + (sc->data[SC_TRICKDEAD] && skill_id != NV_TRICKDEAD) + || (sc->data[SC_AUTOCOUNTER] && !flag) + || (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF && skill_id != PA_GOSPEL) + || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF && flag != 2) + ) + return 0; + + if (sc->data[SC_WINKCHARM] && target && !flag) { //Prevents skill usage + if( unit_bl2ud(src) && (unit_bl2ud(src))->walktimer == INVALID_TIMER ) + unit_walktobl(src, map_id2bl(sc->data[SC_WINKCHARM]->val2), 3, 1); + clif_emotion(src, E_LV); + return 0; + } + + if (sc->data[SC_BLADESTOP]) { + switch (sc->data[SC_BLADESTOP]->val1) + { + case 5: if (skill_id == MO_EXTREMITYFIST) break; + case 4: if (skill_id == MO_CHAINCOMBO) break; + case 3: if (skill_id == MO_INVESTIGATE) break; + case 2: if (skill_id == MO_FINGEROFFENSIVE) break; + default: return 0; + } + } + + if (sc->data[SC_DANCING] && flag!=2) { + if( src->type == BL_PC && skill_id >= WA_SWING_DANCE && skill_id <= WM_UNLIMITED_HUMMING_VOICE ) + { // Lvl 5 Lesson or higher allow you use 3rd job skills while dancing.v + if( pc_checkskill((TBL_PC*)src,WM_LESSON) < 5 ) + return 0; + } else if(sc->data[SC_LONGING]) { //Allow everything except dancing/re-dancing. [Skotlex] + if (skill_id == BD_ENCORE || + skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_ENSEMBLE_SKILL) + ) + return 0; + } else { + switch (skill_id) { + case BD_ADAPTATION: + case CG_LONGINGFREEDOM: + case BA_MUSICALSTRIKE: + case DC_THROWARROW: + break; + default: + return 0; + } + } + if ((sc->data[SC_DANCING]->val1&0xFFFF) == CG_HERMODE && skill_id == BD_ADAPTATION) + return 0; //Can't amp out of Wand of Hermode :/ [Skotlex] + } + + if (skill_id && //Do not block item-casted skills. + (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id) + ) { //Skills blocked through status changes... + if (!flag && ( //Blocked only from using the skill (stuff like autospell may still go through + sc->cant.cast || + (sc->data[SC_MARIONETTE] && skill_id != CG_MARIONETTE) || //Only skill you can use is marionette again to cancel it + (sc->data[SC_MARIONETTE2] && skill_id == CG_MARIONETTE) || //Cannot use marionette if you are being buffed by another + (sc->data[SC_STASIS] && skill_block_check(src, SC_STASIS, skill_id)) || + (sc->data[SC_KAGEHUMI] && skill_block_check(src, SC_KAGEHUMI, skill_id)) + )) + return 0; + + //Skill blocking. + if ( + (sc->data[SC_VOLCANO] && skill_id == WZ_ICEWALL) || + (sc->data[SC_ROKISWEIL] && skill_id != BD_ADAPTATION) || + (sc->data[SC_HERMODE] && skill_get_inf(skill_id) & INF_SUPPORT_SKILL) || + (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOSKILL) + ) + return 0; + + if( sc->data[SC__MANHOLE] || ((tsc = status_get_sc(target)) && tsc->data[SC__MANHOLE]) ) { + switch(skill_id) {//##TODO## make this a flag in skill_db? + // Skills that can be used even under Man Hole effects. + case SC_SHADOWFORM: + case SC_STRIPACCESSARY: + break; + default: + return 0; + } + } + + } + } + + if (sc && sc->option) + { + if (sc->option&OPTION_HIDE) + switch (skill_id) { //Usable skills while hiding. + case TF_HIDING: + case AS_GRIMTOOTH: + case RG_BACKSTAP: + case RG_RAID: + case NJ_SHADOWJUMP: + case NJ_KIRIKAGE: + case KO_YAMIKUMO: + break; + default: + //Non players can use all skills while hidden. + if (!skill_id || src->type == BL_PC) + return 0; + } + if (sc->option&OPTION_CHASEWALK && skill_id != ST_CHASEWALK) + return 0; + if(sc->option&OPTION_MOUNTING) + return 0;//New mounts can't attack nor use skills in the client; this check makes it cheat-safe [Ind] + } + + if (target == NULL || target == src) //No further checking needed. + return 1; + + tsc = status_get_sc(target); + + if(tsc && tsc->count) { + /* attacks in invincible are capped to 1 damage and handled in batte.c; allow spell break and eske for sealed shrine GDB when in INVINCIBLE state. */ + if( tsc->data[SC_INVINCIBLE] && !tsc->data[SC_INVINCIBLEOFF] && skill_id && !(skill_id&(SA_SPELLBREAKER|SL_SKE)) ) + return 0; + if(!skill_id && tsc->data[SC_TRICKDEAD]) + return 0; + if((skill_id == WZ_STORMGUST || skill_id == WZ_FROSTNOVA || skill_id == NJ_HYOUSYOURAKU) + && tsc->data[SC_FREEZE]) + return 0; + if(skill_id == PR_LEXAETERNA && (tsc->data[SC_FREEZE] || (tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE))) + return 0; + } + + //If targetting, cloak+hide protect you, otherwise only hiding does. + hide_flag = flag?OPTION_HIDE:(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK); + + //You cannot hide from ground skills. + if( skill_get_ele(skill_id,1) == ELE_EARTH ) //TODO: Need Skill Lv here :/ + hide_flag &= ~OPTION_HIDE; + + switch( target->type ) { + case BL_PC: { + struct map_session_data *sd = (TBL_PC*) target; + bool is_boss = (status->mode&MD_BOSS); + bool is_detect = ((status->mode&MD_DETECTOR)?true:false);//god-knows-why gcc doesn't shut up until this happens + if (pc_isinvisible(sd)) + return 0; + if (tsc->option&hide_flag && !is_boss && + ((sd->special_state.perfect_hiding || !is_detect) || + (tsc->data[SC_CLOAKINGEXCEED] && is_detect))) + return 0; + if( tsc->data[SC_CAMOUFLAGE] && !(is_boss || is_detect) && !skill_id ) + return 0; + if( tsc->data[SC_STEALTHFIELD] && !is_boss ) + return 0; + } + break; + case BL_ITEM: //Allow targetting of items to pick'em up (or in the case of mobs, to loot them). + //TODO: Would be nice if this could be used to judge whether the player can or not pick up the item it targets. [Skotlex] + if (status->mode&MD_LOOTER) + return 1; + return 0; + case BL_HOM: + case BL_MER: + case BL_ELEM: + if( target->type == BL_HOM && skill_id && battle_config.hom_setting&0x1 && skill_get_inf(skill_id)&INF_SUPPORT_SKILL && battle_get_master(target) != src ) + return 0; // Can't use support skills on Homunculus (only Master/Self) + if( target->type == BL_MER && (skill_id == PR_ASPERSIO || (skill_id >= SA_FLAMELAUNCHER && skill_id <= SA_SEISMICWEAPON)) && battle_get_master(target) != src ) + return 0; // Can't use Weapon endow skills on Mercenary (only Master) + if( skill_id == AM_POTIONPITCHER && ( target->type == BL_MER || target->type == BL_ELEM) ) + return 0; // Can't use Potion Pitcher on Mercenaries + default: + //Check for chase-walk/hiding/cloaking opponents. + if( tsc ) { + if( tsc->option&hide_flag && !(status->mode&(MD_BOSS|MD_DETECTOR))) + return 0; + if( tsc->data[SC_STEALTHFIELD] && !(status->mode&MD_BOSS) ) + return 0; + } + } + return 1; +} + +//Checks whether the source can see and chase target. +int status_check_visibility(struct block_list *src, struct block_list *target) +{ + int view_range; + struct status_data* status = status_get_status_data(src); + struct status_change* tsc = status_get_sc(target); + switch (src->type) { + case BL_MOB: + view_range = ((TBL_MOB*)src)->min_chase; + break; + case BL_PET: + view_range = ((TBL_PET*)src)->db->range2; + break; + default: + view_range = AREA_SIZE; + } + + if (src->m != target->m || !check_distance_bl(src, target, view_range)) + return 0; + + if( tsc && tsc->data[SC_STEALTHFIELD] ) + return 0; + + switch (target->type) + { //Check for chase-walk/hiding/cloaking opponents. + case BL_PC: + if ( tsc->data[SC_CLOAKINGEXCEED] && !(status->mode&MD_BOSS) ) + return 0; + if( (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&MD_BOSS) && + ( ((TBL_PC*)target)->special_state.perfect_hiding || !(status->mode&MD_DETECTOR) ) ) + return 0; + break; + default: + if( tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&(MD_BOSS|MD_DETECTOR)) ) + return 0; + + } + + return 1; +} + +// Basic ASPD value +int status_base_amotion_pc(struct map_session_data* sd, struct status_data* status) +{ + int amotion; +#ifdef RENEWAL_ASPD + short mod = -1; + + switch( sd->weapontype2 ){ // adjustment for dual weilding + case W_DAGGER: mod = 0; break; // 0, 1, 1 + case W_1HSWORD: + case W_1HAXE: mod = 1; + if( (sd->class_&MAPID_THIRDMASK) == MAPID_GUILLOTINE_CROSS ) // 0, 2, 3 + mod = sd->weapontype2 / W_1HSWORD + W_1HSWORD / sd->weapontype2 ; + } + + amotion = ( sd->status.weapon < MAX_WEAPON_TYPE && mod < 0 ) + ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon + : ((aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] // dual-wield + + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2]) * 6 / 10 + 10 * mod + - aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] + + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1]); + + if ( sd->status.shield ) + amotion += ( 2000 - aspd_base[pc_class2idx(sd->status.class_)][W_FIST] ) + + ( aspd_base[pc_class2idx(sd->status.class_)][MAX_WEAPON_TYPE] - 2000 ); + +#else + // base weapon delay + amotion = (sd->status.weapon < MAX_WEAPON_TYPE) + ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon + : (aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1] + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2])*7/10; // dual-wield + + // percentual delay reduction from stats + amotion -= amotion * (4*status->agi + status->dex)/1000; +#endif + // raw delay adjustment from bAspd bonus + amotion += sd->bonus.aspd_add; + + return amotion; +} + +static unsigned short status_base_atk(const struct block_list *bl, const struct status_data *status) +{ + int flag = 0, str, dex, +#ifdef RENEWAL + rstr, +#endif + dstr; + + + if(!(bl->type&battle_config.enable_baseatk)) + return 0; + + if (bl->type == BL_PC) + switch(((TBL_PC*)bl)->status.weapon){ + case W_BOW: + case W_MUSICAL: + case W_WHIP: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + flag = 1; + } + if (flag) { +#ifdef RENEWAL + rstr = +#endif + str = status->dex; + dex = status->str; + } else { +#ifdef RENEWAL + rstr = +#endif + str = status->str; + dex = status->dex; + } + //Normally only players have base-atk, but homunc have a different batk + // equation, hinting that perhaps non-players should use this for batk. + // [Skotlex] + dstr = str/10; + str += dstr*dstr; + if (bl->type == BL_PC) +#ifdef RENEWAL + str = (rstr*10 + dex*10/5 + status->luk*10/3 + ((TBL_PC*)bl)->status.base_level*10/4)/10; +#else + str+= dex/5 + status->luk/5; +#endif + return cap_value(str, 0, USHRT_MAX); +} + +#ifndef RENEWAL +static inline unsigned short status_base_matk_min(const struct status_data* status){ return status->int_+(status->int_/7)*(status->int_/7); } +static inline unsigned short status_base_matk_max(const struct status_data* status){ return status->int_+(status->int_/5)*(status->int_/5); } +#else +unsigned short status_base_matk(const struct status_data* status, int level){ return status->int_+(status->int_/2)+(status->dex/5)+(status->luk/3)+(level/4); } +#endif + +//Fills in the misc data that can be calculated from the other status info (except for level) +void status_calc_misc(struct block_list *bl, struct status_data *status, int level) +{ + //Non players get the value set, players need to stack with previous bonuses. + if( bl->type != BL_PC ) + status->batk = + status->hit = status->flee = + status->def2 = status->mdef2 = + status->cri = status->flee2 = 0; + +#ifdef RENEWAL // renewal formulas + status->matk_min = status->matk_max = status_base_matk(status, level); + status->hit += level + status->dex + status->luk/3 + 175; //base level + ( every 1 dex = +1 hit ) + (every 3 luk = +1 hit) + 175 + status->flee += level + status->agi + status->luk/5 + 100; //base level + ( every 1 agi = +1 flee ) + (every 5 luk = +1 flee) + 100 + status->def2 += (int)(((float)level + status->vit)/2 + ((float)status->agi/5)); //base level + (every 2 vit = +1 def) + (every 5 agi = +1 def) + status->mdef2 += (int)(status->int_ + ((float)level/4) + ((float)status->dex/5) + ((float)status->vit/5)); //(every 4 base level = +1 mdef) + (every 1 int = +1 mdef) + (every 5 dex = +1 mdef) + (every 5 vit = +1 mdef) +#else + status->matk_min = status_base_matk_min(status); + status->matk_max = status_base_matk_max(status); + status->hit += level + status->dex; + status->flee += level + status->agi; + status->def2 += status->vit; + status->mdef2 += status->int_ + (status->vit>>1); +#endif + + if( bl->type&battle_config.enable_critical ) + status->cri += 10 + (status->luk*10/3); //(every 1 luk = +0.3 critical) + else + status->cri = 0; + + if (bl->type&battle_config.enable_perfect_flee) + status->flee2 += status->luk + 10; //(every 10 luk = +1 perfect flee) + else + status->flee2 = 0; + + if (status->batk) { + int temp = status->batk + status_base_atk(bl, status); + status->batk = cap_value(temp, 0, USHRT_MAX); + } else + status->batk = status_base_atk(bl, status); + if (status->cri) + switch (bl->type) { + case BL_MOB: + if(battle_config.mob_critical_rate != 100) + status->cri = status->cri*battle_config.mob_critical_rate/100; + if(!status->cri && battle_config.mob_critical_rate) + status->cri = 10; + break; + case BL_PC: + //Players don't have a critical adjustment setting as of yet. + break; + default: + if(battle_config.critical_rate != 100) + status->cri = status->cri*battle_config.critical_rate/100; + if (!status->cri && battle_config.critical_rate) + status->cri = 10; + } + if(bl->type&BL_REGEN) + status_calc_regen(bl, status, status_get_regen_data(bl)); +} + +//Skotlex: Calculates the initial status for the given mob +//first will only be false when the mob leveled up or got a GuardUp level. +int status_calc_mob_(struct mob_data* md, bool first) +{ + struct status_data *status; + struct block_list *mbl = NULL; + int flag=0; + + if(first) + { //Set basic level on respawn. + if (md->level > 0 && md->level <= MAX_LEVEL && md->level != md->db->lv) + ; + else + md->level = md->db->lv; + } + + //Check if we need custom base-status + if (battle_config.mobs_level_up && md->level > md->db->lv) + flag|=1; + + if (md->special_state.size) + flag|=2; + + if (md->guardian_data && md->guardian_data->guardup_lv) + flag|=4; + if (md->class_ == MOBID_EMPERIUM) + flag|=4; + + if (battle_config.slaves_inherit_speed && md->master_id) + flag|=8; + + if (md->master_id && md->special_state.ai>1) + flag|=16; + + if (!flag) + { //No special status required. + if (md->base_status) { + aFree(md->base_status); + md->base_status = NULL; + } + if(first) + memcpy(&md->status, &md->db->status, sizeof(struct status_data)); + return 0; + } + if (!md->base_status) + md->base_status = (struct status_data*)aCalloc(1, sizeof(struct status_data)); + + status = md->base_status; + memcpy(status, &md->db->status, sizeof(struct status_data)); + + if (flag&(8|16)) + mbl = map_id2bl(md->master_id); + + if (flag&8 && mbl) { + struct status_data *mstatus = status_get_base_status(mbl); + if (mstatus && + battle_config.slaves_inherit_speed&(mstatus->mode&MD_CANMOVE?1:2)) + status->speed = mstatus->speed; + if( status->speed < 2 ) /* minimum for the unit to function properly */ + status->speed = 2; + } + + if (flag&16 && mbl) + { //Max HP setting from Summon Flora/marine Sphere + struct unit_data *ud = unit_bl2ud(mbl); + //Remove special AI when this is used by regular mobs. + if (mbl->type == BL_MOB && !((TBL_MOB*)mbl)->special_state.ai) + md->special_state.ai = 0; + if (ud) + { // different levels of HP according to skill level + if (ud->skill_id == AM_SPHEREMINE) { + status->max_hp = 2000 + 400*ud->skill_lv; + } else if(ud->skill_id == KO_ZANZOU){ + status->max_hp = 3000 + 3000 * ud->skill_lv; + } else { //AM_CANNIBALIZE + status->max_hp = 1500 + 200*ud->skill_lv + 10*status_get_lv(mbl); + status->mode|= MD_CANATTACK|MD_AGGRESSIVE; + } + status->hp = status->max_hp; + } + } + + if (flag&1) + { // increase from mobs leveling up [Valaris] + int diff = md->level - md->db->lv; + status->str+= diff; + status->agi+= diff; + status->vit+= diff; + status->int_+= diff; + status->dex+= diff; + status->luk+= diff; + status->max_hp += diff*status->vit; + status->max_sp += diff*status->int_; + status->hp = status->max_hp; + status->sp = status->max_sp; + status->speed -= cap_value(diff, 0, status->speed - 10); + } + + + if (flag&2 && battle_config.mob_size_influence) + { // change for sized monsters [Valaris] + if (md->special_state.size==SZ_MEDIUM) { + status->max_hp>>=1; + status->max_sp>>=1; + if (!status->max_hp) status->max_hp = 1; + if (!status->max_sp) status->max_sp = 1; + status->hp=status->max_hp; + status->sp=status->max_sp; + status->str>>=1; + status->agi>>=1; + status->vit>>=1; + status->int_>>=1; + status->dex>>=1; + status->luk>>=1; + if (!status->str) status->str = 1; + if (!status->agi) status->agi = 1; + if (!status->vit) status->vit = 1; + if (!status->int_) status->int_ = 1; + if (!status->dex) status->dex = 1; + if (!status->luk) status->luk = 1; + } else if (md->special_state.size==SZ_BIG) { + status->max_hp<<=1; + status->max_sp<<=1; + status->hp=status->max_hp; + status->sp=status->max_sp; + status->str<<=1; + status->agi<<=1; + status->vit<<=1; + status->int_<<=1; + status->dex<<=1; + status->luk<<=1; + } + } + + status_calc_misc(&md->bl, status, md->level); + + if(flag&4) + { // Strengthen Guardians - custom value +10% / lv + struct guild_castle *gc; + gc=guild_mapname2gc(map[md->bl.m].name); + if (!gc) + ShowError("status_calc_mob: No castle set at map %s\n", map[md->bl.m].name); + else + if(gc->castle_id < 24 || md->class_ == MOBID_EMPERIUM) { +#ifdef RENEWAL + status->max_hp += 50 * gc->defense; + status->max_sp += 70 * gc->defense; +#else + status->max_hp += 1000 * gc->defense; + status->max_sp += 200 * gc->defense; +#endif + status->hp = status->max_hp; + status->sp = status->max_sp; + status->def += (gc->defense+2)/3; + status->mdef += (gc->defense+2)/3; + } + if(md->class_ != MOBID_EMPERIUM) { + status->batk += status->batk * 10*md->guardian_data->guardup_lv/100; + status->rhw.atk += status->rhw.atk * 10*md->guardian_data->guardup_lv/100; + status->rhw.atk2 += status->rhw.atk2 * 10*md->guardian_data->guardup_lv/100; + status->aspd_rate -= 100*md->guardian_data->guardup_lv; + } + } + + if( first ) //Initial battle status + memcpy(&md->status, status, sizeof(struct status_data)); + + return 1; +} + +//Skotlex: Calculates the stats of the given pet. +int status_calc_pet_(struct pet_data *pd, bool first) +{ + nullpo_ret(pd); + + if (first) { + memcpy(&pd->status, &pd->db->status, sizeof(struct status_data)); + pd->status.mode = MD_CANMOVE; // pets discard all modes, except walking + pd->status.speed = pd->petDB->speed; + + if(battle_config.pet_attack_support || battle_config.pet_damage_support) + {// attack support requires the pet to be able to attack + pd->status.mode|= MD_CANATTACK; + } + } + + if (battle_config.pet_lv_rate && pd->msd) + { + struct map_session_data *sd = pd->msd; + int lv; + + lv =sd->status.base_level*battle_config.pet_lv_rate/100; + if (lv < 0) + lv = 1; + if (lv != pd->pet.level || first) + { + struct status_data *bstat = &pd->db->status, *status = &pd->status; + pd->pet.level = lv; + if (!first) //Lv Up animation + clif_misceffect(&pd->bl, 0); + status->rhw.atk = (bstat->rhw.atk*lv)/pd->db->lv; + status->rhw.atk2 = (bstat->rhw.atk2*lv)/pd->db->lv; + status->str = (bstat->str*lv)/pd->db->lv; + status->agi = (bstat->agi*lv)/pd->db->lv; + status->vit = (bstat->vit*lv)/pd->db->lv; + status->int_ = (bstat->int_*lv)/pd->db->lv; + status->dex = (bstat->dex*lv)/pd->db->lv; + status->luk = (bstat->luk*lv)/pd->db->lv; + + status->rhw.atk = cap_value(status->rhw.atk, 1, battle_config.pet_max_atk1); + status->rhw.atk2 = cap_value(status->rhw.atk2, 2, battle_config.pet_max_atk2); + status->str = cap_value(status->str,1,battle_config.pet_max_stats); + status->agi = cap_value(status->agi,1,battle_config.pet_max_stats); + status->vit = cap_value(status->vit,1,battle_config.pet_max_stats); + status->int_= cap_value(status->int_,1,battle_config.pet_max_stats); + status->dex = cap_value(status->dex,1,battle_config.pet_max_stats); + status->luk = cap_value(status->luk,1,battle_config.pet_max_stats); + + status_calc_misc(&pd->bl, &pd->status, lv); + + if (!first) //Not done the first time because the pet is not visible yet + clif_send_petstatus(sd); + } + } else if (first) { + status_calc_misc(&pd->bl, &pd->status, pd->db->lv); + if (!battle_config.pet_lv_rate && pd->pet.level != pd->db->lv) + pd->pet.level = pd->db->lv; + } + + //Support rate modifier (1000 = 100%) + pd->rate_fix = 1000*(pd->pet.intimate - battle_config.pet_support_min_friendly)/(1000- battle_config.pet_support_min_friendly) +500; + if(battle_config.pet_support_rate != 100) + pd->rate_fix = pd->rate_fix*battle_config.pet_support_rate/100; + + return 1; +} + +/// Helper function for status_base_pc_maxhp(), used to pre-calculate the hp_sigma_val[] array +static void status_calc_sigma(void) +{ + int i,j; + + for(i = 0; i < CLASS_COUNT; i++) + { + unsigned int k = 0; + hp_sigma_val[i][0] = hp_sigma_val[i][1] = 0; + for(j = 2; j <= MAX_LEVEL; j++) + { + k += (hp_coefficient[i]*j + 50) / 100; + hp_sigma_val[i][j] = k; + if (k >= INT_MAX) + break; //Overflow protection. [Skotlex] + } + for(; j <= MAX_LEVEL; j++) + hp_sigma_val[i][j] = INT_MAX; + } +} + +/// Calculates base MaxHP value according to class and base level +/// The recursive equation used to calculate level bonus is (using integer operations) +/// f(0) = 35 | f(x+1) = f(x) + A + (x + B)*C/D +/// which reduces to something close to +/// f(x) = 35 + x*(A + B*C/D) + sum(i=2..x){ i*C/D } +static unsigned int status_base_pc_maxhp(struct map_session_data* sd, struct status_data* status) +{ + uint64 val = pc_class2idx(sd->status.class_); + val = 35 + sd->status.base_level*(int64)hp_coefficient2[val]/100 + hp_sigma_val[val][sd->status.base_level]; + + if((sd->class_&MAPID_UPPERMASK) == MAPID_NINJA || (sd->class_&MAPID_UPPERMASK) == MAPID_GUNSLINGER) + val += 100; //Since their HP can't be approximated well enough without this. + if((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) + val *= 3; //Triple max HP for top ranking Taekwons over level 90. + if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.base_level >= 99) + val += 2000; //Supernovice lvl99 hp bonus. + + val += val * status->vit/100; // +1% per each point of VIT + + if (sd->class_&JOBL_UPPER) + val += val * 25/100; //Trans classes get a 25% hp bonus + else if (sd->class_&JOBL_BABY) + val -= val * 30/100; //Baby classes get a 30% hp penalty + return (unsigned int)val; +} + +static unsigned int status_base_pc_maxsp(struct map_session_data* sd, struct status_data *status) +{ + uint64 val; + + val = 10 + sd->status.base_level*(int64)sp_coefficient[pc_class2idx(sd->status.class_)]/100; + val += val * status->int_/100; + + if (sd->class_&JOBL_UPPER) + val += val * 25/100; + else if (sd->class_&JOBL_BABY) + val -= val * 30/100; + if ((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) + val *= 3; //Triple max SP for top ranking Taekwons over level 90. + + return (unsigned int)val; +} + +//Calculates player data from scratch without counting SC adjustments. +//Should be invoked whenever players raise stats, learn passive skills or change equipment. +int status_calc_pc_(struct map_session_data* sd, bool first) +{ + static int calculating = 0; //Check for recursive call preemption. [Skotlex] + struct status_data *status; // pointer to the player's base status + const struct status_change *sc = &sd->sc; + struct s_skill b_skill[MAX_SKILL]; // previous skill tree + int b_weight, b_max_weight, b_cart_weight_max, // previous weight + i, index, skill,refinedef=0; + int64 i64; + + if (++calculating > 10) //Too many recursive calls! + return -1; + + // remember player-specific values that are currently being shown to the client (for refresh purposes) + memcpy(b_skill, &sd->status.skill, sizeof(b_skill)); + b_weight = sd->weight; + b_max_weight = sd->max_weight; + b_cart_weight_max = sd->cart_weight_max; + + pc_calc_skilltree(sd); // SkillTree calculation + + sd->max_weight = max_weight_base[pc_class2idx(sd->status.class_)]+sd->status.str*300; + + if(first) { + //Load Hp/SP from char-received data. + sd->battle_status.hp = sd->status.hp; + sd->battle_status.sp = sd->status.sp; + sd->regen.sregen = &sd->sregen; + sd->regen.ssregen = &sd->ssregen; + sd->weight=0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0 || sd->inventory_data[i] == NULL) + continue; + sd->weight += sd->inventory_data[i]->weight*sd->status.inventory[i].amount; + } + sd->cart_weight=0; + sd->cart_num=0; + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==0) + continue; + sd->cart_weight+=itemdb_weight(sd->status.cart[i].nameid)*sd->status.cart[i].amount; + sd->cart_num++; + } + } + + status = &sd->base_status; + // these are not zeroed. [zzo] + sd->hprate=100; + sd->sprate=100; + sd->castrate=100; + sd->delayrate=100; + sd->dsprate=100; + sd->hprecov_rate = 100; + sd->sprecov_rate = 100; + sd->matk_rate = 100; + sd->critical_rate = sd->hit_rate = sd->flee_rate = sd->flee2_rate = 100; + sd->def_rate = sd->def2_rate = sd->mdef_rate = sd->mdef2_rate = 100; + sd->regen.state.block = 0; + + // zeroed arrays, order follows the order in pc.h. + // add new arrays to the end of zeroed area in pc.h (see comments) and size here. [zzo] + memset (sd->param_bonus, 0, sizeof(sd->param_bonus) + + sizeof(sd->param_equip) + + sizeof(sd->subele) + + sizeof(sd->subrace) + + sizeof(sd->subrace2) + + sizeof(sd->subsize) + + sizeof(sd->reseff) + + sizeof(sd->weapon_coma_ele) + + sizeof(sd->weapon_coma_race) + + sizeof(sd->weapon_atk) + + sizeof(sd->weapon_atk_rate) + + sizeof(sd->arrow_addele) + + sizeof(sd->arrow_addrace) + + sizeof(sd->arrow_addsize) + + sizeof(sd->magic_addele) + + sizeof(sd->magic_addrace) + + sizeof(sd->magic_addsize) + + sizeof(sd->magic_atk_ele) + + sizeof(sd->critaddrace) + + sizeof(sd->expaddrace) + + sizeof(sd->ignore_mdef) + + sizeof(sd->ignore_def) + + sizeof(sd->itemgrouphealrate) + + sizeof(sd->sp_gain_race) + + sizeof(sd->sp_gain_race_attack) + + sizeof(sd->hp_gain_race_attack) + ); + + memset (&sd->right_weapon.overrefine, 0, sizeof(sd->right_weapon) - sizeof(sd->right_weapon.atkmods)); + memset (&sd->left_weapon.overrefine, 0, sizeof(sd->left_weapon) - sizeof(sd->left_weapon.atkmods)); + + if (sd->special_state.intravision && !sd->sc.data[SC_INTRAVISION]) //Clear intravision as long as nothing else is using it + clif_status_load(&sd->bl, SI_INTRAVISION, 0); + + memset(&sd->special_state,0,sizeof(sd->special_state)); + memset(&status->max_hp, 0, sizeof(struct status_data)-(sizeof(status->hp)+sizeof(status->sp))); + + //FIXME: Most of these stuff should be calculated once, but how do I fix the memset above to do that? [Skotlex] + status->speed = DEFAULT_WALK_SPEED; + //Give them all modes except these (useful for clones) + status->mode = MD_MASK&~(MD_BOSS|MD_PLANT|MD_DETECTOR|MD_ANGRY|MD_TARGETWEAK); + + status->size = (sd->class_&JOBL_BABY)?SZ_SMALL:SZ_MEDIUM; + if (battle_config.character_size && pc_isriding(sd)) { //[Lupus] + if (sd->class_&JOBL_BABY) { + if (battle_config.character_size&SZ_BIG) + status->size++; + } else + if(battle_config.character_size&SZ_MEDIUM) + status->size++; + } + status->aspd_rate = 1000; + status->ele_lv = 1; + status->race = RC_DEMIHUMAN; + + //zero up structures... + memset(&sd->autospell,0,sizeof(sd->autospell) + + sizeof(sd->autospell2) + + sizeof(sd->autospell3) + + sizeof(sd->addeff) + + sizeof(sd->addeff2) + + sizeof(sd->addeff3) + + sizeof(sd->skillatk) + + sizeof(sd->skillusesprate) + + sizeof(sd->skillusesp) + + sizeof(sd->skillheal) + + sizeof(sd->skillheal2) + + sizeof(sd->hp_loss) + + sizeof(sd->sp_loss) + + sizeof(sd->hp_regen) + + sizeof(sd->sp_regen) + + sizeof(sd->skillblown) + + sizeof(sd->skillcast) + + sizeof(sd->add_def) + + sizeof(sd->add_mdef) + + sizeof(sd->add_mdmg) + + sizeof(sd->add_drop) + + sizeof(sd->itemhealrate) + + sizeof(sd->subele2) + + sizeof(sd->skillcooldown) + + sizeof(sd->skillfixcast) + + sizeof(sd->skillvarcast) + ); + + memset (&sd->bonus, 0,sizeof(sd->bonus)); + + // Autobonus + pc_delautobonus(sd,sd->autobonus,ARRAYLENGTH(sd->autobonus),true); + pc_delautobonus(sd,sd->autobonus2,ARRAYLENGTH(sd->autobonus2),true); + pc_delautobonus(sd,sd->autobonus3,ARRAYLENGTH(sd->autobonus3),true); + + // Parse equipment. + for(i=0;i<EQI_MAX-1;i++) { + current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] + if(index < 0) + continue; + if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) + continue; + if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) + continue; + if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) + continue; + if(i == EQI_COSTUME_MID && sd->equip_index[EQI_COSTUME_LOW] == index) + continue; + if(i == EQI_COSTUME_TOP && (sd->equip_index[EQI_COSTUME_MID] == index || sd->equip_index[EQI_COSTUME_LOW] == index)) + continue; + if(!sd->inventory_data[index]) + continue; + + status->def += sd->inventory_data[index]->def; + + if(first && sd->inventory_data[index]->equip_script) + { //Execute equip-script on login + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + if (!calculating) + return 1; + } + + // sanitize the refine level in case someone decreased the value inbetween + if (sd->status.inventory[index].refine > MAX_REFINE) + sd->status.inventory[index].refine = MAX_REFINE; + + if(sd->inventory_data[index]->type == IT_WEAPON) { + int r,wlv = sd->inventory_data[index]->wlv; + struct weapon_data *wd; + struct weapon_atk *wa; + if (wlv >= REFINE_TYPE_MAX) + wlv = REFINE_TYPE_MAX - 1; + if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) { + wd = &sd->left_weapon; // Left-hand weapon + wa = &status->lhw; + } else { + wd = &sd->right_weapon; + wa = &status->rhw; + } + wa->atk += sd->inventory_data[index]->atk; + if ( (r = sd->status.inventory[index].refine) ) + wa->atk2 = refine_info[wlv].bonus[r-1] / 100; + +#ifdef RENEWAL + wa->matk += sd->inventory_data[index]->matk; + wa->wlv = wlv; + if( r ) // renewal magic attack refine bonus + wa->matk += refine_info[wlv].bonus[r-1] / 100; +#endif + + //Overrefine bonus. + if (r) + wd->overrefine = refine_info[wlv].randombonus_max[r-1] / 100; + + wa->range += sd->inventory_data[index]->range; + if(sd->inventory_data[index]->script) { + if (wd == &sd->left_weapon) { + sd->state.lr_flag = 1; + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } else + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + if (!calculating) //Abort, run_script retriggered this. [Skotlex] + return 1; + } + + if(sd->status.inventory[index].card[0]==CARD0_FORGE) + { // Forged weapon + wd->star += (sd->status.inventory[index].card[1]>>8); + if(wd->star >= 15) wd->star = 40; // 3 Star Crumbs now give +40 dmg + if(pc_famerank(MakeDWord(sd->status.inventory[index].card[2],sd->status.inventory[index].card[3]) ,MAPID_BLACKSMITH)) + wd->star += 10; + + if (!wa->ele) //Do not overwrite element from previous bonuses. + wa->ele = (sd->status.inventory[index].card[1]&0x0f); + } + } + else if(sd->inventory_data[index]->type == IT_ARMOR) { + int r; + if ( (r = sd->status.inventory[index].refine) ) + refinedef += refine_info[REFINE_TYPE_ARMOR].bonus[r-1]; + if(sd->inventory_data[index]->script) { + if( i == EQI_HAND_L ) //Shield + sd->state.lr_flag = 3; + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + if( i == EQI_HAND_L ) //Shield + sd->state.lr_flag = 0; + if (!calculating) //Abort, run_script retriggered this. [Skotlex] + return 1; + } + } + } + + if(sd->equip_index[EQI_AMMO] >= 0){ + index = sd->equip_index[EQI_AMMO]; + if(sd->inventory_data[index]){ // Arrows + sd->bonus.arrow_atk += sd->inventory_data[index]->atk; + sd->state.lr_flag = 2; + if( !itemdb_is_GNthrowable(sd->inventory_data[index]->nameid) ) //don't run scripts on throwable items + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + if (!calculating) //Abort, run_script retriggered status_calc_pc. [Skotlex] + return 1; + } + } + + /* we've got combos to process */ + if( sd->combos.count ) { + for( i = 0; i < sd->combos.count; i++ ) { + run_script(sd->combos.bonus[i],0,sd->bl.id,0); + if (!calculating) //Abort, run_script retriggered this. + return 1; + } + } + + //Store equipment script bonuses + memcpy(sd->param_equip,sd->param_bonus,sizeof(sd->param_equip)); + memset(sd->param_bonus, 0, sizeof(sd->param_bonus)); + + status->def += (refinedef+50)/100; + + //Parse Cards + for(i=0;i<EQI_MAX-1;i++) { + current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] + if(index < 0) + continue; + if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) + continue; + if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) + continue; + if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) + continue; + + if(sd->inventory_data[index]) { + int j,c; + struct item_data *data; + + //Card script execution. + if(itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + for(j=0;j<MAX_SLOTS;j++){ // Uses MAX_SLOTS to support Soul Bound system [Inkfish] + current_equip_card_id= c= sd->status.inventory[index].card[j]; + if(!c) + continue; + data = itemdb_exists(c); + if(!data) + continue; + if(first && data->equip_script) + { //Execute equip-script on login + run_script(data->equip_script,0,sd->bl.id,0); + if (!calculating) + return 1; + } + if(!data->script) + continue; + if(data->flag.no_equip) { //Card restriction checks. + if(map[sd->bl.m].flag.restricted && data->flag.no_equip&(8*map[sd->bl.m].zone)) + continue; + if(!map_flag_vs(sd->bl.m) && data->flag.no_equip&1) + continue; + if(map[sd->bl.m].flag.pvp && data->flag.no_equip&2) + continue; + if(map_flag_gvg(sd->bl.m) && data->flag.no_equip&4) + continue; + if(map[sd->bl.m].flag.battleground && data->flag.no_equip&8) + continue; + } + if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) + { //Left hand status. + sd->state.lr_flag = 1; + run_script(data->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } else + run_script(data->script,0,sd->bl.id,0); + if (!calculating) //Abort, run_script his function. [Skotlex] + return 1; + } + } + } + + if( sc->count && sc->data[SC_ITEMSCRIPT] ) + { + struct item_data *data = itemdb_exists(sc->data[SC_ITEMSCRIPT]->val1); + if( data && data->script ) + run_script(data->script,0,sd->bl.id,0); + } + + if( sd->pd ) + { // Pet Bonus + struct pet_data *pd = sd->pd; + if( pd && pd->petDB && pd->petDB->equip_script && pd->pet.intimate >= battle_config.pet_equip_min_friendly ) + run_script(pd->petDB->equip_script,0,sd->bl.id,0); + if( pd && pd->pet.intimate > 0 && (!battle_config.pet_equip_required || pd->pet.equip > 0) && pd->state.skillbonus == 1 && pd->bonus ) + pc_bonus(sd,pd->bonus->type, pd->bonus->val); + } + + //param_bonus now holds card bonuses. + if(status->rhw.range < 1) status->rhw.range = 1; + if(status->lhw.range < 1) status->lhw.range = 1; + if(status->rhw.range < status->lhw.range) + status->rhw.range = status->lhw.range; + + sd->bonus.double_rate += sd->bonus.double_add_rate; + sd->bonus.perfect_hit += sd->bonus.perfect_hit_add; + sd->bonus.splash_range += sd->bonus.splash_add_range; + + // Damage modifiers from weapon type + sd->right_weapon.atkmods[0] = atkmods[0][sd->weapontype1]; + sd->right_weapon.atkmods[1] = atkmods[1][sd->weapontype1]; + sd->right_weapon.atkmods[2] = atkmods[2][sd->weapontype1]; + sd->left_weapon.atkmods[0] = atkmods[0][sd->weapontype2]; + sd->left_weapon.atkmods[1] = atkmods[1][sd->weapontype2]; + sd->left_weapon.atkmods[2] = atkmods[2][sd->weapontype2]; + + if(pc_isriding(sd) && + (sd->status.weapon==W_1HSPEAR || sd->status.weapon==W_2HSPEAR)) + { //When Riding with spear, damage modifier to mid-class becomes + //same as versus large size. + sd->right_weapon.atkmods[1] = sd->right_weapon.atkmods[2]; + sd->left_weapon.atkmods[1] = sd->left_weapon.atkmods[2]; + } + +// ----- STATS CALCULATION ----- + + // Job bonuses + index = pc_class2idx(sd->status.class_); + for(i=0;i<(int)sd->status.job_level && i<MAX_LEVEL;i++){ + if(!job_bonus[index][i]) + continue; + switch(job_bonus[index][i]) { + case 1: status->str++; break; + case 2: status->agi++; break; + case 3: status->vit++; break; + case 4: status->int_++; break; + case 5: status->dex++; break; + case 6: status->luk++; break; + } + } + + // If a Super Novice has never died and is at least joblv 70, he gets all stats +10 + if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->die_counter == 0 && sd->status.job_level >= 70){ + status->str += 10; + status->agi += 10; + status->vit += 10; + status->int_+= 10; + status->dex += 10; + status->luk += 10; + } + + // Absolute modifiers from passive skills + if(pc_checkskill(sd,BS_HILTBINDING)>0) + status->str++; + if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0) + status->int_ += (skill+1)/2; // +1 INT / 2 lv + if((skill=pc_checkskill(sd,AC_OWL))>0) + status->dex += skill; + if((skill = pc_checkskill(sd,RA_RESEARCHTRAP))>0) + status->int_ += skill; + + // Bonuses from cards and equipment as well as base stat, remember to avoid overflows. + i = status->str + sd->status.str + sd->param_bonus[0] + sd->param_equip[0]; + status->str = cap_value(i,0,USHRT_MAX); + i = status->agi + sd->status.agi + sd->param_bonus[1] + sd->param_equip[1]; + status->agi = cap_value(i,0,USHRT_MAX); + i = status->vit + sd->status.vit + sd->param_bonus[2] + sd->param_equip[2]; + status->vit = cap_value(i,0,USHRT_MAX); + i = status->int_+ sd->status.int_+ sd->param_bonus[3] + sd->param_equip[3]; + status->int_ = cap_value(i,0,USHRT_MAX); + i = status->dex + sd->status.dex + sd->param_bonus[4] + sd->param_equip[4]; + status->dex = cap_value(i,0,USHRT_MAX); + i = status->luk + sd->status.luk + sd->param_bonus[5] + sd->param_equip[5]; + status->luk = cap_value(i,0,USHRT_MAX); + +// ------ BASE ATTACK CALCULATION ------ + + // Base batk value is set on status_calc_misc + // weapon-type bonus (FIXME: Why is the weapon_atk bonus applied to base attack?) + if (sd->status.weapon < MAX_WEAPON_TYPE && sd->weapon_atk[sd->status.weapon]) + status->batk += sd->weapon_atk[sd->status.weapon]; + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,BS_HILTBINDING))>0) + status->batk += 4; + +// ----- HP MAX CALCULATION ----- + + // Basic MaxHP value + //We hold the standard Max HP here to make it faster to recalculate on vit changes. + sd->status.max_hp = status_base_pc_maxhp(sd,status); + //This is done to handle underflows from negative Max HP bonuses + i64 = sd->status.max_hp + (int)status->max_hp; + status->max_hp = (unsigned int)cap_value(i64, 0, INT_MAX); + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,CR_TRUST))>0) + status->max_hp += skill*200; + + // Apply relative modifiers from equipment + if(sd->hprate < 0) + sd->hprate = 0; + if(sd->hprate!=100) + status->max_hp = (int64)status->max_hp * sd->hprate/100; + if(battle_config.hp_rate != 100) + status->max_hp = (int64)status->max_hp * battle_config.hp_rate/100; + + if(status->max_hp > (unsigned int)battle_config.max_hp) + status->max_hp = battle_config.max_hp; + else if(!status->max_hp) + status->max_hp = 1; + +// ----- SP MAX CALCULATION ----- + + // Basic MaxSP value + sd->status.max_sp = status_base_pc_maxsp(sd,status); + //This is done to handle underflows from negative Max SP bonuses + i64 = sd->status.max_sp + (int)status->max_sp; + status->max_sp = (unsigned int)cap_value(i64, 0, INT_MAX); + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,SL_KAINA))>0) + status->max_sp += 30*skill; + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) + status->max_sp += (int64)status->max_sp * skill/100; + if((skill=pc_checkskill(sd,HW_SOULDRAIN))>0) + status->max_sp += (int64)status->max_sp * 2*skill/100; + if( (skill = pc_checkskill(sd,RA_RESEARCHTRAP)) > 0 ) + status->max_sp += 200 + 20 * skill; + if( (skill = pc_checkskill(sd,WM_LESSON)) > 0 ) + status->max_sp += 30 * skill; + + + // Apply relative modifiers from equipment + if(sd->sprate < 0) + sd->sprate = 0; + if(sd->sprate!=100) + status->max_sp = (int64)status->max_sp * sd->sprate/100; + if(battle_config.sp_rate != 100) + status->max_sp = (int64)status->max_sp * battle_config.sp_rate/100; + + if(status->max_sp > (unsigned int)battle_config.max_sp) + status->max_sp = battle_config.max_sp; + else if(!status->max_sp) + status->max_sp = 1; + +// ----- RESPAWN HP/SP ----- +// + //Calc respawn hp and store it on base_status + if (sd->special_state.restart_full_recover) + { + status->hp = status->max_hp; + status->sp = status->max_sp; + } else { + if((sd->class_&MAPID_BASEMASK) == MAPID_NOVICE && !(sd->class_&JOBL_2) + && battle_config.restart_hp_rate < 50) + status->hp = status->max_hp>>1; + else + status->hp = (int64)status->max_hp * battle_config.restart_hp_rate/100; + if(!status->hp) + status->hp = 1; + + status->sp = (int64)status->max_sp * battle_config.restart_sp_rate /100; + + if( !status->sp ) /* the minimum for the respawn setting is SP:1 */ + status->sp = 1; + } + +// ----- MISC CALCULATION ----- + status_calc_misc(&sd->bl, status, sd->status.base_level); + + //Equipment modifiers for misc settings + if(sd->matk_rate < 0) + sd->matk_rate = 0; + + if(sd->matk_rate != 100){ + status->matk_max = status->matk_max * sd->matk_rate/100; + status->matk_min = status->matk_min * sd->matk_rate/100; + } + + if(sd->hit_rate < 0) + sd->hit_rate = 0; + if(sd->hit_rate != 100) + status->hit = status->hit * sd->hit_rate/100; + + if(sd->flee_rate < 0) + sd->flee_rate = 0; + if(sd->flee_rate != 100) + status->flee = status->flee * sd->flee_rate/100; + + if(sd->def2_rate < 0) + sd->def2_rate = 0; + if(sd->def2_rate != 100) + status->def2 = status->def2 * sd->def2_rate/100; + + if(sd->mdef2_rate < 0) + sd->mdef2_rate = 0; + if(sd->mdef2_rate != 100) + status->mdef2 = status->mdef2 * sd->mdef2_rate/100; + + if(sd->critical_rate < 0) + sd->critical_rate = 0; + if(sd->critical_rate != 100) + status->cri = status->cri * sd->critical_rate/100; + + if(sd->flee2_rate < 0) + sd->flee2_rate = 0; + if(sd->flee2_rate != 100) + status->flee2 = status->flee2 * sd->flee2_rate/100; + +// ----- HIT CALCULATION ----- + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,BS_WEAPONRESEARCH))>0) + status->hit += skill*2; + if((skill=pc_checkskill(sd,AC_VULTURE))>0){ +#ifndef RENEWAL + status->hit += skill; +#endif + if(sd->status.weapon == W_BOW) + status->rhw.range += skill; + } + if(sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE) + { + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0) + status->hit += 2*skill; + if((skill=pc_checkskill(sd,GS_SNAKEEYE))>0) { + status->hit += skill; + status->rhw.range += skill; + } + } + +// ----- FLEE CALCULATION ----- + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,TF_MISS))>0) + status->flee += skill*(sd->class_&JOBL_2 && (sd->class_&MAPID_BASEMASK) == MAPID_THIEF? 4 : 3); + if((skill=pc_checkskill(sd,MO_DODGE))>0) + status->flee += (skill*3)>>1; +// ----- EQUIPMENT-DEF CALCULATION ----- + + // Apply relative modifiers from equipment + if(sd->def_rate < 0) + sd->def_rate = 0; + if(sd->def_rate != 100) { + i = status->def * sd->def_rate/100; + status->def = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); + } + +#ifndef RENEWAL + if (!battle_config.weapon_defense_type && status->def > battle_config.max_def) + { + status->def2 += battle_config.over_def_bonus*(status->def -battle_config.max_def); + status->def = (unsigned char)battle_config.max_def; + } +#endif + +// ----- EQUIPMENT-MDEF CALCULATION ----- + + // Apply relative modifiers from equipment + if(sd->mdef_rate < 0) + sd->mdef_rate = 0; + if(sd->mdef_rate != 100) { + i = status->mdef * sd->mdef_rate/100; + status->mdef = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); + } + +#ifndef RENEWAL + if (!battle_config.magic_defense_type && status->mdef > battle_config.max_def) + { + status->mdef2 += battle_config.over_def_bonus*(status->mdef -battle_config.max_def); + status->mdef = (signed char)battle_config.max_def; + } +#endif + +// ----- ASPD CALCULATION ----- +// Unlike other stats, ASPD rate modifiers from skills/SCs/items/etc are first all added together, then the final modifier is applied + + // Basic ASPD value + i = status_base_amotion_pc(sd,status); + status->amotion = cap_value(i,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); + + // Relative modifiers from passive skills +#ifndef RENEWAL_ASPD + if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) + status->aspd_rate -= 5*skill; + if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) + status->aspd_rate -= 30*skill; + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && + (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) + status->aspd_rate -= ((skill+1)/2) * 10; + if(pc_isriding(sd)) + status->aspd_rate += 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); + else if(pc_isridingdragon(sd)) + status->aspd_rate += 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); +#else // needs more info + if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) + status->aspd_rate += 5*skill; + if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) + status->aspd_rate += 30*skill; + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && + (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) + status->aspd_rate += ((skill+1)/2) * 10; + if(pc_isriding(sd)) + status->aspd_rate -= 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); + else if(pc_isridingdragon(sd)) + status->aspd_rate -= 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); +#endif + status->adelay = 2*status->amotion; + + +// ----- DMOTION ----- +// + i = 800-status->agi*4; + status->dmotion = cap_value(i, 400, 800); + if(battle_config.pc_damage_delay_rate != 100) + status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; + +// ----- MISC CALCULATIONS ----- + + // Weight + if((skill=pc_checkskill(sd,MC_INCCARRY))>0) + sd->max_weight += 2000*skill; + if(pc_isriding(sd) && pc_checkskill(sd,KN_RIDING)>0) + sd->max_weight += 10000; + else if(pc_isridingdragon(sd)) + sd->max_weight += 5000+2000*pc_checkskill(sd,RK_DRAGONTRAINING); + if(sc->data[SC_KNOWLEDGE]) + sd->max_weight += sd->max_weight*sc->data[SC_KNOWLEDGE]->val1/10; + if((skill=pc_checkskill(sd,ALL_INCCARRY))>0) + sd->max_weight += 2000*skill; + + sd->cart_weight_max = battle_config.max_cart_weight + (pc_checkskill(sd, GN_REMODELING_CART)*5000); + + if (pc_checkskill(sd,SM_MOVINGRECOVERY)>0) + sd->regen.state.walk = 1; + else + sd->regen.state.walk = 0; + + // Skill SP cost + if((skill=pc_checkskill(sd,HP_MANARECHARGE))>0 ) + sd->dsprate -= 4*skill; + + if(sc->data[SC_SERVICE4U]) + sd->dsprate -= sc->data[SC_SERVICE4U]->val3; + + if(sc->data[SC_SPCOST_RATE]) + sd->dsprate -= sc->data[SC_SPCOST_RATE]->val1; + + //Underflow protections. + if(sd->dsprate < 0) + sd->dsprate = 0; + if(sd->castrate < 0) + sd->castrate = 0; + if(sd->delayrate < 0) + sd->delayrate = 0; + if(sd->hprecov_rate < 0) + sd->hprecov_rate = 0; + if(sd->sprecov_rate < 0) + sd->sprecov_rate = 0; + + // Anti-element and anti-race + if((skill=pc_checkskill(sd,CR_TRUST))>0) + sd->subele[ELE_HOLY] += skill*5; + if((skill=pc_checkskill(sd,BS_SKINTEMPER))>0) { + sd->subele[ELE_NEUTRAL] += skill; + sd->subele[ELE_FIRE] += skill*4; + } + if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0 ){ + skill = skill*4; + sd->right_weapon.addrace[RC_DRAGON]+=skill; + sd->left_weapon.addrace[RC_DRAGON]+=skill; + sd->magic_addrace[RC_DRAGON]+=skill; + sd->subrace[RC_DRAGON]+=skill; + } + + if(sc->count){ + if(sc->data[SC_CONCENTRATE]) { //Update the card-bonus data + sc->data[SC_CONCENTRATE]->val3 = sd->param_bonus[1]; //Agi + sc->data[SC_CONCENTRATE]->val4 = sd->param_bonus[4]; //Dex + } + if(sc->data[SC_SIEGFRIED]){ + i = sc->data[SC_SIEGFRIED]->val2; + sd->subele[ELE_WATER] += i; + sd->subele[ELE_EARTH] += i; + sd->subele[ELE_FIRE] += i; + sd->subele[ELE_WIND] += i; + sd->subele[ELE_POISON] += i; + sd->subele[ELE_HOLY] += i; + sd->subele[ELE_DARK] += i; + sd->subele[ELE_GHOST] += i; + sd->subele[ELE_UNDEAD] += i; + } + if(sc->data[SC_PROVIDENCE]){ + sd->subele[ELE_HOLY] += sc->data[SC_PROVIDENCE]->val2; + sd->subrace[RC_DEMON] += sc->data[SC_PROVIDENCE]->val2; + } + if(sc->data[SC_ARMOR_ELEMENT]) { //This status change should grant card-type elemental resist. + sd->subele[ELE_WATER] += sc->data[SC_ARMOR_ELEMENT]->val1; + sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_ELEMENT]->val2; + sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_ELEMENT]->val3; + sd->subele[ELE_WIND] += sc->data[SC_ARMOR_ELEMENT]->val4; + } + if(sc->data[SC_ARMOR_RESIST]) { // Undead Scroll + sd->subele[ELE_WATER] += sc->data[SC_ARMOR_RESIST]->val1; + sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_RESIST]->val2; + sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_RESIST]->val3; + sd->subele[ELE_WIND] += sc->data[SC_ARMOR_RESIST]->val4; + } + if( sc->data[SC_FIRE_CLOAK_OPTION] ) { + i = sc->data[SC_FIRE_CLOAK_OPTION]->val2; + sd->subele[ELE_FIRE] += i; + sd->subele[ELE_WATER] -= i; + } + if( sc->data[SC_WATER_DROP_OPTION] ) { + i = sc->data[SC_WATER_DROP_OPTION]->val2; + sd->subele[ELE_WATER] += i; + sd->subele[ELE_WIND] -= i; + } + if( sc->data[SC_WIND_CURTAIN_OPTION] ) { + i = sc->data[SC_WIND_CURTAIN_OPTION]->val2; + sd->subele[ELE_WIND] += i; + sd->subele[ELE_EARTH] -= i; + } + if( sc->data[SC_STONE_SHIELD_OPTION] ) { + i = sc->data[SC_STONE_SHIELD_OPTION]->val2; + sd->subele[ELE_EARTH] += i; + sd->subele[ELE_FIRE] -= i; + } + if( sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_FIRE] += 25; + if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_WATER] += 25; + if( sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_WIND] += 25; + if( sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_EARTH] += 25; + } + status_cpy(&sd->battle_status, status); + +// ----- CLIENT-SIDE REFRESH ----- + if(!sd->bl.prev) { + //Will update on LoadEndAck + calculating = 0; + return 0; + } + if(memcmp(b_skill,sd->status.skill,sizeof(sd->status.skill))) + clif_skillinfoblock(sd); + if(b_weight != sd->weight) + clif_updatestatus(sd,SP_WEIGHT); + if(b_max_weight != sd->max_weight) { + clif_updatestatus(sd,SP_MAXWEIGHT); + pc_updateweightstatus(sd); + } + if( b_cart_weight_max != sd->cart_weight_max ) { + clif_updatestatus(sd,SP_CARTINFO); + } + + calculating = 0; + + return 0; +} + +int status_calc_mercenary_(struct mercenary_data *md, bool first) +{ + struct status_data *status = &md->base_status; + struct s_mercenary *merc = &md->mercenary; + + if( first ) + { + memcpy(status, &md->db->status, sizeof(struct status_data)); + status->mode = MD_CANMOVE|MD_CANATTACK; + status->hp = status->max_hp; + status->sp = status->max_sp; + md->battle_status.hp = merc->hp; + md->battle_status.sp = merc->sp; + } + + status_calc_misc(&md->bl, status, md->db->lv); + status_cpy(&md->battle_status, status); + + return 0; +} + +int status_calc_homunculus_(struct homun_data *hd, bool first) +{ + struct status_data *status = &hd->base_status; + struct s_homunculus *hom = &hd->homunculus; + int skill; + int amotion; + + status->str = hom->str / 10; + status->agi = hom->agi / 10; + status->vit = hom->vit / 10; + status->dex = hom->dex / 10; + status->int_ = hom->int_ / 10; + status->luk = hom->luk / 10; + + if (first) { //[orn] + const struct s_homunculus_db *db = hd->homunculusDB; + status->def_ele = db->element; + status->ele_lv = 1; + status->race = db->race; + status->size = (hom->class_ == db->evo_class)?db->evo_size:db->base_size; + status->rhw.range = 1 + status->size; + status->mode = MD_CANMOVE|MD_CANATTACK; + status->speed = DEFAULT_WALK_SPEED; + if (battle_config.hom_setting&0x8 && hd->master) + status->speed = status_get_speed(&hd->master->bl); + + status->hp = 1; + status->sp = 1; + } + skill = hom->level/10 + status->vit/5; + status->def = cap_value(skill, 0, 99); + + skill = hom->level/10 + status->int_/5; + status->mdef = cap_value(skill, 0, 99); + + status->max_hp = hom->max_hp ; + status->max_sp = hom->max_sp ; + + merc_hom_calc_skilltree(hd, 0); + + if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) + status->def += skill * 4; + + if((skill = merc_hom_checkskill(hd,HVAN_INSTRUCT)) > 0) + { + status->int_ += 1 +skill/2 +skill/4 +skill/5; + status->str += 1 +skill/3 +skill/3 +skill/4; + } + + if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) + status->max_hp += skill * 2 * status->max_hp / 100; + + if((skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0) + status->max_sp += (1 +skill/2 -skill/4 +skill/5) * status->max_sp / 100 ; + + if (first) { + hd->battle_status.hp = hom->hp ; + hd->battle_status.sp = hom->sp ; + } + + status->rhw.atk = status->dex; + status->rhw.atk2 = status->str + hom->level; + + status->aspd_rate = 1000; + + amotion = (1000 -4*status->agi -status->dex) * hd->homunculusDB->baseASPD/1000; + status->amotion = cap_value(amotion,battle_config.max_aspd,2000); + status->adelay = status->amotion; //It seems adelay = amotion for Homunculus. + + status_calc_misc(&hd->bl, status, hom->level); + +#ifdef RENEWAL + status->matk_max = status->matk_min; +#endif + + status_cpy(&hd->battle_status, status); + return 1; +} + +int status_calc_elemental_(struct elemental_data *ed, bool first) { + struct status_data *status = &ed->base_status; + struct s_elemental *ele = &ed->elemental; + struct map_session_data *sd = ed->master; + + if( !sd ) + return 0; + + if( first ) { + memcpy(status, &ed->db->status, sizeof(struct status_data)); + if( !ele->mode ) + status->mode = EL_MODE_PASSIVE; + else + status->mode = ele->mode; + + status_calc_misc(&ed->bl, status, 0); + + status->max_hp = ele->max_hp; + status->max_sp = ele->max_sp; + status->hp = ele->hp; + status->sp = ele->sp; + status->rhw.atk = ele->atk; + status->rhw.atk2 = ele->atk2; + + status->matk_min += ele->matk; + status->def += ele->def; + status->mdef += ele->mdef; + status->flee = ele->flee; + status->hit = ele->hit; + + memcpy(&ed->battle_status,status,sizeof(struct status_data)); + } else { + status_calc_misc(&ed->bl, status, 0); + status_cpy(&ed->battle_status, status); + } + + return 0; +} + +int status_calc_npc_(struct npc_data *nd, bool first) { + struct status_data *status = &nd->status; + + if (!nd) + return 0; + + if (first) { + status->hp = 1; + status->sp = 1; + status->max_hp = 1; + status->max_sp = 1; + + status->def_ele = ELE_NEUTRAL; + status->ele_lv = 1; + status->race = RC_DEMIHUMAN; + status->size = nd->size; + status->rhw.range = 1 + status->size; + status->mode = MD_CANMOVE|MD_CANATTACK; + status->speed = nd->speed; + } + + status->str = nd->stat_point; + status->agi = nd->stat_point; + status->vit = nd->stat_point; + status->int_= nd->stat_point; + status->dex = nd->stat_point; + status->luk = nd->stat_point; + + status_calc_misc(&nd->bl, status, nd->level); + status_cpy(&nd->status, status); + + return 0; +} + +static unsigned short status_calc_str(struct block_list *,struct status_change *,int); +static unsigned short status_calc_agi(struct block_list *,struct status_change *,int); +static unsigned short status_calc_vit(struct block_list *,struct status_change *,int); +static unsigned short status_calc_int(struct block_list *,struct status_change *,int); +static unsigned short status_calc_dex(struct block_list *,struct status_change *,int); +static unsigned short status_calc_luk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_batk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_watk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_matk(struct block_list *,struct status_change *,int); +static signed short status_calc_hit(struct block_list *,struct status_change *,int); +static signed short status_calc_critical(struct block_list *,struct status_change *,int); +static signed short status_calc_flee(struct block_list *,struct status_change *,int); +static signed short status_calc_flee2(struct block_list *,struct status_change *,int); +static defType status_calc_def(struct block_list *bl, struct status_change *sc, int); +static signed short status_calc_def2(struct block_list *,struct status_change *,int); +static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int); +static signed short status_calc_mdef2(struct block_list *,struct status_change *,int); +static unsigned short status_calc_speed(struct block_list *,struct status_change *,int); +static short status_calc_aspd_rate(struct block_list *,struct status_change *,int); +static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion); +#ifdef RENEWAL_ASPD +static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag); +#endif +static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int); +static unsigned int status_calc_maxhp(struct block_list *,struct status_change *, uint64); +static unsigned int status_calc_maxsp(struct block_list *,struct status_change *,unsigned int); +static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element); +static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv); +static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode); +#ifdef RENEWAL +static unsigned short status_calc_ematk(struct block_list *,struct status_change *,int); +#endif + +//Calculates base regen values. +void status_calc_regen(struct block_list *bl, struct status_data *status, struct regen_data *regen) +{ + struct map_session_data *sd; + int val, skill, reg_flag; + + if( !(bl->type&BL_REGEN) || !regen ) + return; + + sd = BL_CAST(BL_PC,bl); + val = 1 + (status->vit/5) + (status->max_hp/200); + + if( sd && sd->hprecov_rate != 100 ) + val = val*sd->hprecov_rate/100; + + reg_flag = bl->type == BL_PC ? 0 : 1; + + regen->hp = cap_value(val, reg_flag, SHRT_MAX); + + val = 1 + (status->int_/6) + (status->max_sp/100); + if( status->int_ >= 120 ) + val += ((status->int_-120)>>1) + 4; + + if( sd && sd->sprecov_rate != 100 ) + val = val*sd->sprecov_rate/100; + + regen->sp = cap_value(val, reg_flag, SHRT_MAX); + + if( sd ) + { + struct regen_data_sub *sregen; + if( (skill=pc_checkskill(sd,HP_MEDITATIO)) > 0 ) + { + val = regen->sp*(100+3*skill)/100; + regen->sp = cap_value(val, 1, SHRT_MAX); + } + //Only players have skill/sitting skill regen for now. + sregen = regen->sregen; + + val = 0; + if( (skill=pc_checkskill(sd,SM_RECOVERY)) > 0 ) + val += skill*5 + skill*status->max_hp/500; + sregen->hp = cap_value(val, 0, SHRT_MAX); + + val = 0; + if( (skill=pc_checkskill(sd,MG_SRECOVERY)) > 0 ) + val += skill*3 + skill*status->max_sp/500; + if( (skill=pc_checkskill(sd,NJ_NINPOU)) > 0 ) + val += skill*3 + skill*status->max_sp/500; + if( (skill=pc_checkskill(sd,WM_LESSON)) > 0 ) + val += 3 + 3 * skill; + + sregen->sp = cap_value(val, 0, SHRT_MAX); + + // Skill-related recovery (only when sit) + sregen = regen->ssregen; + + val = 0; + if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) + val += skill*4 + skill*status->max_hp/500; + + if( (skill=pc_checkskill(sd,TK_HPTIME)) > 0 && sd->state.rest ) + val += skill*30 + skill*status->max_hp/500; + sregen->hp = cap_value(val, 0, SHRT_MAX); + + val = 0; + if( (skill=pc_checkskill(sd,TK_SPTIME)) > 0 && sd->state.rest ) + { + val += skill*3 + skill*status->max_sp/500; + if ((skill=pc_checkskill(sd,SL_KAINA)) > 0) //Power up Enjoyable Rest + val += (30+10*skill)*val/100; + } + if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) + val += skill*2 + skill*status->max_sp/500; + sregen->sp = cap_value(val, 0, SHRT_MAX); + } + + if( bl->type == BL_HOM ) { + struct homun_data *hd = (TBL_HOM*)bl; + if( (skill = merc_hom_checkskill(hd,HAMI_SKIN)) > 0 ) { + val = regen->hp*(100+5*skill)/100; + regen->hp = cap_value(val, 1, SHRT_MAX); + } + if( (skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0 ) { + val = regen->sp*(100+3*skill)/100; + regen->sp = cap_value(val, 1, SHRT_MAX); + } + } else if( bl->type == BL_MER ) { + val = (status->max_hp * status->vit / 10000 + 1) * 6; + regen->hp = cap_value(val, 1, SHRT_MAX); + + val = (status->max_sp * (status->int_ + 10) / 750) + 1; + regen->sp = cap_value(val, 1, SHRT_MAX); + } else if( bl->type == BL_ELEM ) { + val = (status->max_hp * status->vit / 10000 + 1) * 6; + regen->hp = cap_value(val, 1, SHRT_MAX); + + val = (status->max_sp * (status->int_ + 10) / 750) + 1; + regen->sp = cap_value(val, 1, SHRT_MAX); + } +} + +//Calculates SC related regen rates. +void status_calc_regen_rate(struct block_list *bl, struct regen_data *regen, struct status_change *sc) +{ + if (!(bl->type&BL_REGEN) || !regen) + return; + + regen->flag = RGN_HP|RGN_SP; + if(regen->sregen) + { + if (regen->sregen->hp) + regen->flag|=RGN_SHP; + + if (regen->sregen->sp) + regen->flag|=RGN_SSP; + regen->sregen->rate.hp = regen->sregen->rate.sp = 1; + } + if (regen->ssregen) + { + if (regen->ssregen->hp) + regen->flag|=RGN_SHP; + + if (regen->ssregen->sp) + regen->flag|=RGN_SSP; + regen->ssregen->rate.hp = regen->ssregen->rate.sp = 1; + } + regen->rate.hp = regen->rate.sp = 1; + + if (!sc || !sc->count) + return; + + if ( + (sc->data[SC_POISON] && !sc->data[SC_SLOWPOISON]) + || (sc->data[SC_DPOISON] && !sc->data[SC_SLOWPOISON]) + || sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST] + || sc->data[SC_TRICKDEAD] + || sc->data[SC_BLEEDING] + || sc->data[SC_MAGICMUSHROOM] + || sc->data[SC_RAISINGDRAGON] + || sc->data[SC_SATURDAYNIGHTFEVER] + ) //No regen + regen->flag = 0; + + if ( + sc->data[SC_DANCING] || sc->data[SC_OBLIVIONCURSE] || sc->data[SC_MAXIMIZEPOWER] + || ( + (bl->type == BL_PC && ((TBL_PC*)bl)->class_&MAPID_UPPERMASK) == MAPID_MONK && + (sc->data[SC_EXTREMITYFIST] || (sc->data[SC_EXPLOSIONSPIRITS] && (!sc->data[SC_SPIRIT] || sc->data[SC_SPIRIT]->val2 != SL_MONK))) + ) + ) //No natural SP regen + regen->flag &=~RGN_SP; + + if( + sc->data[SC_TENSIONRELAX] + ) { + regen->rate.hp += 2; + if (regen->sregen) + regen->sregen->rate.hp += 3; + } + if (sc->data[SC_MAGNIFICAT]) + { + regen->rate.hp += 1; + regen->rate.sp += 1; + } + if (sc->data[SC_REGENERATION]) + { + const struct status_change_entry *sce = sc->data[SC_REGENERATION]; + if (!sce->val4) + { + regen->rate.hp += sce->val2; + regen->rate.sp += sce->val3; + } else + regen->flag&=~sce->val4; //Remove regen as specified by val4 + } + if(sc->data[SC_GT_REVITALIZE]){ + regen->hp = cap_value(regen->hp*sc->data[SC_GT_REVITALIZE]->val3/100, 1, SHRT_MAX); + regen->state.walk= 1; + } + if ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) //if insignia lvl 1 + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1)) + regen->rate.hp *= 2; + +} +void status_calc_state( struct block_list *bl, struct status_change *sc, enum scs_flag flag, bool start ) { + + /* no sc at all, we can zero without any extra weight over our conciousness */ + if( !sc->count ) { + memset(&sc->cant, 0, sizeof (sc->cant)); + return; + } + + /* can move? */ + if( flag&SCS_NOMOVE ) { + if( !(flag&SCS_NOMOVECOND) ) { + sc->cant.move += ( start ? 1 : -1 ); + } else if( + (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) // cannot move while gospel is in effect + || (sc->data[SC_BASILICA] && sc->data[SC_BASILICA]->val4 == bl->id) // Basilica caster cannot move + || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF) + || (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) + || (sc->data[SC_CAMOUFLAGE] && sc->data[SC_CAMOUFLAGE]->val1 < 3 + && !(sc->data[SC_CAMOUFLAGE]->val3&1)) + ) { + sc->cant.move += ( start ? 1 : -1 ); + } + } + + /* can't use skills */ + if( flag&SCS_NOCAST ) { + if( !(flag&SCS_NOCASTCOND) ) { + sc->cant.cast += ( start ? 1 : -1 ); + } else if( (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) ){ + sc->cant.cast += ( start ? 1 : -1 ); + } + } + + /* player-only states */ + if( bl->type == BL_PC ) { + + /* can pick items? */ + if( flag&SCS_NOPICKITEM ) { + if( !(flag&SCS_NOPICKITEMCOND) ) { + sc->cant.pickup += ( start ? 1 : -1 ); + } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { + sc->cant.pickup += ( start ? 1 : -1 ); + } + } + + /* can drop items? */ + if( flag&SCS_NODROPITEM ) { + if( !(flag&SCS_NODROPITEMCOND) ) { + sc->cant.drop += ( start ? 1 : -1 ); + } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { + sc->cant.drop += ( start ? 1 : -1 ); + } + } + } + + return; +} +/// Recalculates parts of an object's battle status according to the specified flags. +/// @param flag bitfield of values from enum scb_flag +void status_calc_bl_main(struct block_list *bl, /*enum scb_flag*/int flag) +{ + const struct status_data *b_status = status_get_base_status(bl); + struct status_data *status = status_get_status_data(bl); + struct status_change *sc = status_get_sc(bl); + TBL_PC *sd = BL_CAST(BL_PC,bl); + int temp; + + if (!b_status || !status) + return; + + if((!(bl->type&BL_REGEN)) && (!sc || !sc->count)) { //No difference. + status_cpy(status, b_status); + return; + } + + if(flag&SCB_STR) { + status->str = status_calc_str(bl, sc, b_status->str); + flag|=SCB_BATK; + if( bl->type&BL_HOM ) + flag |= SCB_WATK; + } + + if(flag&SCB_AGI) { + status->agi = status_calc_agi(bl, sc, b_status->agi); + flag|=SCB_FLEE +#ifdef RENEWAL + |SCB_DEF2 +#endif + ; + if( bl->type&(BL_PC|BL_HOM) ) + flag |= SCB_ASPD|SCB_DSPD; + } + + if(flag&SCB_VIT) { + status->vit = status_calc_vit(bl, sc, b_status->vit); + flag|=SCB_DEF2|SCB_MDEF2; + if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) + flag |= SCB_MAXHP; + if( bl->type&BL_HOM ) + flag |= SCB_DEF; + } + + if(flag&SCB_INT) { + status->int_ = status_calc_int(bl, sc, b_status->int_); + flag|=SCB_MATK|SCB_MDEF2; + if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) + flag |= SCB_MAXSP; + if( bl->type&BL_HOM ) + flag |= SCB_MDEF; + } + + if(flag&SCB_DEX) { + status->dex = status_calc_dex(bl, sc, b_status->dex); + flag|=SCB_BATK|SCB_HIT +#ifdef RENEWAL + |SCB_MATK|SCB_MDEF2 +#endif + ; + if( bl->type&(BL_PC|BL_HOM) ) + flag |= SCB_ASPD; + if( bl->type&BL_HOM ) + flag |= SCB_WATK; + } + + if(flag&SCB_LUK) { + status->luk = status_calc_luk(bl, sc, b_status->luk); + flag|=SCB_BATK|SCB_CRI|SCB_FLEE2 +#ifdef RENEWAL + |SCB_MATK|SCB_HIT|SCB_FLEE +#endif + ; + } + + if(flag&SCB_BATK && b_status->batk) { + status->batk = status_base_atk(bl,status); + temp = b_status->batk - status_base_atk(bl,b_status); + if (temp) + { + temp += status->batk; + status->batk = cap_value(temp, 0, USHRT_MAX); + } + status->batk = status_calc_batk(bl, sc, status->batk); + } + + if(flag&SCB_WATK) { + + status->rhw.atk = status_calc_watk(bl, sc, b_status->rhw.atk); + if (!sd) //Should not affect weapon refine bonus + status->rhw.atk2 = status_calc_watk(bl, sc, b_status->rhw.atk2); + + if(b_status->lhw.atk) { + if (sd) { + sd->state.lr_flag = 1; + status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); + sd->state.lr_flag = 0; + } else { + status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); + status->lhw.atk2= status_calc_watk(bl, sc, b_status->lhw.atk2); + } + } + + if( bl->type&BL_HOM ) + { + status->rhw.atk += (status->dex - b_status->dex); + status->rhw.atk2 += (status->str - b_status->str); + if( status->rhw.atk2 < status->rhw.atk ) + status->rhw.atk2 = status->rhw.atk; + } + } + + if(flag&SCB_HIT) { + if (status->dex == b_status->dex +#ifdef RENEWAL + && status->luk == b_status->luk +#endif + ) + status->hit = status_calc_hit(bl, sc, b_status->hit); + else + status->hit = status_calc_hit(bl, sc, b_status->hit + (status->dex - b_status->dex) +#ifdef RENEWAL + + (status->luk/3 - b_status->luk/3) +#endif + ); + } + + if(flag&SCB_FLEE) { + if (status->agi == b_status->agi +#ifdef RENEWAL + && status->luk == b_status->luk +#endif + ) + status->flee = status_calc_flee(bl, sc, b_status->flee); + else + status->flee = status_calc_flee(bl, sc, b_status->flee +(status->agi - b_status->agi) +#ifdef RENEWAL + + (status->luk/5 - b_status->luk/5) +#endif + ); + } + + if(flag&SCB_DEF) + { + status->def = status_calc_def(bl, sc, b_status->def); + + if( bl->type&BL_HOM ) + status->def += (status->vit/5 - b_status->vit/5); + } + + if(flag&SCB_DEF2) { + if (status->vit == b_status->vit +#ifdef RENEWAL + && status->agi == b_status->agi +#endif + ) + status->def2 = status_calc_def2(bl, sc, b_status->def2); + else + status->def2 = status_calc_def2(bl, sc, b_status->def2 +#ifdef RENEWAL + + (int)( ((float)status->vit/2 + (float)b_status->vit/2) + ((float)status->agi/5 + (float)b_status->agi/5) ) +#else + + (status->vit - b_status->vit) +#endif + ); + } + + if(flag&SCB_MDEF) + { + status->mdef = status_calc_mdef(bl, sc, b_status->mdef); + + if( bl->type&BL_HOM ) + status->mdef += (status->int_/5 - b_status->int_/5); + } + + if(flag&SCB_MDEF2) { + if (status->int_ == b_status->int_ && status->vit == b_status->vit +#ifdef RENEWAL + && status->dex == b_status->dex +#endif + ) + status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2); + else + status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2 +(status->int_ - b_status->int_) +#ifdef RENEWAL + + (int)( ((float)status->dex/5 - (float)b_status->dex/5) + ((float)status->vit/5 + (float)b_status->vit/5) ) +#else + + ((status->vit - b_status->vit)>>1) +#endif + ); + } + + if(flag&SCB_SPEED) { + struct unit_data *ud = unit_bl2ud(bl); + status->speed = status_calc_speed(bl, sc, b_status->speed); + + //Re-walk to adjust speed (we do not check if walktimer != INVALID_TIMER + //because if you step on something while walking, the moment this + //piece of code triggers the walk-timer is set on INVALID_TIMER) [Skotlex] + if (ud) + ud->state.change_walk_target = ud->state.speed_changed = 1; + + if( bl->type&BL_PC && status->speed < battle_config.max_walk_speed ) + status->speed = battle_config.max_walk_speed; + + if( bl->type&BL_HOM && battle_config.hom_setting&0x8 && ((TBL_HOM*)bl)->master) + status->speed = status_get_speed(&((TBL_HOM*)bl)->master->bl); + + + } + + if(flag&SCB_CRI && b_status->cri) { + if (status->luk == b_status->luk) + status->cri = status_calc_critical(bl, sc, b_status->cri); + else + status->cri = status_calc_critical(bl, sc, b_status->cri + 3*(status->luk - b_status->luk)); + /** + * after status_calc_critical so the bonus is applied despite if you have or not a sc bugreport:5240 + **/ + if( bl->type == BL_PC && ((TBL_PC*)bl)->status.weapon == W_KATAR ) + status->cri <<= 1; + + } + + if(flag&SCB_FLEE2 && b_status->flee2) { + if (status->luk == b_status->luk) + status->flee2 = status_calc_flee2(bl, sc, b_status->flee2); + else + status->flee2 = status_calc_flee2(bl, sc, b_status->flee2 +(status->luk - b_status->luk)); + } + + if(flag&SCB_ATK_ELE) { + status->rhw.ele = status_calc_attack_element(bl, sc, b_status->rhw.ele); + if (sd) sd->state.lr_flag = 1; + status->lhw.ele = status_calc_attack_element(bl, sc, b_status->lhw.ele); + if (sd) sd->state.lr_flag = 0; + } + + if(flag&SCB_DEF_ELE) { + status->def_ele = status_calc_element(bl, sc, b_status->def_ele); + status->ele_lv = status_calc_element_lv(bl, sc, b_status->ele_lv); + } + + if(flag&SCB_MODE) + { + status->mode = status_calc_mode(bl, sc, b_status->mode); + //Since mode changed, reset their state. + if (!(status->mode&MD_CANATTACK)) + unit_stop_attack(bl); + if (!(status->mode&MD_CANMOVE)) + unit_stop_walking(bl,1); + } + +// No status changes alter these yet. +// if(flag&SCB_SIZE) +// if(flag&SCB_RACE) +// if(flag&SCB_RANGE) + + if(flag&SCB_MAXHP) { + if( bl->type&BL_PC ) + { + status->max_hp = status_base_pc_maxhp(sd,status); + status->max_hp += b_status->max_hp - sd->status.max_hp; + + status->max_hp = status_calc_maxhp(bl, sc, status->max_hp); + + if( status->max_hp > (unsigned int)battle_config.max_hp ) + status->max_hp = (unsigned int)battle_config.max_hp; + } + else + { + status->max_hp = status_calc_maxhp(bl, sc, b_status->max_hp); + } + + if( status->hp > status->max_hp ) //FIXME: Should perhaps a status_zap should be issued? + { + status->hp = status->max_hp; + if( sd ) clif_updatestatus(sd,SP_HP); + } + } + + if(flag&SCB_MAXSP) { + if( bl->type&BL_PC ) + { + status->max_sp = status_base_pc_maxsp(sd,status); + status->max_sp += b_status->max_sp - sd->status.max_sp; + + status->max_sp = status_calc_maxsp(&sd->bl, &sd->sc, status->max_sp); + + if( status->max_sp > (unsigned int)battle_config.max_sp ) + status->max_sp = (unsigned int)battle_config.max_sp; + } + else + { + status->max_sp = status_calc_maxsp(bl, sc, b_status->max_sp); + } + + if( status->sp > status->max_sp ) + { + status->sp = status->max_sp; + if( sd ) clif_updatestatus(sd,SP_SP); + } + } + + if(flag&SCB_MATK) { +#ifndef RENEWAL + status->matk_min = status_base_matk_min(status) + (sd?sd->bonus.ematk:0); + status->matk_max = status_base_matk_max(status) + (sd?sd->bonus.ematk:0); +#else + /** + * RE MATK Formula (from irowiki:http://irowiki.org/wiki/MATK) + * MATK = (sMATK + wMATK + eMATK) * Multiplicative Modifiers + **/ + status->matk_min = status->matk_max = status_base_matk(status, status_get_lv(bl)); + if( bl->type&BL_PC ){ + // Any +MATK you get from skills and cards, including cards in weapon, is added here. + if( sd->bonus.ematk > 0 ){ + status->matk_max += sd->bonus.ematk; + status->matk_min += sd->bonus.ematk; + } + status->matk_min = status_calc_ematk(bl, sc, status->matk_min); + status->matk_max = status_calc_ematk(bl, sc, status->matk_max); + //This is the only portion in MATK that varies depending on the weapon level and refinement rate. + if( status->rhw.matk > 0 ){ + int wMatk = status->rhw.matk; + int variance = wMatk * status->rhw.wlv / 10; + status->matk_min += wMatk - variance; + status->matk_max += wMatk + variance; + } + } +#endif + if (bl->type&BL_PC && sd->matk_rate != 100) { + status->matk_max = status->matk_max * sd->matk_rate/100; + status->matk_min = status->matk_min * sd->matk_rate/100; + } + + status->matk_min = status_calc_matk(bl, sc, status->matk_min); + status->matk_max = status_calc_matk(bl, sc, status->matk_max); + + if ((bl->type&BL_HOM && battle_config.hom_setting&0x20) //Hom Min Matk is always the same as Max Matk + || sc->data[SC_RECOGNIZEDSPELL]) + status->matk_min = status->matk_max; + +#ifdef RENEWAL + if( sd && sd->right_weapon.overrefine > 0){ + status->matk_min++; + status->matk_max += sd->right_weapon.overrefine - 1; + } +#endif + + } + + if(flag&SCB_ASPD) { + int amotion; + if( bl->type&BL_PC ) + { + amotion = status_base_amotion_pc(sd,status); +#ifndef RENEWAL_ASPD + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; +#else + // aspd = baseaspd + floor(sqrt((agi^2/2) + (dex^2/5))/4 + (potskillbonus*agi/200)) + amotion -= (int)(sqrt( (pow(status->agi, 2) / 2) + (pow(status->dex, 2) / 5) ) / 4 + (status_calc_aspd(bl, sc, 1) * status->agi / 200)) * 10; + + if( (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) != 0 ) // RE ASPD percertage modifier + amotion -= ( amotion - ((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd) ) + * (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) / 100; + + if(status->aspd_rate != 1000) // absolute percentage modifier + amotion = ( 200 - (200-amotion/10) * status->aspd_rate / 1000 ) * 10; +#endif + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); + + status->adelay = 2*status->amotion; + } + else + if( bl->type&BL_HOM ) + { + amotion = (1000 -4*status->agi -status->dex) * ((TBL_HOM*)bl)->homunculusDB->baseASPD/1000; + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; + + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion,battle_config.max_aspd,2000); + + status->adelay = status->amotion; + } + else // mercenary and mobs + { + amotion = b_status->amotion; + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; + + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion, battle_config.monster_max_aspd, 2000); + + temp = b_status->adelay*status->aspd_rate/1000; + status->adelay = cap_value(temp, battle_config.monster_max_aspd*2, 4000); + } + } + + if(flag&SCB_DSPD) { + int dmotion; + if( bl->type&BL_PC ) + { + if (b_status->agi == status->agi) + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + else { + dmotion = 800-status->agi*4; + status->dmotion = cap_value(dmotion, 400, 800); + if(battle_config.pc_damage_delay_rate != 100) + status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; + //It's safe to ignore b_status->dmotion since no bonus affects it. + status->dmotion = status_calc_dmotion(bl, sc, status->dmotion); + } + } + else + if( bl->type&BL_HOM ) + { + dmotion = 800-status->agi*4; + status->dmotion = cap_value(dmotion, 400, 800); + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + } + else // mercenary and mobs + { + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + } + } + + if(flag&(SCB_VIT|SCB_MAXHP|SCB_INT|SCB_MAXSP) && bl->type&BL_REGEN) + status_calc_regen(bl, status, status_get_regen_data(bl)); + + if(flag&SCB_REGEN && bl->type&BL_REGEN) + status_calc_regen_rate(bl, status_get_regen_data(bl), sc); +} +/// Recalculates parts of an object's base status and battle status according to the specified flags. +/// Also sends updates to the client wherever applicable. +/// @param flag bitfield of values from enum scb_flag +/// @param first if true, will cause status_calc_* functions to run their base status initialization code +void status_calc_bl_(struct block_list* bl, enum scb_flag flag, bool first) +{ + struct status_data b_status; // previous battle status + struct status_data* status; // pointer to current battle status + + // remember previous values + status = status_get_status_data(bl); + memcpy(&b_status, status, sizeof(struct status_data)); + + if( flag&SCB_BASE ) {// calculate the object's base status too + switch( bl->type ) { + case BL_PC: status_calc_pc_(BL_CAST(BL_PC,bl), first); break; + case BL_MOB: status_calc_mob_(BL_CAST(BL_MOB,bl), first); break; + case BL_PET: status_calc_pet_(BL_CAST(BL_PET,bl), first); break; + case BL_HOM: status_calc_homunculus_(BL_CAST(BL_HOM,bl), first); break; + case BL_MER: status_calc_mercenary_(BL_CAST(BL_MER,bl), first); break; + case BL_ELEM: status_calc_elemental_(BL_CAST(BL_ELEM,bl), first); break; + case BL_NPC: status_calc_npc_(BL_CAST(BL_NPC,bl), first); break; + } + } + + if( bl->type == BL_PET ) + return; // pets are not affected by statuses + + if( first && bl->type == BL_MOB ) + return; // assume there will be no statuses active + + status_calc_bl_main(bl, flag); + + if( first && bl->type == BL_HOM ) + return; // client update handled by caller + + // compare against new values and send client updates + if( bl->type == BL_PC ) + { + TBL_PC* sd = BL_CAST(BL_PC, bl); + if(b_status.str != status->str) + clif_updatestatus(sd,SP_STR); + if(b_status.agi != status->agi) + clif_updatestatus(sd,SP_AGI); + if(b_status.vit != status->vit) + clif_updatestatus(sd,SP_VIT); + if(b_status.int_ != status->int_) + clif_updatestatus(sd,SP_INT); + if(b_status.dex != status->dex) + clif_updatestatus(sd,SP_DEX); + if(b_status.luk != status->luk) + clif_updatestatus(sd,SP_LUK); + if(b_status.hit != status->hit) + clif_updatestatus(sd,SP_HIT); + if(b_status.flee != status->flee) + clif_updatestatus(sd,SP_FLEE1); + if(b_status.amotion != status->amotion) + clif_updatestatus(sd,SP_ASPD); + if(b_status.speed != status->speed) + clif_updatestatus(sd,SP_SPEED); + + if(b_status.batk != status->batk +#ifndef RENEWAL + || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk +#endif + ) + clif_updatestatus(sd,SP_ATK1); + + if(b_status.def != status->def){ + clif_updatestatus(sd,SP_DEF1); +#ifdef RENEWAL + clif_updatestatus(sd,SP_DEF2); +#endif + } + + if(b_status.rhw.atk2 != status->rhw.atk2 || b_status.lhw.atk2 != status->lhw.atk2 +#ifdef RENEWAL + || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk +#endif + ) + clif_updatestatus(sd,SP_ATK2); + + if(b_status.def2 != status->def2){ + clif_updatestatus(sd,SP_DEF2); +#ifdef RENEWAL + clif_updatestatus(sd,SP_DEF1); +#endif + } + if(b_status.flee2 != status->flee2) + clif_updatestatus(sd,SP_FLEE2); + if(b_status.cri != status->cri) + clif_updatestatus(sd,SP_CRITICAL); +#ifndef RENEWAL + if(b_status.matk_max != status->matk_max) + clif_updatestatus(sd,SP_MATK1); + if(b_status.matk_min != status->matk_min) + clif_updatestatus(sd,SP_MATK2); +#else + if(b_status.matk_max != status->matk_max || b_status.matk_min != status->matk_min){ + clif_updatestatus(sd,SP_MATK2); + clif_updatestatus(sd,SP_MATK1); + } +#endif + if(b_status.mdef != status->mdef){ + clif_updatestatus(sd,SP_MDEF1); +#ifdef RENEWAL + clif_updatestatus(sd,SP_MDEF2); +#endif + } + if(b_status.mdef2 != status->mdef2){ + clif_updatestatus(sd,SP_MDEF2); +#ifdef RENEWAL + clif_updatestatus(sd,SP_MDEF1); +#endif + } + if(b_status.rhw.range != status->rhw.range) + clif_updatestatus(sd,SP_ATTACKRANGE); + if(b_status.max_hp != status->max_hp) + clif_updatestatus(sd,SP_MAXHP); + if(b_status.max_sp != status->max_sp) + clif_updatestatus(sd,SP_MAXSP); + if(b_status.hp != status->hp) + clif_updatestatus(sd,SP_HP); + if(b_status.sp != status->sp) + clif_updatestatus(sd,SP_SP); + } else if( bl->type == BL_HOM ) { + TBL_HOM* hd = BL_CAST(BL_HOM, bl); + if( hd->master && memcmp(&b_status, status, sizeof(struct status_data)) != 0 ) + clif_hominfo(hd->master,hd,0); + } else if( bl->type == BL_MER ) { + TBL_MER* md = BL_CAST(BL_MER, bl); + if( b_status.rhw.atk != status->rhw.atk || b_status.rhw.atk2 != status->rhw.atk2 ) + clif_mercenary_updatestatus(md->master, SP_ATK1); + if( b_status.matk_max != status->matk_max ) + clif_mercenary_updatestatus(md->master, SP_MATK1); + if( b_status.hit != status->hit ) + clif_mercenary_updatestatus(md->master, SP_HIT); + if( b_status.cri != status->cri ) + clif_mercenary_updatestatus(md->master, SP_CRITICAL); + if( b_status.def != status->def ) + clif_mercenary_updatestatus(md->master, SP_DEF1); + if( b_status.mdef != status->mdef ) + clif_mercenary_updatestatus(md->master, SP_MDEF1); + if( b_status.flee != status->flee ) + clif_mercenary_updatestatus(md->master, SP_MERCFLEE); + if( b_status.amotion != status->amotion ) + clif_mercenary_updatestatus(md->master, SP_ASPD); + if( b_status.max_hp != status->max_hp ) + clif_mercenary_updatestatus(md->master, SP_MAXHP); + if( b_status.max_sp != status->max_sp ) + clif_mercenary_updatestatus(md->master, SP_MAXSP); + if( b_status.hp != status->hp ) + clif_mercenary_updatestatus(md->master, SP_HP); + if( b_status.sp != status->sp ) + clif_mercenary_updatestatus(md->master, SP_SP); + } else if( bl->type == BL_ELEM ) { + TBL_ELEM* ed = BL_CAST(BL_ELEM, bl); + if( b_status.max_hp != status->max_hp ) + clif_elemental_updatestatus(ed->master, SP_MAXHP); + if( b_status.max_sp != status->max_sp ) + clif_elemental_updatestatus(ed->master, SP_MAXSP); + if( b_status.hp != status->hp ) + clif_elemental_updatestatus(ed->master, SP_HP); + if( b_status.sp != status->sp ) + clif_mercenary_updatestatus(ed->master, SP_SP); + } +} + +/*========================================== + * Apply shared stat mods from status changes [DracoRPG] + *------------------------------------------*/ +static unsigned short status_calc_str(struct block_list *bl, struct status_change *sc, int str) +{ + if(!sc || !sc->count) + return cap_value(str,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + str -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(str,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && str < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + str += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCSTR]) + str += sc->data[SC_INCSTR]->val1; + if(sc->data[SC_STRFOOD]) + str += sc->data[SC_STRFOOD]->val1; + if(sc->data[SC_FOOD_STR_CASH]) + str += sc->data[SC_FOOD_STR_CASH]->val1; + if(sc->data[SC_BATTLEORDERS]) + str += 5; + if(sc->data[SC_LEADERSHIP]) + str += sc->data[SC_LEADERSHIP]->val1; + if(sc->data[SC_LOUD]) + str += 4; + if(sc->data[SC_TRUESIGHT]) + str += 5; + if(sc->data[SC_SPURT]) + str += 10; + if(sc->data[SC_NEN]) + str += sc->data[SC_NEN]->val1; + if(sc->data[SC_BLESSING]){ + if(sc->data[SC_BLESSING]->val2) + str += sc->data[SC_BLESSING]->val2; + else + str >>= 1; + } + if(sc->data[SC_MARIONETTE]) + str -= ((sc->data[SC_MARIONETTE]->val3)>>16)&0xFF; + if(sc->data[SC_MARIONETTE2]) + str += ((sc->data[SC_MARIONETTE2]->val3)>>16)&0xFF; + if(sc->data[SC_GIANTGROWTH]) + str += 30; + if(sc->data[SC_SAVAGE_STEAK]) + str += sc->data[SC_SAVAGE_STEAK]->val1; + if(sc->data[SC_INSPIRATION]) + str += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + str -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + str -= sc->data[SC_KYOUGAKU]->val2; + + return (unsigned short)cap_value(str,0,USHRT_MAX); +} + +static unsigned short status_calc_agi(struct block_list *bl, struct status_change *sc, int agi) +{ + if(!sc || !sc->count) + return cap_value(agi,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + agi -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(agi,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && agi < 50) + return 50; + if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) + agi += (agi-sc->data[SC_CONCENTRATE]->val3)*sc->data[SC_CONCENTRATE]->val2/100; + if(sc->data[SC_INCALLSTATUS]) + agi += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCAGI]) + agi += sc->data[SC_INCAGI]->val1; + if(sc->data[SC_AGIFOOD]) + agi += sc->data[SC_AGIFOOD]->val1; + if(sc->data[SC_FOOD_AGI_CASH]) + agi += sc->data[SC_FOOD_AGI_CASH]->val1; + if(sc->data[SC_SOULCOLD]) + agi += sc->data[SC_SOULCOLD]->val1; + if(sc->data[SC_TRUESIGHT]) + agi += 5; + if(sc->data[SC_INCREASEAGI]) + agi += sc->data[SC_INCREASEAGI]->val2; + if(sc->data[SC_INCREASING]) + agi += 4; // added based on skill updates [Reddozen] + if(sc->data[SC_DECREASEAGI]) + agi -= sc->data[SC_DECREASEAGI]->val2; + if(sc->data[SC_QUAGMIRE]) + agi -= sc->data[SC_QUAGMIRE]->val2; + if(sc->data[SC_SUITON] && sc->data[SC_SUITON]->val3) + agi -= sc->data[SC_SUITON]->val2; + if(sc->data[SC_MARIONETTE]) + agi -= ((sc->data[SC_MARIONETTE]->val3)>>8)&0xFF; + if(sc->data[SC_MARIONETTE2]) + agi += ((sc->data[SC_MARIONETTE2]->val3)>>8)&0xFF; + if(sc->data[SC_ADORAMUS]) + agi -= sc->data[SC_ADORAMUS]->val2; + if(sc->data[SC_DROCERA_HERB_STEAMED]) + agi += sc->data[SC_DROCERA_HERB_STEAMED]->val1; + if(sc->data[SC_INSPIRATION]) + agi += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + agi -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + agi -= sc->data[SC_KYOUGAKU]->val2; + + return (unsigned short)cap_value(agi,0,USHRT_MAX); +} + +static unsigned short status_calc_vit(struct block_list *bl, struct status_change *sc, int vit) +{ + if(!sc || !sc->count) + return cap_value(vit,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + vit -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(vit,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && vit < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + vit += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCVIT]) + vit += sc->data[SC_INCVIT]->val1; + if(sc->data[SC_VITFOOD]) + vit += sc->data[SC_VITFOOD]->val1; + if(sc->data[SC_FOOD_VIT_CASH]) + vit += sc->data[SC_FOOD_VIT_CASH]->val1; + if(sc->data[SC_CHANGE]) + vit += sc->data[SC_CHANGE]->val2; + if(sc->data[SC_GLORYWOUNDS]) + vit += sc->data[SC_GLORYWOUNDS]->val1; + if(sc->data[SC_TRUESIGHT]) + vit += 5; + if(sc->data[SC_MARIONETTE]) + vit -= sc->data[SC_MARIONETTE]->val3&0xFF; + if(sc->data[SC_MARIONETTE2]) + vit += sc->data[SC_MARIONETTE2]->val3&0xFF; + if(sc->data[SC_LAUDAAGNUS]) + vit += 4 + sc->data[SC_LAUDAAGNUS]->val1; + if(sc->data[SC_MINOR_BBQ]) + vit += sc->data[SC_MINOR_BBQ]->val1; + if(sc->data[SC_INSPIRATION]) + vit += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + vit -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + vit -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC_STRIPARMOR]) + vit -= vit * sc->data[SC_STRIPARMOR]->val2/100; + + return (unsigned short)cap_value(vit,0,USHRT_MAX); +} + +static unsigned short status_calc_int(struct block_list *bl, struct status_change *sc, int int_) +{ + if(!sc || !sc->count) + return cap_value(int_,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + int_ -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(int_,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && int_ < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + int_ += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCINT]) + int_ += sc->data[SC_INCINT]->val1; + if(sc->data[SC_INTFOOD]) + int_ += sc->data[SC_INTFOOD]->val1; + if(sc->data[SC_FOOD_INT_CASH]) + int_ += sc->data[SC_FOOD_INT_CASH]->val1; + if(sc->data[SC_CHANGE]) + int_ += sc->data[SC_CHANGE]->val3; + if(sc->data[SC_BATTLEORDERS]) + int_ += 5; + if(sc->data[SC_TRUESIGHT]) + int_ += 5; + if(sc->data[SC_BLESSING]){ + if (sc->data[SC_BLESSING]->val2) + int_ += sc->data[SC_BLESSING]->val2; + else + int_ >>= 1; + } + if(sc->data[SC_NEN]) + int_ += sc->data[SC_NEN]->val1; + if(sc->data[SC_MARIONETTE]) + int_ -= ((sc->data[SC_MARIONETTE]->val4)>>16)&0xFF; + if(sc->data[SC_MARIONETTE2]) + int_ += ((sc->data[SC_MARIONETTE2]->val4)>>16)&0xFF; + if(sc->data[SC_MANDRAGORA]) + int_ -= 5 + 5 * sc->data[SC_MANDRAGORA]->val1; + if(sc->data[SC_COCKTAIL_WARG_BLOOD]) + int_ += sc->data[SC_COCKTAIL_WARG_BLOOD]->val1; + if(sc->data[SC_INSPIRATION]) + int_ += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + int_ -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + int_ -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC_STRIPHELM]) + int_ -= int_ * sc->data[SC_STRIPHELM]->val2/100; + if(sc->data[SC__STRIPACCESSORY]) + int_ -= int_ * sc->data[SC__STRIPACCESSORY]->val2 / 100; + + return (unsigned short)cap_value(int_,0,USHRT_MAX); +} + +static unsigned short status_calc_dex(struct block_list *bl, struct status_change *sc, int dex) +{ + if(!sc || !sc->count) + return cap_value(dex,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + dex -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(dex,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && dex < 50) + return 50; + if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) + dex += (dex-sc->data[SC_CONCENTRATE]->val4)*sc->data[SC_CONCENTRATE]->val2/100; + if(sc->data[SC_INCALLSTATUS]) + dex += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCDEX]) + dex += sc->data[SC_INCDEX]->val1; + if(sc->data[SC_DEXFOOD]) + dex += sc->data[SC_DEXFOOD]->val1; + if(sc->data[SC_FOOD_DEX_CASH]) + dex += sc->data[SC_FOOD_DEX_CASH]->val1; + if(sc->data[SC_BATTLEORDERS]) + dex += 5; + if(sc->data[SC_HAWKEYES]) + dex += sc->data[SC_HAWKEYES]->val1; + if(sc->data[SC_TRUESIGHT]) + dex += 5; + if(sc->data[SC_QUAGMIRE]) + dex -= sc->data[SC_QUAGMIRE]->val2; + if(sc->data[SC_BLESSING]){ + if (sc->data[SC_BLESSING]->val2) + dex += sc->data[SC_BLESSING]->val2; + else + dex >>= 1; + } + if(sc->data[SC_INCREASING]) + dex += 4; // added based on skill updates [Reddozen] + if(sc->data[SC_MARIONETTE]) + dex -= ((sc->data[SC_MARIONETTE]->val4)>>8)&0xFF; + if(sc->data[SC_MARIONETTE2]) + dex += ((sc->data[SC_MARIONETTE2]->val4)>>8)&0xFF; + if(sc->data[SC_SIROMA_ICE_TEA]) + dex += sc->data[SC_SIROMA_ICE_TEA]->val1; + if(sc->data[SC_INSPIRATION]) + dex += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + dex -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + dex -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC__STRIPACCESSORY]) + dex -= dex * sc->data[SC__STRIPACCESSORY]->val2 / 100; + + return (unsigned short)cap_value(dex,0,USHRT_MAX); +} + +static unsigned short status_calc_luk(struct block_list *bl, struct status_change *sc, int luk) +{ + if(!sc || !sc->count) + return cap_value(luk,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + luk -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(luk,0,USHRT_MAX); + } + if(sc->data[SC_CURSE]) + return 0; + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && luk < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + luk += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCLUK]) + luk += sc->data[SC_INCLUK]->val1; + if(sc->data[SC_LUKFOOD]) + luk += sc->data[SC_LUKFOOD]->val1; + if(sc->data[SC_FOOD_LUK_CASH]) + luk += sc->data[SC_FOOD_LUK_CASH]->val1; + if(sc->data[SC_TRUESIGHT]) + luk += 5; + if(sc->data[SC_GLORIA]) + luk += 30; + if(sc->data[SC_MARIONETTE]) + luk -= sc->data[SC_MARIONETTE]->val4&0xFF; + if(sc->data[SC_MARIONETTE2]) + luk += sc->data[SC_MARIONETTE2]->val4&0xFF; + if(sc->data[SC_PUTTI_TAILS_NOODLES]) + luk += sc->data[SC_PUTTI_TAILS_NOODLES]->val1; + if(sc->data[SC_INSPIRATION]) + luk += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + luk -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + luk -= sc->data[SC_KYOUGAKU]->val2; + if(sc->data[SC_LAUDARAMUS]) + luk += 4 + sc->data[SC_LAUDARAMUS]->val1; + + if(sc->data[SC__STRIPACCESSORY]) + luk -= luk * sc->data[SC__STRIPACCESSORY]->val2 / 100; + if(sc->data[SC_BANANA_BOMB]) + luk -= luk * sc->data[SC_BANANA_BOMB]->val1 / 100; + + return (unsigned short)cap_value(luk,0,USHRT_MAX); +} + +static unsigned short status_calc_batk(struct block_list *bl, struct status_change *sc, int batk) +{ + if(!sc || !sc->count) + return cap_value(batk,0,USHRT_MAX); + + if(sc->data[SC_ATKPOTION]) + batk += sc->data[SC_ATKPOTION]->val1; + if(sc->data[SC_BATKFOOD]) + batk += sc->data[SC_BATKFOOD]->val1; + if(sc->data[SC_GATLINGFEVER]) + batk += sc->data[SC_GATLINGFEVER]->val3; + if(sc->data[SC_MADNESSCANCEL]) + batk += 100; + if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) + batk += 50; + if(bl->type == BL_ELEM + && ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1)) + ) + batk += batk / 5; + if(sc->data[SC_FULL_SWING_K]) + batk += sc->data[SC_FULL_SWING_K]->val1; + if(sc->data[SC_ODINS_POWER]) + batk += 70; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_element(bl) == ELE_WATER) //water type + batk /= 2; + } + if(sc->data[SC_PYROCLASTIC]) + batk += sc->data[SC_PYROCLASTIC]->val2; + if (sc->data[SC_ANGRIFFS_MODUS]) + batk += sc->data[SC_ANGRIFFS_MODUS]->val2; + + if(sc->data[SC_INCATKRATE]) + batk += batk * sc->data[SC_INCATKRATE]->val1/100; + if(sc->data[SC_PROVOKE]) + batk += batk * sc->data[SC_PROVOKE]->val3/100; + if(sc->data[SC_CONCENTRATION]) + batk += batk * sc->data[SC_CONCENTRATION]->val2/100; + if(sc->data[SC_SKE]) + batk += batk * 3; + if(sc->data[SC_BLOODLUST]) + batk += batk * sc->data[SC_BLOODLUST]->val2/100; + if(sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST) + batk -= batk * 25/100; + if(sc->data[SC_CURSE]) + batk -= batk * 25/100; +//Curse shouldn't effect on this? <- Curse OR Bleeding?? +// if(sc->data[SC_BLEEDING]) +// batk -= batk * 25/100; + if(sc->data[SC_FLEET]) + batk += batk * sc->data[SC_FLEET]->val3/100; + if(sc->data[SC__ENERVATION]) + batk -= batk * sc->data[SC__ENERVATION]->val2 / 100; + if(sc->data[SC_RUSHWINDMILL]) + batk += batk * sc->data[SC_RUSHWINDMILL]->val2/100; + if(sc->data[SC_SATURDAYNIGHTFEVER]) + batk += 100 * sc->data[SC_SATURDAYNIGHTFEVER]->val1; + if(sc->data[SC_MELODYOFSINK]) + batk -= batk * sc->data[SC_MELODYOFSINK]->val3/100; + if(sc->data[SC_BEYONDOFWARCRY]) + batk += batk * sc->data[SC_BEYONDOFWARCRY]->val3/100; + if( sc->data[SC_ZANGETSU] ) + batk += batk * sc->data[SC_ZANGETSU]->val2 / 100; + + return (unsigned short)cap_value(batk,0,USHRT_MAX); +} + +static unsigned short status_calc_watk(struct block_list *bl, struct status_change *sc, int watk) +{ + if(!sc || !sc->count) + return cap_value(watk,0,USHRT_MAX); + + if(sc->data[SC_IMPOSITIO]) + watk += sc->data[SC_IMPOSITIO]->val2; + if(sc->data[SC_WATKFOOD]) + watk += sc->data[SC_WATKFOOD]->val1; + if(sc->data[SC_DRUMBATTLE]) + watk += sc->data[SC_DRUMBATTLE]->val2; + if(sc->data[SC_VOLCANO]) + watk += sc->data[SC_VOLCANO]->val2; + if(sc->data[SC_MERC_ATKUP]) + watk += sc->data[SC_MERC_ATKUP]->val2; + if(sc->data[SC_FIGHTINGSPIRIT]) + watk += sc->data[SC_FIGHTINGSPIRIT]->val1; + if(sc->data[SC_STRIKING]) + watk += sc->data[SC_STRIKING]->val2; + if(sc->data[SC_SHIELDSPELL_DEF] && sc->data[SC_SHIELDSPELL_DEF]->val1 == 3) + watk += sc->data[SC_SHIELDSPELL_DEF]->val2; + if(sc->data[SC_INSPIRATION]) + watk += sc->data[SC_INSPIRATION]->val2; + if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) + watk += (10 + 10 * sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); + if( sc->data[SC_TROPIC_OPTION] ) + watk += sc->data[SC_TROPIC_OPTION]->val2; + if( sc->data[SC_HEATER_OPTION] ) + watk += sc->data[SC_HEATER_OPTION]->val2; + if( sc->data[SC_WATER_BARRIER] ) + watk -= sc->data[SC_WATER_BARRIER]->val3; + if( sc->data[SC_PYROTECHNIC_OPTION] ) + watk += sc->data[SC_PYROTECHNIC_OPTION]->val2; + if(sc->data[SC_NIBELUNGEN]) { + if (bl->type != BL_PC) + watk += sc->data[SC_NIBELUNGEN]->val2; + else { + #ifndef RENEWAL + TBL_PC *sd = (TBL_PC*)bl; + int index = sd->equip_index[sd->state.lr_flag?EQI_HAND_L:EQI_HAND_R]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + #endif + watk += sc->data[SC_NIBELUNGEN]->val2; + } + } + + if(sc->data[SC_INCATKRATE]) + watk += watk * sc->data[SC_INCATKRATE]->val1/100; + if(sc->data[SC_PROVOKE]) + watk += watk * sc->data[SC_PROVOKE]->val3/100; + if(sc->data[SC_CONCENTRATION]) + watk += watk * sc->data[SC_CONCENTRATION]->val2/100; + if(sc->data[SC_SKE]) + watk += watk * 3; + if(sc->data[SC__ENERVATION]) + watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; + if(sc->data[SC_FLEET]) + watk += watk * sc->data[SC_FLEET]->val3/100; + if(sc->data[SC_CURSE]) + watk -= watk * 25/100; + if(sc->data[SC_STRIPWEAPON]) + watk -= watk * sc->data[SC_STRIPWEAPON]->val2/100; + if(sc->data[SC__ENERVATION]) + watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; + if((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + ) + watk += watk / 10; + if( sc && sc->data[SC_TIDAL_WEAPON] ) + watk += watk * sc->data[SC_TIDAL_WEAPON]->val2 / 100; + if(sc->data[SC_ANGRIFFS_MODUS]) + watk += watk * sc->data[SC_ANGRIFFS_MODUS]->val2/100; +#ifdef RENEWAL_EDP + if( sc->data[SC_EDP] ) + watk = watk * (100 + sc->data[SC_EDP]->val1 * 80) / 100; +#endif + + return (unsigned short)cap_value(watk,0,USHRT_MAX); +} +#ifdef RENEWAL +static unsigned short status_calc_ematk(struct block_list *bl, struct status_change *sc, int matk) +{ + + if (!sc || !sc->count) + return cap_value(matk,0,USHRT_MAX); + if (sc->data[SC_MATKPOTION]) + matk += sc->data[SC_MATKPOTION]->val1; + if (sc->data[SC_MATKFOOD]) + matk += sc->data[SC_MATKFOOD]->val1; + if(sc->data[SC_MANA_PLUS]) + matk += sc->data[SC_MANA_PLUS]->val1; + if(sc->data[SC_AQUAPLAY_OPTION]) + matk += sc->data[SC_AQUAPLAY_OPTION]->val2; + if(sc->data[SC_CHILLY_AIR_OPTION]) + matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; + if(sc->data[SC_WATER_BARRIER]) + matk -= sc->data[SC_WATER_BARRIER]->val3; + if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) + matk += 50; + if(sc->data[SC_ODINS_POWER]) + matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 + if(sc->data[SC_IZAYOI]) + matk += 50 * sc->data[SC_IZAYOI]->val1; + return (unsigned short)cap_value(matk,0,USHRT_MAX); +} +#endif +static unsigned short status_calc_matk(struct block_list *bl, struct status_change *sc, int matk) +{ + if(!sc || !sc->count) + return cap_value(matk,0,USHRT_MAX); +#ifndef RENEWAL + // take note fixed value first before % modifiers + if (sc->data[SC_MATKPOTION]) + matk += sc->data[SC_MATKPOTION]->val1; + if (sc->data[SC_MATKFOOD]) + matk += sc->data[SC_MATKFOOD]->val1; + if (sc->data[SC_MANA_PLUS]) + matk += sc->data[SC_MANA_PLUS]->val1; + if (sc->data[SC_AQUAPLAY_OPTION]) + matk += sc->data[SC_AQUAPLAY_OPTION]->val2; + if (sc->data[SC_CHILLY_AIR_OPTION]) + matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; + if (sc->data[SC_WATER_BARRIER]) + matk -= sc->data[SC_WATER_BARRIER]->val3; + if (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) + matk += 50; + if (sc->data[SC_ODINS_POWER]) + matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 + if (sc->data[SC_IZAYOI]) + matk += 50 * sc->data[SC_IZAYOI]->val1; +#endif + if (sc->data[SC_MAGICPOWER]) + matk += matk * sc->data[SC_MAGICPOWER]->val3/100; + if (sc->data[SC_MINDBREAKER]) + matk += matk * sc->data[SC_MINDBREAKER]->val2/100; + if (sc->data[SC_INCMATKRATE]) + matk += matk * sc->data[SC_INCMATKRATE]->val1/100; + if (sc->data[SC_MOONLITSERENADE]) + matk += matk * sc->data[SC_MOONLITSERENADE]->val2/100; + if (sc->data[SC_MELODYOFSINK]) + matk += matk * sc->data[SC_MELODYOFSINK]->val3/100; + if (sc->data[SC_BEYONDOFWARCRY]) + matk -= matk * sc->data[SC_BEYONDOFWARCRY]->val3/100; + if( sc->data[SC_ZANGETSU] ) + matk += matk * sc->data[SC_ZANGETSU]->val2 / 100; + + return (unsigned short)cap_value(matk,0,USHRT_MAX); +} + +static signed short status_calc_critical(struct block_list *bl, struct status_change *sc, int critical) { + + if(!sc || !sc->count) + return cap_value(critical,10,SHRT_MAX); + + if (sc->data[SC_INCCRI]) + critical += sc->data[SC_INCCRI]->val2; + if (sc->data[SC_EXPLOSIONSPIRITS]) + critical += sc->data[SC_EXPLOSIONSPIRITS]->val2; + if (sc->data[SC_FORTUNE]) + critical += sc->data[SC_FORTUNE]->val2; + if (sc->data[SC_TRUESIGHT]) + critical += sc->data[SC_TRUESIGHT]->val2; + if(sc->data[SC_CLOAKING]) + critical += critical; + if(sc->data[SC_STRIKING]) + critical += sc->data[SC_STRIKING]->val1; +#ifdef RENEWAL + if (sc->data[SC_SPEARQUICKEN]) + critical += 3*sc->data[SC_SPEARQUICKEN]->val1*10; +#endif + + if(sc->data[SC__INVISIBILITY]) + critical += critical * sc->data[SC__INVISIBILITY]->val3 / 100; + if(sc->data[SC__UNLUCKY]) + critical -= critical * sc->data[SC__UNLUCKY]->val2 / 100; + + return (short)cap_value(critical,10,SHRT_MAX); +} + +static signed short status_calc_hit(struct block_list *bl, struct status_change *sc, int hit) +{ + + if(!sc || !sc->count) + return cap_value(hit,1,SHRT_MAX); + + if(sc->data[SC_INCHIT]) + hit += sc->data[SC_INCHIT]->val1; + if(sc->data[SC_HITFOOD]) + hit += sc->data[SC_HITFOOD]->val1; + if(sc->data[SC_TRUESIGHT]) + hit += sc->data[SC_TRUESIGHT]->val3; + if(sc->data[SC_HUMMING]) + hit += sc->data[SC_HUMMING]->val2; + if(sc->data[SC_CONCENTRATION]) + hit += sc->data[SC_CONCENTRATION]->val3; + if(sc->data[SC_INSPIRATION]) + hit += 5 * sc->data[SC_INSPIRATION]->val1; + if(sc->data[SC_ADJUSTMENT]) + hit -= 30; + if(sc->data[SC_INCREASING]) + hit += 20; // RockmanEXE; changed based on updated [Reddozen] + if(sc->data[SC_MERC_HITUP]) + hit += sc->data[SC_MERC_HITUP]->val2; + + if(sc->data[SC_INCHITRATE]) + hit += hit * sc->data[SC_INCHITRATE]->val1/100; + if(sc->data[SC_BLIND]) + hit -= hit * 25/100; + if(sc->data[SC__GROOMY]) + hit -= hit * sc->data[SC__GROOMY]->val3 / 100; + if(sc->data[SC_FEAR]) + hit -= hit * 20 / 100; + if (sc->data[SC_ASH]) + hit /= 2; + + return (short)cap_value(hit,1,SHRT_MAX); +} + +static signed short status_calc_flee(struct block_list *bl, struct status_change *sc, int flee) +{ + if( bl->type == BL_PC ) + { + if( map_flag_gvg(bl->m) ) + flee -= flee * battle_config.gvg_flee_penalty/100; + else if( map[bl->m].flag.battleground ) + flee -= flee * battle_config.bg_flee_penalty/100; + } + + if(!sc || !sc->count) + return cap_value(flee,1,SHRT_MAX); + + if(sc->data[SC_INCFLEE]) + flee += sc->data[SC_INCFLEE]->val1; + if(sc->data[SC_FLEEFOOD]) + flee += sc->data[SC_FLEEFOOD]->val1; + if(sc->data[SC_WHISTLE]) + flee += sc->data[SC_WHISTLE]->val2; + if(sc->data[SC_WINDWALK]) + flee += sc->data[SC_WINDWALK]->val2; + if(sc->data[SC_VIOLENTGALE]) + flee += sc->data[SC_VIOLENTGALE]->val2; + if(sc->data[SC_MOON_COMFORT]) //SG skill [Komurka] + flee += sc->data[SC_MOON_COMFORT]->val2; + if(sc->data[SC_CLOSECONFINE]) + flee += 10; + if (sc->data[SC_ANGRIFFS_MODUS]) + flee -= sc->data[SC_ANGRIFFS_MODUS]->val3; + if (sc->data[SC_OVERED_BOOST]) + flee = max(flee,sc->data[SC_OVERED_BOOST]->val2); + if(sc->data[SC_ADJUSTMENT]) + flee += 30; + if(sc->data[SC_SPEED]) + flee += 10 + sc->data[SC_SPEED]->val1 * 10; + if(sc->data[SC_GATLINGFEVER]) + flee -= sc->data[SC_GATLINGFEVER]->val4; + if(sc->data[SC_PARTYFLEE]) + flee += sc->data[SC_PARTYFLEE]->val1 * 10; + if(sc->data[SC_MERC_FLEEUP]) + flee += sc->data[SC_MERC_FLEEUP]->val2; + if( sc->data[SC_HALLUCINATIONWALK] ) + flee += sc->data[SC_HALLUCINATIONWALK]->val2; + if( sc->data[SC_WATER_BARRIER] ) + flee -= sc->data[SC_WATER_BARRIER]->val3; + if( sc->data[SC_MARSHOFABYSS] ) + flee -= (9 * sc->data[SC_MARSHOFABYSS]->val3 / 10 + sc->data[SC_MARSHOFABYSS]->val2 / 10) * (bl->type == BL_MOB ? 2 : 1); +#ifdef RENEWAL + if( sc->data[SC_SPEARQUICKEN] ) + flee += 2 * sc->data[SC_SPEARQUICKEN]->val1; +#endif + + if(sc->data[SC_INCFLEERATE]) + flee += flee * sc->data[SC_INCFLEERATE]->val1/100; + if(sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1) + flee -= flee * 50/100; + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + flee -= flee * 50/100; + if(sc->data[SC_BLIND]) + flee -= flee * 25/100; + if(sc->data[SC_FEAR]) + flee -= flee * 20 / 100; + if(sc->data[SC_PARALYSE]) + flee -= flee * 10 / 100; // 10% Flee reduction + if(sc->data[SC_INFRAREDSCAN]) + flee -= flee * 30 / 100; + if( sc->data[SC__LAZINESS] ) + flee -= flee * sc->data[SC__LAZINESS]->val3 / 100; + if( sc->data[SC_GLOOMYDAY] ) + flee -= flee * sc->data[SC_GLOOMYDAY]->val2 / 100; + if( sc->data[SC_SATURDAYNIGHTFEVER] ) + flee -= flee * (40 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; + if( sc->data[SC_WIND_STEP_OPTION] ) + flee += flee * sc->data[SC_WIND_STEP_OPTION]->val2 / 100; + if( sc->data[SC_ZEPHYR] ) + flee += flee * sc->data[SC_ZEPHYR]->val2 / 100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ //mob + if(status_get_element(bl) == ELE_WATER) //water type + flee /= 2; + } + + return (short)cap_value(flee,1,SHRT_MAX); +} + +static signed short status_calc_flee2(struct block_list *bl, struct status_change *sc, int flee2) +{ + if(!sc || !sc->count) + return cap_value(flee2,10,SHRT_MAX); + + if(sc->data[SC_INCFLEE2]) + flee2 += sc->data[SC_INCFLEE2]->val2; + if(sc->data[SC_WHISTLE]) + flee2 += sc->data[SC_WHISTLE]->val3*10; + if(sc->data[SC__UNLUCKY]) + flee2 -= flee2 * sc->data[SC__UNLUCKY]->val2 / 100; + + return (short)cap_value(flee2,10,SHRT_MAX); +} +static defType status_calc_def(struct block_list *bl, struct status_change *sc, int def) { + + if(!sc || !sc->count) + return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX); + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_SKA]) + return sc->data[SC_SKA]->val3; + if(sc->data[SC_BARRIER]) + return 100; + if(sc->data[SC_KEEPING]) + return 90; +#ifndef RENEWAL // does not provide 90 DEF in renewal mode + if(sc->data[SC_STEELBODY]) + return 90; +#endif + + if(sc->data[SC_ARMORCHANGE]) + def += sc->data[SC_ARMORCHANGE]->val2; + if(sc->data[SC_DRUMBATTLE]) + def += sc->data[SC_DRUMBATTLE]->val3; + if(sc->data[SC_DEFENCE]) //[orn] + def += sc->data[SC_DEFENCE]->val2 ; + if(sc->data[SC_INCDEFRATE]) + def += def * sc->data[SC_INCDEFRATE]->val1/100; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + def += 50; + if(sc->data[SC_ODINS_POWER]) + def -= 20; + if( sc->data[SC_ANGRIFFS_MODUS] ) + def -= 30 + 20 * sc->data[SC_ANGRIFFS_MODUS]->val1; + if(sc->data[SC_STONEHARDSKIN])// Final DEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] + def += sc->data[SC_STONEHARDSKIN]->val1; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + def >>=1; + if(sc->data[SC_FREEZE]) + def >>=1; + if(sc->data[SC_SIGNUMCRUCIS]) + def -= def * sc->data[SC_SIGNUMCRUCIS]->val2/100; + if(sc->data[SC_CONCENTRATION]) + def -= def * sc->data[SC_CONCENTRATION]->val4/100; + if(sc->data[SC_SKE]) + def >>=1; + if(sc->data[SC_PROVOKE] && bl->type != BL_PC) // Provoke doesn't alter player defense-> + def -= def * sc->data[SC_PROVOKE]->val4/100; + if(sc->data[SC_STRIPSHIELD]) + def -= def * sc->data[SC_STRIPSHIELD]->val2/100; + if (sc->data[SC_FLING]) + def -= def * (sc->data[SC_FLING]->val2)/100; + if( sc->data[SC_FREEZING] ) + def -= def * 10 / 100; + if( sc->data[SC_MARSHOFABYSS] ) + def -= def * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; + if( sc->data[SC_ANALYZE] ) + def -= def * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if( sc->data[SC_FORCEOFVANGUARD] ) + def += def * 2 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; + if(sc->data[SC_SATURDAYNIGHTFEVER]) + def -= def * (10 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; + if(sc->data[SC_EARTHDRIVE]) + def -= def * 25 / 100; + if( sc->data[SC_ROCK_CRUSHER] ) + def -= def * sc->data[SC_ROCK_CRUSHER]->val2 / 100; + if( sc->data[SC_POWER_OF_GAIA] ) + def += def * sc->data[SC_POWER_OF_GAIA]->val2 / 100; + if( sc->data[SC_PRESTIGE] ) + def += def * sc->data[SC_PRESTIGE]->val1 / 100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_race(bl)==RC_PLANT) + def /= 2; + } + + return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX);; +} + +static signed short status_calc_def2(struct block_list *bl, struct status_change *sc, int def2) +{ + if(!sc || !sc->count) +#ifdef RENEWAL + return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(def2,1,SHRT_MAX); +#endif + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_ETERNALCHAOS]) + return 0; + if(sc->data[SC_SUN_COMFORT]) + def2 += sc->data[SC_SUN_COMFORT]->val2; + if( sc->data[SC_SHIELDSPELL_REF] && sc->data[SC_SHIELDSPELL_REF]->val1 == 1 ) + def2 += sc->data[SC_SHIELDSPELL_REF]->val2; + if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) + def2 += (5 + sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); + + if(sc->data[SC_ANGELUS]) +#ifdef RENEWAL //in renewal only the VIT stat bonus is boosted by angelus + def2 += status_get_vit(bl) / 2 * sc->data[SC_ANGELUS]->val2/100; +#else + def2 += def2 * sc->data[SC_ANGELUS]->val2/100; +#endif + if(sc->data[SC_CONCENTRATION]) + def2 -= def2 * sc->data[SC_CONCENTRATION]->val4/100; + if(sc->data[SC_POISON]) + def2 -= def2 * 25/100; + if(sc->data[SC_DPOISON]) + def2 -= def2 * 25/100; + if(sc->data[SC_SKE]) + def2 -= def2 * 50/100; + if(sc->data[SC_PROVOKE]) + def2 -= def2 * sc->data[SC_PROVOKE]->val4/100; + if(sc->data[SC_JOINTBEAT]) + def2 -= def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_SHOULDER ? 50 : 0 ) / 100 + + def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST ? 25 : 0 ) / 100; + if(sc->data[SC_FLING]) + def2 -= def2 * (sc->data[SC_FLING]->val3)/100; + if( sc->data[SC_FREEZING] ) + def2 -= def2 * 3 / 10; + if(sc->data[SC_ANALYZE]) + def2 -= def2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if( sc->data[SC_ECHOSONG] ) + def2 += def2 * sc->data[SC_ECHOSONG]->val2/100; + if( sc->data[SC_GT_REVITALIZE] && sc->data[SC_GT_REVITALIZE]->val4) + def2 += def2 * sc->data[SC_GT_REVITALIZE]->val4 / 100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_race(bl)==RC_PLANT) + def2 /= 2; + } + if (sc->data[SC_PARALYSIS]) + def2 -= def2 * sc->data[SC_PARALYSIS]->val2 / 100; + +#ifdef RENEWAL + return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(def2,1,SHRT_MAX); +#endif +} + + +static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int mdef) { + + if(!sc || !sc->count) + return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_BARRIER]) + return 100; + +#ifndef RENEWAL // no longer provides 90 MDEF in renewal mode + if(sc->data[SC_STEELBODY]) + return 90; +#endif + + if(sc->data[SC_ARMORCHANGE]) + mdef += sc->data[SC_ARMORCHANGE]->val3; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) + mdef += 50; + if(sc->data[SC_ENDURE])// It has been confirmed that eddga card grants 1 MDEF, not 0, not 10, but 1. + mdef += (sc->data[SC_ENDURE]->val4 == 0) ? sc->data[SC_ENDURE]->val1 : 1; + if(sc->data[SC_CONCENTRATION]) + mdef += 1; //Skill info says it adds a fixed 1 Mdef point. + if(sc->data[SC_STONEHARDSKIN])// Final MDEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] + mdef += sc->data[SC_STONEHARDSKIN]->val1; + if(sc->data[SC_WATER_BARRIER]) + mdef += sc->data[SC_WATER_BARRIER]->val2; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + mdef += 25*mdef/100; + if(sc->data[SC_FREEZE]) + mdef += 25*mdef/100; + if( sc->data[SC_MARSHOFABYSS] ) + mdef -= mdef * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; + if(sc->data[SC_ANALYZE]) + mdef -= mdef * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if(sc->data[SC_SYMPHONYOFLOVER]) + mdef += mdef * sc->data[SC_SYMPHONYOFLOVER]->val2 / 100; + if(sc->data[SC_GT_CHANGE] && sc->data[SC_GT_CHANGE]->val4) + mdef -= mdef * sc->data[SC_GT_CHANGE]->val4 / 100; + if (sc->data[SC_ODINS_POWER]) + mdef -= 20 * sc->data[SC_ODINS_POWER]->val1; + + return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); +} + +static signed short status_calc_mdef2(struct block_list *bl, struct status_change *sc, int mdef2) +{ + if(!sc || !sc->count) +#ifdef RENEWAL + return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(mdef2,1,SHRT_MAX); +#endif + + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_SKA]) + return 90; + if(sc->data[SC_MINDBREAKER]) + mdef2 -= mdef2 * sc->data[SC_MINDBREAKER]->val3/100; + if(sc->data[SC_ANALYZE]) + mdef2 -= mdef2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + +#ifdef RENEWAL + return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(mdef2,1,SHRT_MAX); +#endif +} + +static unsigned short status_calc_speed(struct block_list *bl, struct status_change *sc, int speed) +{ + TBL_PC* sd = BL_CAST(BL_PC, bl); + int speed_rate; + + if( sc == NULL ) + return cap_value(speed,10,USHRT_MAX); + + if( sd && sd->ud.skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || sd->ud.skill_id == LG_EXEEDBREAK) ) + { + if( sd->ud.skill_id == LG_EXEEDBREAK ) + speed_rate = 100 + 60 - (sd->ud.skill_lv * 10); + else + speed_rate = 175 - 5 * pc_checkskill(sd,SA_FREECAST); + } + else + { + speed_rate = 100; + + //GetMoveHasteValue2() + { + int val = 0; + + if( sc->data[SC_FUSION] ) + val = 25; + else if( sd ) { + if( pc_isriding(sd) || sd->sc.option&(OPTION_DRAGON|OPTION_MOUNTING) ) + val = 25;//Same bonus + else if( pc_isridingwug(sd) ) + val = 15 + 5 * pc_checkskill(sd, RA_WUGRIDER); + else if( pc_ismadogear(sd) ) { + val = (- 10 * (5 - pc_checkskill(sd,NC_MADOLICENCE))); + if( sc->data[SC_ACCELERATION] ) + val += 25; + } + } + + speed_rate -= val; + } + + //GetMoveSlowValue() + { + int val = 0; + + if( sd && sc->data[SC_HIDING] && pc_checkskill(sd,RG_TUNNELDRIVE) > 0 ) + val = 120 - 6 * pc_checkskill(sd,RG_TUNNELDRIVE); + else + if( sd && sc->data[SC_CHASEWALK] && sc->data[SC_CHASEWALK]->val3 < 0 ) + val = sc->data[SC_CHASEWALK]->val3; + else + { + // Longing for Freedom cancels song/dance penalty + if( sc->data[SC_LONGING] ) + val = max( val, 50 - 10 * sc->data[SC_LONGING]->val1 ); + else + if( sd && sc->data[SC_DANCING] ) + val = max( val, 500 - (40 + 10 * (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) * pc_checkskill(sd,(sd->status.sex?BA_MUSICALLESSON:DC_DANCINGLESSON)) ); + + if( sc->data[SC_DECREASEAGI] ) + val = max( val, 25 ); + if( sc->data[SC_QUAGMIRE] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY] || (sc->data[SC_GLOOMYDAY] && sc->data[SC_GLOOMYDAY]->val4) ) + val = max( val, 50 ); + if( sc->data[SC_DONTFORGETME] ) + val = max( val, sc->data[SC_DONTFORGETME]->val3 ); + if( sc->data[SC_CURSE] ) + val = max( val, 300 ); + if( sc->data[SC_CHASEWALK] ) + val = max( val, sc->data[SC_CHASEWALK]->val3 ); + if( sc->data[SC_WEDDING] ) + val = max( val, 100 ); + if( sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&(BREAK_ANKLE|BREAK_KNEE) ) + val = max( val, (sc->data[SC_JOINTBEAT]->val2&BREAK_ANKLE ? 50 : 0) + (sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ? 30 : 0) ); + if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 0 ) + val = max( val, sc->data[SC_CLOAKING]->val1 < 3 ? 300 : 30 - 3 * sc->data[SC_CLOAKING]->val1 ); + if( sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY ) + val = max( val, 75 ); + if( sc->data[SC_SLOWDOWN] ) // Slow Potion + val = max( val, 100 ); + if( sc->data[SC_GATLINGFEVER] ) + val = max( val, 100 ); + if( sc->data[SC_SUITON] ) + val = max( val, sc->data[SC_SUITON]->val3 ); + if( sc->data[SC_SWOO] ) + val = max( val, 300 ); + if( sc->data[SC_FREEZING] ) + val = max( val, 70 ); + if( sc->data[SC_MARSHOFABYSS] ) + val = max( val, 40 + 10 * sc->data[SC_MARSHOFABYSS]->val1 ); + if( sc->data[SC_CAMOUFLAGE] && (sc->data[SC_CAMOUFLAGE]->val3&1) == 0 ) + val = max( val, sc->data[SC_CAMOUFLAGE]->val1 < 3 ? 0 : 25 * (5 - sc->data[SC_CAMOUFLAGE]->val1) ); + if( sc->data[SC__GROOMY] ) + val = max( val, sc->data[SC__GROOMY]->val2); + if( sc->data[SC_STEALTHFIELD_MASTER] ) + val = max( val, 30 ); + if( sc->data[SC_BANDING_DEFENCE] ) + val = max( val, sc->data[SC_BANDING_DEFENCE]->val1 );//+90% walking speed. + if( sc->data[SC_ROCK_CRUSHER_ATK] ) + val = max( val, sc->data[SC_ROCK_CRUSHER_ATK]->val2 ); + if( sc->data[SC_POWER_OF_GAIA] ) + val = max( val, sc->data[SC_POWER_OF_GAIA]->val2 ); + if( sc->data[SC_MELON_BOMB] ) + val = max( val, sc->data[SC_MELON_BOMB]->val1 ); + + if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate > 0 ) // permanent item-based speedup + val = max( val, sd->bonus.speed_rate + sd->bonus.speed_add_rate ); + } + + speed_rate += val; + } + + //GetMoveHasteValue1() + { + int val = 0; + + if( sc->data[SC_SPEEDUP1] ) //FIXME: used both by NPC_AGIUP and Speed Potion script + val = max( val, 50 ); + if( sc->data[SC_INCREASEAGI] ) + val = max( val, 25 ); + if( sc->data[SC_WINDWALK] ) + val = max( val, 2 * sc->data[SC_WINDWALK]->val1 ); + if( sc->data[SC_CARTBOOST] ) + val = max( val, 20 ); + if( sd && (sd->class_&MAPID_UPPERMASK) == MAPID_ASSASSIN && pc_checkskill(sd,TF_MISS) > 0 ) + val = max( val, 1 * pc_checkskill(sd,TF_MISS) ); + if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 1 ) + val = max( val, sc->data[SC_CLOAKING]->val1 >= 10 ? 25 : 3 * sc->data[SC_CLOAKING]->val1 - 3 ); + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + val = max( val, 25 ); + if( sc->data[SC_RUN] ) + val = max( val, 55 ); + if( sc->data[SC_AVOID] ) + val = max( val, 10 * sc->data[SC_AVOID]->val1 ); + if( sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + val = max( val, 75 ); + if( sc->data[SC_CLOAKINGEXCEED] ) + val = max( val, sc->data[SC_CLOAKINGEXCEED]->val3); + if( sc->data[SC_HOVERING] ) + val = max( val, 10 ); + if( sc->data[SC_GN_CARTBOOST] ) + val = max( val, sc->data[SC_GN_CARTBOOST]->val2 ); + if( sc->data[SC_SWINGDANCE] ) + val = max( val, sc->data[SC_SWINGDANCE]->val2 ); + if( sc->data[SC_WIND_STEP_OPTION] ) + val = max( val, sc->data[SC_WIND_STEP_OPTION]->val2 ); + + //FIXME: official items use a single bonus for this [ultramage] + if( sc->data[SC_SPEEDUP0] ) // temporary item-based speedup + val = max( val, 25 ); + if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate < 0 ) // permanent item-based speedup + val = max( val, -(sd->bonus.speed_rate + sd->bonus.speed_add_rate) ); + + speed_rate -= val; + } + + if( speed_rate < 40 ) + speed_rate = 40; + } + + //GetSpeed() + { + if( sd && pc_iscarton(sd) ) + speed += speed * (50 - 5 * pc_checkskill(sd,MC_PUSHCART)) / 100; + if( sc->data[SC_PARALYSE] ) + speed += speed * 50 / 100; + if( speed_rate != 100 ) + speed = speed * speed_rate / 100; + if( sc->data[SC_STEELBODY] ) + speed = 200; + if( sc->data[SC_DEFENDER] ) + speed = max(speed, 200); + if( sc->data[SC_WALKSPEED] && sc->data[SC_WALKSPEED]->val1 > 0 ) // ChangeSpeed + speed = speed * 100 / sc->data[SC_WALKSPEED]->val1; + } + + return (short)cap_value(speed,10,USHRT_MAX); +} + +#ifdef RENEWAL_ASPD +// flag&1 - fixed value [malufett] +// flag&2 - percentage value +static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag) +{ + int i, pots = 0, skills1 = 0, skills2 = 0; + + if(!sc || !sc->count) + return 0; + + if(sc->data[i=SC_ASPDPOTION3] || + sc->data[i=SC_ASPDPOTION2] || + sc->data[i=SC_ASPDPOTION1] || + sc->data[i=SC_ASPDPOTION0]) + pots += sc->data[i]->val1; + + if( !sc->data[SC_QUAGMIRE] ){ + if(sc->data[SC_STAR_COMFORT]) + skills1 = 5; // needs more info + + if(sc->data[SC_TWOHANDQUICKEN] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_ONEHAND] && skills1 < 7) skills1 = 7; + + if(sc->data[SC_MERC_QUICKEN] && skills1 < 7) // needs more info + skills1 = 7; + + if(sc->data[SC_ADRENALINE2] && skills1 < 6) + skills1 = 6; + + if(sc->data[SC_ADRENALINE] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_SPEARQUICKEN] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_GATLINGFEVER] && skills1 < 9) // needs more info + skills1 = 9; + + if(sc->data[SC_FLEET] && skills1 < 5) + skills1 = 5; + + if(sc->data[SC_ASSNCROS] && + skills1 < 5+1*sc->data[SC_ASSNCROS]->val1) // needs more info + { + if (bl->type!=BL_PC) + skills1 = 4+1*sc->data[SC_ASSNCROS]->val1; + else + switch(((TBL_PC*)bl)->status.weapon) + { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + break; + default: + skills1 = 5+1*sc->data[SC_ASSNCROS]->val1; + } + } + } + + if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) && skills1 < 15) + skills1 = 15; + else if(sc->data[SC_MADNESSCANCEL] && skills1 < 15) // needs more info + skills1 = 15; + + if(sc->data[SC_DONTFORGETME]) + skills2 -= sc->data[SC_DONTFORGETME]->val2; // needs more info + if(sc->data[SC_LONGING]) + skills2 -= sc->data[SC_LONGING]->val2; // needs more info + if(sc->data[SC_STEELBODY]) + skills2 -= 25; + if(sc->data[SC_SKA]) + skills2 -= 25; + if(sc->data[SC_DEFENDER]) + skills2 -= sc->data[SC_DEFENDER]->val4; // needs more info + if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) // needs more info + skills2 -= 25; + if(sc->data[SC_GRAVITATION]) + skills2 -= sc->data[SC_GRAVITATION]->val2; // needs more info + if(sc->data[SC_JOINTBEAT]) { // needs more info + if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) + skills2 -= 25; + if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) + skills2 -= 10; + } + if( sc->data[SC_FREEZING] ) + skills2 -= 30; + if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) + skills2 -= 50; + if( sc->data[SC_PARALYSE] ) + skills2 -= 10; + if( sc->data[SC__BODYPAINT] ) + skills2 -= 2 + 5 * sc->data[SC__BODYPAINT]->val1; + if( sc->data[SC__INVISIBILITY] ) + skills2 -= sc->data[SC__INVISIBILITY]->val2 ; + if( sc->data[SC__GROOMY] ) + skills2 -= sc->data[SC__GROOMY]->val2; + if( sc->data[SC_SWINGDANCE] ) + skills2 += sc->data[SC_SWINGDANCE]->val2; + if( sc->data[SC_DANCEWITHWUG] ) + skills2 += sc->data[SC_DANCEWITHWUG]->val3; + if( sc->data[SC_GLOOMYDAY] ) + skills2 -= sc->data[SC_GLOOMYDAY]->val3; + if( sc->data[SC_EARTHDRIVE] ) + skills2 -= 25; + if( sc->data[SC_GT_CHANGE] ) + skills2 += sc->data[SC_GT_CHANGE]->val3; + if( sc->data[SC_MELON_BOMB] ) + skills2 -= sc->data[SC_MELON_BOMB]->val1; + if( sc->data[SC_BOOST500] ) + skills2 += sc->data[SC_BOOST500]->val1; + if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) + skills2 += sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1; + if( sc->data[SC_INCASPDRATE] ) + skills2 += sc->data[SC_INCASPDRATE]->val1; + + return ( flag&1? (skills1 + pots) : skills2 ); +} +#endif + +static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int aspd) { + if (!sc || !sc->count) + return cap_value(aspd, 0, 2000); + + if (!sc->data[SC_QUAGMIRE]) { + if (sc->data[SC_OVERED_BOOST]) + aspd = 2000 - sc->data[SC_OVERED_BOOST]->val3*10; + } + + if ((sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] + || sc->data[SC_WILD_STORM_OPTION])) + aspd -= 50; // +5 ASPD + if( sc && sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) + aspd -= (bl->type==BL_PC?pc_checkskill((TBL_PC *)bl, RK_RUNEMASTERY):10) / 10 * 40; + + return cap_value(aspd, 0, 2000); // will be recap for proper bl anyway +} + +/// Calculates an object's ASPD modifier (alters the base amotion value). +/// Note that the scale of aspd_rate is 1000 = 100%. +static short status_calc_aspd_rate(struct block_list *bl, struct status_change *sc, int aspd_rate) +{ + int i; + + if(!sc || !sc->count) + return cap_value(aspd_rate,0,SHRT_MAX); + + if( !sc->data[SC_QUAGMIRE] ){ + int max = 0; + if(sc->data[SC_STAR_COMFORT]) + max = sc->data[SC_STAR_COMFORT]->val2; + + if(sc->data[SC_TWOHANDQUICKEN] && + max < sc->data[SC_TWOHANDQUICKEN]->val2) + max = sc->data[SC_TWOHANDQUICKEN]->val2; + + if(sc->data[SC_ONEHAND] && + max < sc->data[SC_ONEHAND]->val2) + max = sc->data[SC_ONEHAND]->val2; + + if(sc->data[SC_MERC_QUICKEN] && + max < sc->data[SC_MERC_QUICKEN]->val2) + max = sc->data[SC_MERC_QUICKEN]->val2; + + if(sc->data[SC_ADRENALINE2] && + max < sc->data[SC_ADRENALINE2]->val3) + max = sc->data[SC_ADRENALINE2]->val3; + + if(sc->data[SC_ADRENALINE] && + max < sc->data[SC_ADRENALINE]->val3) + max = sc->data[SC_ADRENALINE]->val3; + + if(sc->data[SC_SPEARQUICKEN] && + max < sc->data[SC_SPEARQUICKEN]->val2) + max = sc->data[SC_SPEARQUICKEN]->val2; + + if(sc->data[SC_GATLINGFEVER] && + max < sc->data[SC_GATLINGFEVER]->val2) + max = sc->data[SC_GATLINGFEVER]->val2; + + if(sc->data[SC_FLEET] && + max < sc->data[SC_FLEET]->val2) + max = sc->data[SC_FLEET]->val2; + + if(sc->data[SC_ASSNCROS] && + max < sc->data[SC_ASSNCROS]->val2) + { + if (bl->type!=BL_PC) + max = sc->data[SC_ASSNCROS]->val2; + else + switch(((TBL_PC*)bl)->status.weapon) + { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + break; + default: + max = sc->data[SC_ASSNCROS]->val2; + } + } + aspd_rate -= max; + + if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) + aspd_rate -= 300; + else if(sc->data[SC_MADNESSCANCEL]) + aspd_rate -= 200; + } + + if( sc->data[i=SC_ASPDPOTION3] || + sc->data[i=SC_ASPDPOTION2] || + sc->data[i=SC_ASPDPOTION1] || + sc->data[i=SC_ASPDPOTION0] ) + aspd_rate -= sc->data[i]->val2; + + if(sc->data[SC_DONTFORGETME]) + aspd_rate += 10 * sc->data[SC_DONTFORGETME]->val2; + if(sc->data[SC_LONGING]) + aspd_rate += sc->data[SC_LONGING]->val2; + if(sc->data[SC_STEELBODY]) + aspd_rate += 250; + if(sc->data[SC_SKA]) + aspd_rate += 250; + if(sc->data[SC_DEFENDER]) + aspd_rate += sc->data[SC_DEFENDER]->val4; + if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) + aspd_rate += 250; + if(sc->data[SC_GRAVITATION]) + aspd_rate += sc->data[SC_GRAVITATION]->val2; + if(sc->data[SC_JOINTBEAT]) { + if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) + aspd_rate += 250; + if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) + aspd_rate += 100; + } + if( sc->data[SC_FREEZING] ) + aspd_rate += 300; + if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) + aspd_rate += 500; + if( sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) + aspd_rate -= sc->data[SC_FIGHTINGSPIRIT]->val2; + if( sc->data[SC_PARALYSE] ) + aspd_rate += 100; + if( sc->data[SC__BODYPAINT] ) + aspd_rate += 200 + 50 * sc->data[SC__BODYPAINT]->val1; + if( sc->data[SC__INVISIBILITY] ) + aspd_rate += sc->data[SC__INVISIBILITY]->val2 * 10 ; + if( sc->data[SC__GROOMY] ) + aspd_rate += sc->data[SC__GROOMY]->val2 * 10; + if( sc->data[SC_SWINGDANCE] ) + aspd_rate -= sc->data[SC_SWINGDANCE]->val2 * 10; + if( sc->data[SC_DANCEWITHWUG] ) + aspd_rate -= sc->data[SC_DANCEWITHWUG]->val3 * 10; + if( sc->data[SC_GLOOMYDAY] ) + aspd_rate += sc->data[SC_GLOOMYDAY]->val3 * 10; + if( sc->data[SC_EARTHDRIVE] ) + aspd_rate += 250; + if( sc->data[SC_GT_CHANGE] ) + aspd_rate -= sc->data[SC_GT_CHANGE]->val3 * 10; + if( sc->data[SC_MELON_BOMB] ) + aspd_rate += sc->data[SC_MELON_BOMB]->val1 * 10; + if( sc->data[SC_BOOST500] ) + aspd_rate -= sc->data[SC_BOOST500]->val1 *10; + if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) + aspd_rate -= sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1 * 10; + if( sc->data[SC_INCASPDRATE] ) + aspd_rate -= sc->data[SC_INCASPDRATE]->val1 * 10; + if( sc->data[SC_PAIN_KILLER]) + aspd_rate += sc->data[SC_PAIN_KILLER]->val2 * 10; + if( sc->data[SC_GOLDENE_FERSE]) + aspd_rate -= sc->data[SC_GOLDENE_FERSE]->val3 * 10; + + return (short)cap_value(aspd_rate,0,SHRT_MAX); +} + +static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion) +{ + if( !sc || !sc->count || map_flag_gvg(bl->m) || map[bl->m].flag.battleground ) + return cap_value(dmotion,0,USHRT_MAX); + /** + * It has been confirmed on official servers that MvP mobs have no dmotion even without endure + **/ + if( sc->data[SC_ENDURE] || ( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) ) + return 0; + if( sc->data[SC_CONCENTRATION] ) + return 0; + if( sc->data[SC_RUN] || sc->data[SC_WUGDASH] ) + return 0; + + return (unsigned short)cap_value(dmotion,0,USHRT_MAX); +} + +static unsigned int status_calc_maxhp(struct block_list *bl, struct status_change *sc, uint64 maxhp) +{ + if(!sc || !sc->count) + return (unsigned int)cap_value(maxhp,1,UINT_MAX); + + if(sc->data[SC_INCMHPRATE]) + maxhp += maxhp * sc->data[SC_INCMHPRATE]->val1/100; + if(sc->data[SC_INCMHP]) + maxhp += (sc->data[SC_INCMHP]->val1); + if(sc->data[SC_APPLEIDUN]) + maxhp += maxhp * sc->data[SC_APPLEIDUN]->val2/100; + if(sc->data[SC_DELUGE]) + maxhp += maxhp * sc->data[SC_DELUGE]->val2/100; + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + maxhp += maxhp * 2; + if(sc->data[SC_MARIONETTE]) + maxhp -= 1000; + if(sc->data[SC_SOLID_SKIN_OPTION]) + maxhp += 2000;// Fix amount. + if(sc->data[SC_POWER_OF_GAIA]) + maxhp += 3000; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + maxhp += 500; + + if(sc->data[SC_MERC_HPUP]) + maxhp += maxhp * sc->data[SC_MERC_HPUP]->val2/100; + + if(sc->data[SC_EPICLESIS]) + maxhp += maxhp * 5 * sc->data[SC_EPICLESIS]->val1 / 100; + if(sc->data[SC_VENOMBLEED]) + maxhp -= maxhp * 15 / 100; + if(sc->data[SC__WEAKNESS]) + maxhp -= maxhp * sc->data[SC__WEAKNESS]->val2 / 100; + if(sc->data[SC_LERADSDEW]) + maxhp += maxhp * sc->data[SC_LERADSDEW]->val3 / 100; + if(sc->data[SC_FORCEOFVANGUARD]) + maxhp += maxhp * 3 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; + if(sc->data[SC_INSPIRATION]) //Custom value. + maxhp += maxhp * 3 * sc->data[SC_INSPIRATION]->val1 / 100; + if(sc->data[SC_RAISINGDRAGON]) + maxhp += maxhp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; + if(sc->data[SC_GT_CHANGE]) // Max HP decrease: [Skill Level x 4] % + maxhp -= maxhp * (4 * sc->data[SC_GT_CHANGE]->val1) / 100; + if(sc->data[SC_GT_REVITALIZE])// Max HP increase: [Skill Level x 2] % + maxhp += maxhp * (2 * sc->data[SC_GT_REVITALIZE]->val1) / 100; + if(sc->data[SC_MUSTLE_M]) + maxhp += maxhp * sc->data[SC_MUSTLE_M]->val1/100; + if(sc->data[SC_MYSTERIOUS_POWDER]) + maxhp -= sc->data[SC_MYSTERIOUS_POWDER]->val1 / 100; + if(sc->data[SC_PETROLOGY_OPTION]) + maxhp += maxhp * sc->data[SC_PETROLOGY_OPTION]->val2 / 100; + if (sc->data[SC_ANGRIFFS_MODUS]) + maxhp += maxhp * 5 * sc->data[SC_ANGRIFFS_MODUS]->val1 /100; + if (sc->data[SC_GOLDENE_FERSE]) + maxhp += maxhp * sc->data[SC_GOLDENE_FERSE]->val2 / 100; + + return (unsigned int)cap_value(maxhp,1,UINT_MAX); +} + +static unsigned int status_calc_maxsp(struct block_list *bl, struct status_change *sc, unsigned int maxsp) +{ + if(!sc || !sc->count) + return cap_value(maxsp,1,UINT_MAX); + + if(sc->data[SC_INCMSPRATE]) + maxsp += maxsp * sc->data[SC_INCMSPRATE]->val1/100; + if(sc->data[SC_INCMSP]) + maxsp += (sc->data[SC_INCMSP]->val1); + if(sc->data[SC_SERVICE4U]) + maxsp += maxsp * sc->data[SC_SERVICE4U]->val2/100; + if(sc->data[SC_MERC_SPUP]) + maxsp += maxsp * sc->data[SC_MERC_SPUP]->val2/100; + if(sc->data[SC_RAISINGDRAGON]) + maxsp += maxsp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; + if(sc->data[SC_LIFE_FORCE_F]) + maxsp += maxsp * sc->data[SC_LIFE_FORCE_F]->val1/100; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) + maxsp += 50; + + return cap_value(maxsp,1,UINT_MAX); +} + +static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element) +{ + if(!sc || !sc->count) + return element; + + if(sc->data[SC_FREEZE]) + return ELE_WATER; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + return ELE_EARTH; + if(sc->data[SC_BENEDICTIO]) + return ELE_HOLY; + if(sc->data[SC_CHANGEUNDEAD]) + return ELE_UNDEAD; + if(sc->data[SC_ELEMENTALCHANGE]) + return sc->data[SC_ELEMENTALCHANGE]->val2; + if(sc->data[SC_SHAPESHIFT]) + return sc->data[SC_SHAPESHIFT]->val2; + + return (unsigned char)cap_value(element,0,UCHAR_MAX); +} + +static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv) +{ + if(!sc || !sc->count) + return lv; + + if(sc->data[SC_FREEZE]) + return 1; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + return 1; + if(sc->data[SC_BENEDICTIO]) + return 1; + if(sc->data[SC_CHANGEUNDEAD]) + return 1; + if(sc->data[SC_ELEMENTALCHANGE]) + return sc->data[SC_ELEMENTALCHANGE]->val1; + if(sc->data[SC_SHAPESHIFT]) + return 1; + if(sc->data[SC__INVISIBILITY]) + return 1; + + return (unsigned char)cap_value(lv,1,4); +} + + +unsigned char status_calc_attack_element(struct block_list *bl, struct status_change *sc, int element) +{ + if(!sc || !sc->count) + return element; + if(sc->data[SC_ENCHANTARMS]) + return sc->data[SC_ENCHANTARMS]->val2; + if(sc->data[SC_WATERWEAPON] + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) ) + return ELE_WATER; + if(sc->data[SC_EARTHWEAPON] + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) ) + return ELE_EARTH; + if(sc->data[SC_FIREWEAPON] + || (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) ) + return ELE_FIRE; + if(sc->data[SC_WINDWEAPON] + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) ) + return ELE_WIND; + if(sc->data[SC_ENCPOISON]) + return ELE_POISON; + if(sc->data[SC_ASPERSIO]) + return ELE_HOLY; + if(sc->data[SC_SHADOWWEAPON]) + return ELE_DARK; + if(sc->data[SC_GHOSTWEAPON] || sc->data[SC__INVISIBILITY]) + return ELE_GHOST; + if(sc->data[SC_TIDAL_WEAPON_OPTION] || sc->data[SC_TIDAL_WEAPON] ) + return ELE_WATER; + if(sc->data[SC_PYROCLASTIC]) + return ELE_FIRE; + return (unsigned char)cap_value(element,0,UCHAR_MAX); +} + +static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode) +{ + if(!sc || !sc->count) + return mode; + if(sc->data[SC_MODECHANGE]) { + if (sc->data[SC_MODECHANGE]->val2) + mode = sc->data[SC_MODECHANGE]->val2; //Set mode + if (sc->data[SC_MODECHANGE]->val3) + mode|= sc->data[SC_MODECHANGE]->val3; //Add mode + if (sc->data[SC_MODECHANGE]->val4) + mode&=~sc->data[SC_MODECHANGE]->val4; //Del mode + } + return cap_value(mode,0,USHRT_MAX); +} + +const char* status_get_name(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: return ((TBL_PC *)bl)->fakename[0] != '\0' ? ((TBL_PC*)bl)->fakename : ((TBL_PC*)bl)->status.name; + case BL_MOB: return ((TBL_MOB*)bl)->name; + case BL_PET: return ((TBL_PET*)bl)->pet.name; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.name; + case BL_NPC: return ((TBL_NPC*)bl)->name; + } + return "Unknown"; +} + +/*========================================== + * Get the class of the current bl + * return + * 0 = fail + * class_id = success + *------------------------------------------*/ +int status_get_class(struct block_list *bl) { + nullpo_ret(bl); + switch( bl->type ) { + case BL_PC: return ((TBL_PC*)bl)->status.class_; + case BL_MOB: return ((TBL_MOB*)bl)->vd->class_; //Class used on all code should be the view class of the mob. + case BL_PET: return ((TBL_PET*)bl)->pet.class_; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.class_; + case BL_MER: return ((TBL_MER*)bl)->mercenary.class_; + case BL_NPC: return ((TBL_NPC*)bl)->class_; + case BL_ELEM: return ((TBL_ELEM*)bl)->elemental.class_; + } + return 0; +} +/*========================================== + * Get the base level of the current bl + * return + * 1 = fail + * level = success + *------------------------------------------*/ +int status_get_lv(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: return ((TBL_PC*)bl)->status.base_level; + case BL_MOB: return ((TBL_MOB*)bl)->level; + case BL_PET: return ((TBL_PET*)bl)->pet.level; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.level; + case BL_MER: return ((TBL_MER*)bl)->db->lv; + case BL_ELEM: return ((TBL_ELEM*)bl)->db->lv; + case BL_NPC: return ((TBL_NPC*)bl)->level; + } + return 1; +} + +struct regen_data *status_get_regen_data(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->regen; + case BL_HOM: return &((TBL_HOM*)bl)->regen; + case BL_MER: return &((TBL_MER*)bl)->regen; + case BL_ELEM: return &((TBL_ELEM*)bl)->regen; + default: + return NULL; + } +} + +struct status_data *status_get_status_data(struct block_list *bl) +{ + nullpo_retr(&dummy_status, bl); + + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->battle_status; + case BL_MOB: return &((TBL_MOB*)bl)->status; + case BL_PET: return &((TBL_PET*)bl)->status; + case BL_HOM: return &((TBL_HOM*)bl)->battle_status; + case BL_MER: return &((TBL_MER*)bl)->battle_status; + case BL_ELEM: return &((TBL_ELEM*)bl)->battle_status; + case BL_NPC: return ((mobdb_checkid(((TBL_NPC*)bl)->class_) == 0) ? &((TBL_NPC*)bl)->status : &dummy_status); + default: + return &dummy_status; + } +} + +struct status_data *status_get_base_status(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->base_status; + case BL_MOB: return ((TBL_MOB*)bl)->base_status ? ((TBL_MOB*)bl)->base_status : &((TBL_MOB*)bl)->db->status; + case BL_PET: return &((TBL_PET*)bl)->db->status; + case BL_HOM: return &((TBL_HOM*)bl)->base_status; + case BL_MER: return &((TBL_MER*)bl)->base_status; + case BL_ELEM: return &((TBL_ELEM*)bl)->base_status; + case BL_NPC: return ((mobdb_checkid(((TBL_NPC*)bl)->class_) == 0) ? &((TBL_NPC*)bl)->status : NULL); + default: + return NULL; + } +} +defType status_get_def(struct block_list *bl) { + struct unit_data *ud; + struct status_data *status = status_get_status_data(bl); + int def = status?status->def:0; + ud = unit_bl2ud(bl); + if (ud && ud->skilltimer != INVALID_TIMER) + def -= def * skill_get_castdef(ud->skill_id)/100; + + return cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX); +} + +unsigned short status_get_speed(struct block_list *bl) +{ + if(bl->type==BL_NPC)//Only BL with speed data but no status_data [Skotlex] + return ((struct npc_data *)bl)->speed; + return status_get_status_data(bl)->speed; +} + +int status_get_party_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->status.party_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->status.party_id; + break; + case BL_MOB: { + struct mob_data *md=(TBL_MOB*)bl; + if( md->master_id > 0 ) { + struct map_session_data *msd; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->status.party_id; + return -md->master_id; + } + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->status.party_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->status.party_id; + break; + case BL_SKILL: + return ((TBL_SKILL*)bl)->group->party_id; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->status.party_id; + break; + } + return 0; +} + +int status_get_guild_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->status.guild_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->status.guild_id; + break; + case BL_MOB: { + struct map_session_data *msd; + struct mob_data *md = (struct mob_data *)bl; + if (md->guardian_data) //Guardian's guild [Skotlex] + return md->guardian_data->guild_id; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->status.guild_id; //Alchemist's mobs [Skotlex] + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->status.guild_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->status.guild_id; + break; + case BL_NPC: + if (((TBL_NPC*)bl)->subtype == SCRIPT) + return ((TBL_NPC*)bl)->u.scr.guild_id; + break; + case BL_SKILL: + return ((TBL_SKILL*)bl)->group->guild_id; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->status.guild_id; + break; + } + return 0; +} + +int status_get_emblem_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->guild_emblem_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->guild_emblem_id; + break; + case BL_MOB: { + struct map_session_data *msd; + struct mob_data *md = (struct mob_data *)bl; + if (md->guardian_data) //Guardian's guild [Skotlex] + return md->guardian_data->emblem_id; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->guild_emblem_id; //Alchemist's mobs [Skotlex] + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->guild_emblem_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->guild_emblem_id; + break; + case BL_NPC: + if (((TBL_NPC*)bl)->subtype == SCRIPT && ((TBL_NPC*)bl)->u.scr.guild_id > 0) { + struct guild *g = guild_search(((TBL_NPC*)bl)->u.scr.guild_id); + if (g) + return g->emblem_id; + } + break; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->guild_emblem_id; + break; + } + return 0; +} + +int status_get_mexp(struct block_list *bl) +{ + nullpo_ret(bl); + if(bl->type==BL_MOB) + return ((struct mob_data *)bl)->db->mexp; + if(bl->type==BL_PET) + return ((struct pet_data *)bl)->db->mexp; + return 0; +} +int status_get_race2(struct block_list *bl) +{ + nullpo_ret(bl); + if(bl->type == BL_MOB) + return ((struct mob_data *)bl)->db->race2; + if(bl->type==BL_PET) + return ((struct pet_data *)bl)->db->race2; + return 0; +} + +int status_isdead(struct block_list *bl) +{ + nullpo_ret(bl); + return status_get_status_data(bl)->hp == 0; +} + +int status_isimmune(struct block_list *bl) +{ + struct status_change *sc =status_get_sc(bl); + if (sc && sc->data[SC_HERMODE]) + return 100; + + if (bl->type == BL_PC && + ((TBL_PC*)bl)->special_state.no_magic_damage >= battle_config.gtb_sc_immunity) + return ((TBL_PC*)bl)->special_state.no_magic_damage; + return 0; +} + +struct view_data* status_get_viewdata(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->vd; + case BL_MOB: return ((TBL_MOB*)bl)->vd; + case BL_PET: return &((TBL_PET*)bl)->vd; + case BL_NPC: return ((TBL_NPC*)bl)->vd; + case BL_HOM: return ((TBL_HOM*)bl)->vd; + case BL_MER: return ((TBL_MER*)bl)->vd; + case BL_ELEM: return ((TBL_ELEM*)bl)->vd; + } + return NULL; +} + +void status_set_viewdata(struct block_list *bl, int class_) +{ + struct view_data* vd; + nullpo_retv(bl); + if (mobdb_checkid(class_) || mob_is_clone(class_)) + vd = mob_get_viewdata(class_); + else if (npcdb_checkid(class_) || (bl->type == BL_NPC && class_ == WARP_CLASS)) + vd = npc_get_viewdata(class_); + else if (homdb_checkid(class_)) + vd = merc_get_hom_viewdata(class_); + else if (merc_class(class_)) + vd = merc_get_viewdata(class_); + else if (elemental_class(class_)) + vd = elemental_get_viewdata(class_); + else + vd = NULL; + + switch (bl->type) { + case BL_PC: + { + TBL_PC* sd = (TBL_PC*)bl; + if (pcdb_checkid(class_)) { + if (sd->sc.option&OPTION_WEDDING) + class_ = JOB_WEDDING; + else if (sd->sc.option&OPTION_SUMMER) + class_ = JOB_SUMMER; + else if (sd->sc.option&OPTION_XMAS) + class_ = JOB_XMAS; + else if (sd->sc.option&OPTION_RIDING) { + switch (class_) { //Adapt class to a Mounted one. + case JOB_KNIGHT: + class_ = JOB_KNIGHT2; + break; + case JOB_CRUSADER: + class_ = JOB_CRUSADER2; + break; + case JOB_LORD_KNIGHT: + class_ = JOB_LORD_KNIGHT2; + break; + case JOB_PALADIN: + class_ = JOB_PALADIN2; + break; + case JOB_BABY_KNIGHT: + class_ = JOB_BABY_KNIGHT2; + break; + case JOB_BABY_CRUSADER: + class_ = JOB_BABY_CRUSADER2; + break; + } + } + sd->vd.class_ = class_; + clif_get_weapon_view(sd, &sd->vd.weapon, &sd->vd.shield); + sd->vd.head_top = sd->status.head_top; + sd->vd.head_mid = sd->status.head_mid; + sd->vd.head_bottom = sd->status.head_bottom; + sd->vd.hair_style = cap_value(sd->status.hair,0,battle_config.max_hair_style); + sd->vd.hair_color = cap_value(sd->status.hair_color,0,battle_config.max_hair_color); + sd->vd.cloth_color = cap_value(sd->status.clothes_color,0,battle_config.max_cloth_color); + sd->vd.sex = sd->status.sex; + } else if (vd) + memcpy(&sd->vd, vd, sizeof(struct view_data)); + else + ShowError("status_set_viewdata (PC): No view data for class %d\n", class_); + } + break; + case BL_MOB: + { + TBL_MOB* md = (TBL_MOB*)bl; + if (vd) + md->vd = vd; + else + ShowError("status_set_viewdata (MOB): No view data for class %d\n", class_); + } + break; + case BL_PET: + { + TBL_PET* pd = (TBL_PET*)bl; + if (vd) { + memcpy(&pd->vd, vd, sizeof(struct view_data)); + if (!pcdb_checkid(vd->class_)) { + pd->vd.hair_style = battle_config.pet_hair_style; + if(pd->pet.equip) { + pd->vd.head_bottom = itemdb_viewid(pd->pet.equip); + if (!pd->vd.head_bottom) + pd->vd.head_bottom = pd->pet.equip; + } + } + } else + ShowError("status_set_viewdata (PET): No view data for class %d\n", class_); + } + break; + case BL_NPC: + { + TBL_NPC* nd = (TBL_NPC*)bl; + if (vd) + nd->vd = vd; + else + ShowError("status_set_viewdata (NPC): No view data for class %d\n", class_); + } + break; + case BL_HOM: //[blackhole89] + { + struct homun_data *hd = (struct homun_data*)bl; + if (vd) + hd->vd = vd; + else + ShowError("status_set_viewdata (HOMUNCULUS): No view data for class %d\n", class_); + } + break; + case BL_MER: + { + struct mercenary_data *md = (struct mercenary_data*)bl; + if (vd) + md->vd = vd; + else + ShowError("status_set_viewdata (MERCENARY): No view data for class %d\n", class_); + } + break; + case BL_ELEM: + { + struct elemental_data *ed = (struct elemental_data*)bl; + if (vd) + ed->vd = vd; + else + ShowError("status_set_viewdata (ELEMENTAL): No view data for class %d\n", class_); + } + break; + } + vd = status_get_viewdata(bl); + if (vd && vd->cloth_color && ( + (vd->class_==JOB_WEDDING && battle_config.wedding_ignorepalette) + || (vd->class_==JOB_XMAS && battle_config.xmas_ignorepalette) + || (vd->class_==JOB_SUMMER && battle_config.summer_ignorepalette) + )) + vd->cloth_color = 0; +} + +/// Returns the status_change data of bl or NULL if it doesn't exist. +struct status_change *status_get_sc(struct block_list *bl) { + if( bl ) + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->sc; + case BL_MOB: return &((TBL_MOB*)bl)->sc; + case BL_NPC: return &((TBL_NPC*)bl)->sc; + case BL_HOM: return &((TBL_HOM*)bl)->sc; + case BL_MER: return &((TBL_MER*)bl)->sc; + case BL_ELEM: return &((TBL_ELEM*)bl)->sc; + } + return NULL; +} + +void status_change_init(struct block_list *bl) +{ + struct status_change *sc = status_get_sc(bl); + nullpo_retv(sc); + memset(sc, 0, sizeof (struct status_change)); +} + +//Applies SC defense to a given status change. +//Returns the adjusted duration based on flag values. +//the flag values are the same as in status_change_start. +int status_get_sc_def(struct block_list *bl, enum sc_type type, int rate, int tick, int flag) +{ + int sc_def = 0, tick_def = 0; + struct status_data* status; + struct status_change* sc; + struct map_session_data *sd; + + nullpo_ret(bl); + + //Status that are blocked by Golden Thief Bug card or Wand of Hermod + if (status_isimmune(bl)) + switch (type) { + case SC_DECREASEAGI: + case SC_SILENCE: + case SC_COMA: + case SC_INCREASEAGI: + case SC_BLESSING: + case SC_SLOWPOISON: + case SC_IMPOSITIO: + case SC_AETERNA: + case SC_SUFFRAGIUM: + case SC_BENEDICTIO: + case SC_PROVIDENCE: + case SC_KYRIE: + case SC_ASSUMPTIO: + case SC_ANGELUS: + case SC_MAGNIFICAT: + case SC_GLORIA: + case SC_WINDWALK: + case SC_MAGICROD: + case SC_HALLUCINATION: + case SC_STONE: + case SC_QUAGMIRE: + case SC_SUITON: + case SC_SWINGDANCE: + case SC__ENERVATION: + case SC__GROOMY: + case SC__IGNORANCE: + case SC__LAZINESS: + case SC__UNLUCKY: + case SC__WEAKNESS: + case SC__BLOODYLUST: + return 0; + } + + sd = BL_CAST(BL_PC,bl); + status = status_get_status_data(bl); + sc = status_get_sc(bl); + if( sc && !sc->count ) + sc = NULL; + switch (type) { + case SC_STUN: + case SC_POISON: + if( sc && sc->data[SC__UNLUCKY] ) + return tick; + case SC_DPOISON: + case SC_SILENCE: + case SC_BLEEDING: + sc_def = 3 +status->vit; + break; + case SC_SLEEP: + sc_def = 3 +status->int_; + break; + case SC_DEEPSLEEP: + tick_def = status->int_ / 10 + status_get_lv(bl) * 65 / 1000; // Seems to be -1 sec every 10 int and -5% chance every 10 int. + sc_def = 5 * status->int_ /10; + break; + case SC_DECREASEAGI: + case SC_ADORAMUS://Arch Bishop + if (sd) tick>>=1; //Half duration for players. + case SC_STONE: + case SC_FREEZE: + sc_def = 3 +status->mdef; + break; + case SC_CURSE: + //Special property: inmunity when luk is greater than level or zero + if (status->luk > status_get_lv(bl) || status->luk == 0) + return 0; + else + sc_def = 3 +status->luk; + tick_def = status->vit; + break; + case SC_BLIND: + if( sc && sc->data[SC__UNLUCKY] ) + return tick; + sc_def = 3 +(status->vit + status->int_)/2; + break; + case SC_CONFUSION: + sc_def = 3 +(status->str + status->int_)/2; + break; + case SC_ANKLE: + if(status->mode&MD_BOSS) // Lasts 5 times less on bosses + tick /= 5; + sc_def = status->agi / 2; + break; + case SC_MAGICMIRROR: + case SC_ARMORCHANGE: + if (sd) //Duration greatly reduced for players. + tick /= 15; + //No defense against it (buff). + rate -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; // Lineal Reduction of Rate + break; + case SC_MARSHOFABYSS: + //5 second (Fixed) + 25 second - {( INT + LUK ) / 20 second } + tick -= (status->int_ + status->luk) / 20 * 1000; + break; + case SC_STASIS: + //5 second (fixed) + { Stasis Skill level * 5 - (Targetソスs VIT + DEX) / 20 } + tick -= (status->vit + status->dex) / 20 * 1000; + break; + case SC_WHITEIMPRISON: + if( tick == 5000 ) // 100% on caster + break; + if( bl->type == BL_PC ) + tick -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; + else + tick -= (status->vit + status->luk) / 20 * 1000; + break; + case SC_BURNING: + // From iROwiki : http://forums.irowiki.org/showpost.php?p=577240&postcount=583 + tick -= 50*status->luk + 60*status->int_ + 170*status->vit; + tick = max(tick,10000); // Minimum Duration 10s. + break; + case SC_FREEZING: + tick -= 1000 * ((status->vit + status->dex) / 20); + tick = max(tick,10000); // Minimum Duration 10s. + break; + case SC_OBLIVIONCURSE: // 100% - (100 - 0.8 x INT) + sc_def = 100 - ( 100 - status->int_* 8 / 10 ); + sc_def = max(sc_def, 5); // minimum of 5% + break; + case SC_BITE: // {(Base Success chance) - (Target's AGI / 4)} + rate -= status->agi*1000/4; + rate = max(rate,50000); // minimum of 50% + break; + case SC_ELECTRICSHOCKER: + if( bl->type == BL_MOB ) + tick -= 1000 * (status->agi/10); + break; + case SC_CRYSTALIZE: + tick -= (1000*(status->vit/10))+(status_get_lv(bl)/50); + break; + case SC_MANDRAGORA: + sc_def = (status->vit+status->luk)/5; + break; + case SC_KYOUGAKU: + tick -= 30*status->int_; + break; + case SC_PARALYSIS: + tick -= 50 * (status->vit + status->luk); //(1000/20); + break; + default: + //Effect that cannot be reduced? Likely a buff. + if (!(rnd()%10000 < rate)) + return 0; + return tick?tick:1; + } + + if (sd) { + + if (battle_config.pc_sc_def_rate != 100) + sc_def = sc_def*battle_config.pc_sc_def_rate/100; + + if (sc_def < battle_config.pc_max_sc_def) + sc_def += (battle_config.pc_max_sc_def - sc_def)* + status->luk/battle_config.pc_luk_sc_def; + else + sc_def = battle_config.pc_max_sc_def; + + if (tick_def) { + if (battle_config.pc_sc_def_rate != 100) + tick_def = tick_def*battle_config.pc_sc_def_rate/100; + } + + } else { + + if (battle_config.mob_sc_def_rate != 100) + sc_def = sc_def*battle_config.mob_sc_def_rate/100; + + if (sc_def < battle_config.mob_max_sc_def) + sc_def += (battle_config.mob_max_sc_def - sc_def)* + status->luk/battle_config.mob_luk_sc_def; + else + sc_def = battle_config.mob_max_sc_def; + + if (tick_def) { + if (battle_config.mob_sc_def_rate != 100) + tick_def = tick_def*battle_config.mob_sc_def_rate/100; + } + } + + if (sc) { + if (sc->data[SC_SCRESIST]) + sc_def += sc->data[SC_SCRESIST]->val1; //Status resist + else if (sc->data[SC_SIEGFRIED]) + sc_def += sc->data[SC_SIEGFRIED]->val3; //Status resistance. + } + + //When no tick def, reduction is the same for both. + if( !tick_def && type != SC_STONE ) //Recent tests show duration of petrify isn't reduced by MDEF. [Inkfish] + tick_def = sc_def; + + //Natural resistance + if (!(flag&8)) { + rate -= rate*sc_def/100; + + //Item resistance (only applies to rate%) + if(sd && SC_COMMON_MIN <= type && type <= SC_COMMON_MAX) + { + if( sd->reseff[type-SC_COMMON_MIN] > 0 ) + rate -= rate*sd->reseff[type-SC_COMMON_MIN]/10000; + if( sd->sc.data[SC_COMMONSC_RESIST] ) + rate -= rate*sd->sc.data[SC_COMMONSC_RESIST]->val1/100; + } + } + if (!(rnd()%10000 < rate)) + return 0; + + //Why would a status start with no duration? Presume it has + //duration defined elsewhere. + if (!tick) return 1; + + //Rate reduction + if (flag&2) + return tick; + + tick -= tick*tick_def/100; + // Changed to 5 seconds according to recent tests [Playtester] + if (type == SC_ANKLE && tick < 5000) + tick = 5000; + return tick<=0?0:tick; +} + +/*========================================== + * Starts a status change. + * 'type' = type, 'val1~4' depend on the type. + * 'rate' = base success rate. 10000 = 100% + * 'tick' is base duration + * 'flag': + * &1: Cannot be avoided (it has to start) + * &2: Tick should not be reduced (by vit, luk, lv, etc) + * &4: sc_data loaded, no value has to be altered. + * &8: rate should not be reduced + *------------------------------------------*/ +int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,int tick,int flag) +{ + struct map_session_data *sd = NULL; + struct status_change* sc; + struct status_change_entry* sce; + struct status_data *status; + struct view_data *vd; + int opt_flag, calc_flag, undead_flag, val_flag = 0, tick_time = 0; + bool sc_isnew = true; + + nullpo_ret(bl); + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if( type <= SC_NONE || type >= SC_MAX ) + { + ShowError("status_change_start: invalid status change (%d)!\n", type); + return 0; + } + + if( !sc ) + return 0; //Unable to receive status changes + + if( status_isdead(bl) && type != SC_NOCHAT ) // SC_NOCHAT should work even on dead characters + return 0; + + if( bl->type == BL_MOB) + { + struct mob_data *md = BL_CAST(BL_MOB,bl); + if(md && (md->class_ == MOBID_EMPERIUM || mob_is_battleground(md)) && type != SC_SAFETYWALL && type != SC_PNEUMA) + return 0; //Emperium/BG Monsters can't be afflicted by status changes + // if(md && mob_is_gvg(md) && status_sc2scb_flag(type)&SCB_MAXHP) + // return 0; //prevent status addinh hp to gvg mob (like bloodylust=hp*3 etc... + } + + if( sc->data[SC_REFRESH] ) { + if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX) // Confirmed. + return 0; // Immune to status ailements + switch( type ) { + case SC_QUAGMIRE://Tester said it protects against this and decrease agi. + case SC_DECREASEAGI: + case SC_BURNING: + case SC_FREEZING: + //case SC_WHITEIMPRISON://Need confirm. Protected against this in the past. [Rytech] + case SC_MARSHOFABYSS: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC_CRYSTALIZE: ////08/31/2011 - Class Balance Changes + case SC_DEEPSLEEP: + case SC_MANDRAGORA: + return 0; + } + } + else if( sc->data[SC_INSPIRATION] ) { + if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX ) + return 0; // Immune to status ailements + switch( type ) { + case SC_DEEPSLEEP: + case SC_SATURDAYNIGHTFEVER: + case SC_PYREXIA: + case SC_DEATHHURT: + case SC_MAGICMUSHROOM: + case SC_VENOMBLEED: + case SC_TOXIN: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC__ENERVATION: + case SC__GROOMY: + case SC__LAZINESS: + case SC__UNLUCKY: + case SC__WEAKNESS: + case SC__BODYPAINT: + case SC__IGNORANCE: + return 0; + } + } + + sd = BL_CAST(BL_PC, bl); + + //Adjust tick according to status resistances + if( !(flag&(1|4)) ) + { + tick = status_get_sc_def(bl, type, rate, tick, flag); + if( !tick ) return 0; + } + + undead_flag = battle_check_undead(status->race,status->def_ele); + //Check for inmunities / sc fails + switch (type) { + case SC_ANGRIFFS_MODUS: + case SC_GOLDENE_FERSE: + if ((type==SC_GOLDENE_FERSE && sc->data[SC_ANGRIFFS_MODUS]) + || (type==SC_ANGRIFFS_MODUS && sc->data[SC_GOLDENE_FERSE]) + ) + return 0; + case SC_STONE: + if(sc->data[SC_POWER_OF_GAIA]) + return 0; + case SC_FREEZE: + //Undead are immune to Freeze/Stone + if (undead_flag && !(flag&1)) + return 0; + case SC_DEEPSLEEP: + case SC_SLEEP: + case SC_STUN: + case SC_FREEZING: + case SC_CRYSTALIZE: + if (sc->opt1) + return 0; //Cannot override other opt1 status changes. [Skotlex] + if((type == SC_FREEZE || type == SC_FREEZING || type == SC_CRYSTALIZE) && sc->data[SC_WARMER]) + return 0; //Immune to Frozen and Freezing status if under Warmer status. [Jobbie] + break; + + //There all like berserk, do not everlap each other + case SC__BLOODYLUST: + if(!sd) return 0; //should only affect player + case SC_BERSERK: + if (((type == SC_BERSERK) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC__BLOODYLUST])) + || ((type == SC__BLOODYLUST) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC_BERSERK])) + ) + return 0; + break; + + case SC_BURNING: + if(sc->opt1 || sc->data[SC_FREEZING]) + return 0; + break; + + case SC_SIGNUMCRUCIS: + //Only affects demons and undead element (but not players) + if((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) + return 0; + break; + case SC_AETERNA: + if( (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) || sc->data[SC_FREEZE] ) + return 0; + break; + case SC_KYRIE: + if (bl->type == BL_MOB) + return 0; + break; + case SC_OVERTHRUST: + if (sc->data[SC_MAXOVERTHRUST]) + return 0; //Overthrust can't take effect if under Max Overthrust. [Skotlex] + case SC_MAXOVERTHRUST: + if( sc->option&OPTION_MADOGEAR ) + return 0;//Overthrust and Overthrust Max cannot be used on Mado Gear [Ind] + break; + case SC_ADRENALINE: + if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE))) + return 0; + if (sc->data[SC_QUAGMIRE] || + sc->data[SC_DECREASEAGI] || + sc->option&OPTION_MADOGEAR //Adrenaline doesn't affect Mado Gear [Ind] + ) + return 0; + break; + case SC_ADRENALINE2: + if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE2))) + return 0; + if (sc->data[SC_QUAGMIRE] || + sc->data[SC_DECREASEAGI] + ) + return 0; + break; + case SC_MAGNIFICAT: + if( sc->option&OPTION_MADOGEAR ) //Mado is immune to magnificat + return 0; + break; + case SC_ONEHAND: + case SC_MERC_QUICKEN: + case SC_TWOHANDQUICKEN: + if(sc->data[SC_DECREASEAGI]) + return 0; + + case SC_INCREASEAGI: + if(sd && pc_issit(sd)){ + pc_setstand(sd); + } + + case SC_CONCENTRATE: + case SC_SPEARQUICKEN: + case SC_TRUESIGHT: + case SC_WINDWALK: + case SC_CARTBOOST: + case SC_ASSNCROS: + if (sc->data[SC_QUAGMIRE]) + return 0; + if(sc->option&OPTION_MADOGEAR) + return 0;//Mado is immune to increase agi, wind walk, cart boost, etc (others above) [Ind] + break; + case SC_CLOAKING: + //Avoid cloaking with no wall and low skill level. [Skotlex] + //Due to the cloaking card, we have to check the wall versus to known + //skill level rather than the used one. [Skotlex] + //if (sd && val1 < 3 && skill_check_cloaking(bl,NULL)) + if( sd && pc_checkskill(sd, AS_CLOAKING) < 3 && !skill_check_cloaking(bl,NULL) ) + return 0; + break; + case SC_MODECHANGE: + { + int mode; + struct status_data *bstatus = status_get_base_status(bl); + if (!bstatus) return 0; + if (sc->data[type]) + { //Pile up with previous values. + if(!val2) val2 = sc->data[type]->val2; + val3 |= sc->data[type]->val3; + val4 |= sc->data[type]->val4; + } + mode = val2?val2:bstatus->mode; //Base mode + if (val4) mode&=~val4; //Del mode + if (val3) mode|= val3; //Add mode + if (mode == bstatus->mode) { //No change. + if (sc->data[type]) //Abort previous status + return status_change_end(bl, type, INVALID_TIMER); + return 0; + } + } + break; + //Strip skills, need to divest something or it fails. + case SC_STRIPWEAPON: + if (sd && !(flag&4)) { //apply sc anyway if loading saved sc_data + int i; + opt_flag = 0; //Reuse to check success condition. + if(sd->bonus.unstripable_equip&EQP_WEAPON) + return 0; + i = sd->equip_index[EQI_HAND_L]; + if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { + opt_flag|=1; + pc_unequipitem(sd,i,3); //L-hand weapon + } + + i = sd->equip_index[EQI_HAND_R]; + if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { + opt_flag|=2; + pc_unequipitem(sd,i,3); + } + if (!opt_flag) return 0; + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPSHIELD: + if( val2 == 1 ) val2 = 0; //GX effect. Do not take shield off.. + else + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_SHIELD) + return 0; + i = sd->equip_index[EQI_HAND_L]; + if ( i < 0 || !sd->inventory_data[i] || sd->inventory_data[i]->type != IT_ARMOR ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPARMOR: + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_ARMOR) + return 0; + i = sd->equip_index[EQI_ARMOR]; + if ( i < 0 || !sd->inventory_data[i] ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPHELM: + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_HELM) + return 0; + i = sd->equip_index[EQI_HEAD_TOP]; + if ( i < 0 || !sd->inventory_data[i] ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HPUP: + case SC_MERC_SPUP: + case SC_MERC_HITUP: + if( bl->type != BL_MER ) + return 0; // Stats only for Mercenaries + break; + case SC_STRFOOD: + if (sc->data[SC_FOOD_STR_CASH] && sc->data[SC_FOOD_STR_CASH]->val1 > val1) + return 0; + break; + case SC_AGIFOOD: + if (sc->data[SC_FOOD_AGI_CASH] && sc->data[SC_FOOD_AGI_CASH]->val1 > val1) + return 0; + break; + case SC_VITFOOD: + if (sc->data[SC_FOOD_VIT_CASH] && sc->data[SC_FOOD_VIT_CASH]->val1 > val1) + return 0; + break; + case SC_INTFOOD: + if (sc->data[SC_FOOD_INT_CASH] && sc->data[SC_FOOD_INT_CASH]->val1 > val1) + return 0; + break; + case SC_DEXFOOD: + if (sc->data[SC_FOOD_DEX_CASH] && sc->data[SC_FOOD_DEX_CASH]->val1 > val1) + return 0; + break; + case SC_LUKFOOD: + if (sc->data[SC_FOOD_LUK_CASH] && sc->data[SC_FOOD_LUK_CASH]->val1 > val1) + return 0; + break; + case SC_FOOD_STR_CASH: + if (sc->data[SC_STRFOOD] && sc->data[SC_STRFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_AGI_CASH: + if (sc->data[SC_AGIFOOD] && sc->data[SC_AGIFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_VIT_CASH: + if (sc->data[SC_VITFOOD] && sc->data[SC_VITFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_INT_CASH: + if (sc->data[SC_INTFOOD] && sc->data[SC_INTFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_DEX_CASH: + if (sc->data[SC_DEXFOOD] && sc->data[SC_DEXFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_LUK_CASH: + if (sc->data[SC_LUKFOOD] && sc->data[SC_LUKFOOD]->val1 > val1) + return 0; + break; + case SC_CAMOUFLAGE: + if( sd && pc_checkskill(sd, RA_CAMOUFLAGE) < 3 && !skill_check_camouflage(bl,NULL) ) + return 0; + break; + case SC__STRIPACCESSORY: + if( sd ) { + int i = -1; + if( !(sd->bonus.unstripable_equip&EQI_ACC_L) ) { + i = sd->equip_index[EQI_ACC_L]; + if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) + pc_unequipitem(sd,i,3); //L-Accessory + } if( !(sd->bonus.unstripable_equip&EQI_ACC_R) ) { + i = sd->equip_index[EQI_ACC_R]; + if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) + pc_unequipitem(sd,i,3); //R-Accessory + } + if( i < 0 ) + return 0; + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + { // it doesn't stack or even renewed + int i = SC_TOXIN; + for(; i<= SC_LEECHESEND; i++) + if(sc->data[i]) return 0; + } + break; + case SC_SATURDAYNIGHTFEVER: + if (sc->data[SC_BERSERK] || sc->data[SC_INSPIRATION] || sc->data[SC__BLOODYLUST]) + return 0; + break; + } + + //Check for BOSS resistances + if(status->mode&MD_BOSS && !(flag&1)) { + if (type>=SC_COMMON_MIN && type <= SC_COMMON_MAX) + return 0; + switch (type) { + case SC_BLESSING: + case SC_DECREASEAGI: + case SC_PROVOKE: + case SC_COMA: + case SC_GRAVITATION: + case SC_SUITON: + case SC_RICHMANKIM: + case SC_ROKISWEIL: + case SC_FOGWALL: + case SC_FREEZING: + case SC_BURNING: + case SC_MARSHOFABYSS: + case SC_ADORAMUS: + case SC_PARALYSIS: + case SC_DEEPSLEEP: + case SC_CRYSTALIZE: + + // Exploit prevention - kRO Fix + case SC_PYREXIA: + case SC_DEATHHURT: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + + // Ranger Effects + case SC_BITE: + case SC_ELECTRICSHOCKER: + case SC_MAGNETICFIELD: + + return 0; + } + } + + //Before overlapping fail, one must check for status cured. + switch (type) { + case SC_BLESSING: + //TO-DO Blessing and Agi up should do 1 damage against players on Undead Status, even on PvM + //but cannot be plagiarized (this requires aegis investigation on packets and official behavior) [Brainstorm] + if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) { + status_change_end(bl, SC_CURSE, INVALID_TIMER); + if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + status_change_end(bl, SC_STONE, INVALID_TIMER); + } + break; + case SC_INCREASEAGI: + status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); + break; + case SC_QUAGMIRE: + status_change_end(bl, SC_CONCENTRATE, INVALID_TIMER); + status_change_end(bl, SC_TRUESIGHT, INVALID_TIMER); + status_change_end(bl, SC_WINDWALK, INVALID_TIMER); + //Also blocks the ones below... + case SC_DECREASEAGI: + status_change_end(bl, SC_CARTBOOST, INVALID_TIMER); + //Also blocks the ones below... + case SC_DONTFORGETME: + status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); + status_change_end(bl, SC_SPEARQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); + status_change_end(bl, SC_ACCELERATION, INVALID_TIMER); + break; + case SC_ONEHAND: + //Removes the Aspd potion effect, as reported by Vicious. [Skotlex] + status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); + break; + case SC_MAXOVERTHRUST: + //Cancels Normal Overthrust. [Skotlex] + status_change_end(bl, SC_OVERTHRUST, INVALID_TIMER); + break; + case SC_KYRIE: + //Cancels Assumptio + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + break; + case SC_DELUGE: + if (sc->data[SC_FOGWALL] && sc->data[SC_BLIND]) + status_change_end(bl, SC_BLIND, INVALID_TIMER); + break; + case SC_SILENCE: + if (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) + status_change_end(bl, SC_GOSPEL, INVALID_TIMER); + break; + case SC_HIDING: + status_change_end(bl, SC_CLOSECONFINE, INVALID_TIMER); + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); + break; + case SC__BLOODYLUST: + case SC_BERSERK: + if(battle_config.berserk_cancels_buffs) { + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_CONCENTRATION, INVALID_TIMER); + status_change_end(bl, SC_PARRYING, INVALID_TIMER); + status_change_end(bl, SC_AURABLADE, INVALID_TIMER); + status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); + } +#ifdef RENEWAL + else { + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + } +#endif + break; + case SC_ASSUMPTIO: + status_change_end(bl, SC_KYRIE, INVALID_TIMER); + status_change_end(bl, SC_KAITE, INVALID_TIMER); + break; + case SC_KAITE: + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + break; + case SC_CARTBOOST: + if(sc->data[SC_DECREASEAGI]) + { //Cancel Decrease Agi, but take no further effect [Skotlex] + status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); + return 0; + } + break; + case SC_FUSION: + status_change_end(bl, SC_SPIRIT, INVALID_TIMER); + break; + case SC_ADJUSTMENT: + status_change_end(bl, SC_MADNESSCANCEL, INVALID_TIMER); + break; + case SC_MADNESSCANCEL: + status_change_end(bl, SC_ADJUSTMENT, INVALID_TIMER); + break; + //NPC_CHANGEUNDEAD will debuff Blessing and Agi Up + case SC_CHANGEUNDEAD: + status_change_end(bl, SC_BLESSING, INVALID_TIMER); + status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); + break; + case SC_STRFOOD: + status_change_end(bl, SC_FOOD_STR_CASH, INVALID_TIMER); + break; + case SC_AGIFOOD: + status_change_end(bl, SC_FOOD_AGI_CASH, INVALID_TIMER); + break; + case SC_VITFOOD: + status_change_end(bl, SC_FOOD_VIT_CASH, INVALID_TIMER); + break; + case SC_INTFOOD: + status_change_end(bl, SC_FOOD_INT_CASH, INVALID_TIMER); + break; + case SC_DEXFOOD: + status_change_end(bl, SC_FOOD_DEX_CASH, INVALID_TIMER); + break; + case SC_LUKFOOD: + status_change_end(bl, SC_FOOD_LUK_CASH, INVALID_TIMER); + break; + case SC_FOOD_STR_CASH: + status_change_end(bl, SC_STRFOOD, INVALID_TIMER); + break; + case SC_FOOD_AGI_CASH: + status_change_end(bl, SC_AGIFOOD, INVALID_TIMER); + break; + case SC_FOOD_VIT_CASH: + status_change_end(bl, SC_VITFOOD, INVALID_TIMER); + break; + case SC_FOOD_INT_CASH: + status_change_end(bl, SC_INTFOOD, INVALID_TIMER); + break; + case SC_FOOD_DEX_CASH: + status_change_end(bl, SC_DEXFOOD, INVALID_TIMER); + break; + case SC_FOOD_LUK_CASH: + status_change_end(bl, SC_LUKFOOD, INVALID_TIMER); + break; + case SC_FIGHTINGSPIRIT: + status_change_end(bl, type, INVALID_TIMER); // Remove previous one. + break; + case SC_MARSHOFABYSS: + status_change_end(bl, SC_INCAGI, INVALID_TIMER); + status_change_end(bl, SC_WINDWALK, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); + break; + case SC_SWINGDANCE: + case SC_SYMPHONYOFLOVER: + case SC_MOONLITSERENADE: + case SC_RUSHWINDMILL: + case SC_ECHOSONG: + case SC_HARMONIZE: //group A doesn't overlap + if (type != SC_SWINGDANCE) status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + if (type != SC_SYMPHONYOFLOVER) status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + if (type != SC_MOONLITSERENADE) status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + if (type != SC_RUSHWINDMILL) status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + if (type != SC_ECHOSONG) status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + if (type != SC_HARMONIZE) status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + break; + case SC_VOICEOFSIREN: + case SC_DEEPSLEEP: + case SC_GLOOMYDAY: + case SC_SONGOFMANA: + case SC_DANCEWITHWUG: + case SC_SATURDAYNIGHTFEVER: + case SC_LERADSDEW: + case SC_MELODYOFSINK: + case SC_BEYONDOFWARCRY: + case SC_UNLIMITEDHUMMINGVOICE: //group B + if (type != SC_VOICEOFSIREN) status_change_end(bl, SC_VOICEOFSIREN, INVALID_TIMER); + if (type != SC_DEEPSLEEP) status_change_end(bl, SC_DEEPSLEEP, INVALID_TIMER); + if (type != SC_LERADSDEW) status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + if (type != SC_MELODYOFSINK) status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + if (type != SC_BEYONDOFWARCRY) status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + if (type != SC_UNLIMITEDHUMMINGVOICE) status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + if (type != SC_GLOOMYDAY) { + status_change_end(bl, SC_GLOOMYDAY, INVALID_TIMER); + status_change_end(bl, SC_GLOOMYDAY_SK, INVALID_TIMER); + } + if (type != SC_SONGOFMANA) status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); + if (type != SC_DANCEWITHWUG) status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + if (type != SC_SATURDAYNIGHTFEVER) { + if (sc->data[SC_SATURDAYNIGHTFEVER]) { + sc->data[SC_SATURDAYNIGHTFEVER]->val2 = 0; //mark to not lose hp + status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + } + } + break; + case SC_REFLECTSHIELD: + status_change_end(bl, SC_REFLECTDAMAGE, INVALID_TIMER); + break; + case SC_REFLECTDAMAGE: + status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); + break; + case SC_SHIELDSPELL_DEF: + case SC_SHIELDSPELL_MDEF: + case SC_SHIELDSPELL_REF: + status_change_end(bl, SC_MAGNIFICAT, INVALID_TIMER); + if( type != SC_SHIELDSPELL_DEF ) + status_change_end(bl, SC_SHIELDSPELL_DEF, INVALID_TIMER); + if( type != SC_SHIELDSPELL_MDEF ) + status_change_end(bl, SC_SHIELDSPELL_MDEF, INVALID_TIMER); + if( type != SC_SHIELDSPELL_REF ) + status_change_end(bl, SC_SHIELDSPELL_REF, INVALID_TIMER); + break; + case SC_GT_ENERGYGAIN: + case SC_GT_CHANGE: + case SC_GT_REVITALIZE: + if( type != SC_GT_REVITALIZE ) + status_change_end(bl, SC_GT_REVITALIZE, INVALID_TIMER); + if( type != SC_GT_ENERGYGAIN ) + status_change_end(bl, SC_GT_ENERGYGAIN, INVALID_TIMER); + if( type != SC_GT_CHANGE ) + status_change_end(bl, SC_GT_CHANGE, INVALID_TIMER); + break; + case SC_INVINCIBLE: + status_change_end(bl, SC_INVINCIBLEOFF, INVALID_TIMER); + break; + case SC_INVINCIBLEOFF: + status_change_end(bl, SC_INVINCIBLE, INVALID_TIMER); + break; + case SC_MAGICPOWER: + status_change_end(bl, type, INVALID_TIMER); + break; + } + + //Check for overlapping fails + if( (sce = sc->data[type]) ) { + switch( type ) { + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HPUP: + case SC_MERC_SPUP: + case SC_MERC_HITUP: + if( sce->val1 > val1 ) + val1 = sce->val1; + break; + case SC_ADRENALINE: + case SC_ADRENALINE2: + case SC_WEAPONPERFECTION: + case SC_OVERTHRUST: + if (sce->val2 > val2) + return 0; + break; + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + case SC_BOSSMAPINFO: + case SC_STUN: + case SC_SLEEP: + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_CONFUSION: + case SC_BLIND: + case SC_BLEEDING: + case SC_DPOISON: + case SC_CLOSECONFINE2: //Can't be re-closed in. + case SC_MARIONETTE: + case SC_MARIONETTE2: + case SC_NOCHAT: + case SC_CHANGE: //Otherwise your Hp/Sp would get refilled while still within effect of the last invocation. + case SC__INVISIBILITY: + case SC__ENERVATION: + case SC__GROOMY: + case SC__IGNORANCE: + case SC__LAZINESS: + case SC__WEAKNESS: + case SC__UNLUCKY: + return 0; + case SC_COMBO: + case SC_DANCING: + case SC_DEVOTION: + case SC_ASPDPOTION0: + case SC_ASPDPOTION1: + case SC_ASPDPOTION2: + case SC_ASPDPOTION3: + case SC_ATKPOTION: + case SC_MATKPOTION: + case SC_ENCHANTARMS: + case SC_ARMOR_ELEMENT: + case SC_ARMOR_RESIST: + break; + case SC_GOSPEL: + //Must not override a casting gospel char. + if(sce->val4 == BCT_SELF) + return 0; + if(sce->val1 > val1) + return 1; + break; + case SC_ENDURE: + if(sce->val4 && !val4) + return 1; //Don't let you override infinite endure. + if(sce->val1 > val1) + return 1; + break; + case SC_KAAHI: + //Kaahi overwrites previous level regardless of existing level. + //Delete timer if it exists. + if (sce->val4 != INVALID_TIMER) { + delete_timer(sce->val4,kaahi_heal_timer); + sce->val4 = INVALID_TIMER; + } + break; + case SC_JAILED: + //When a player is already jailed, do not edit the jail data. + val2 = sce->val2; + val3 = sce->val3; + val4 = sce->val4; + break; + case SC_LERADSDEW: + if (sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) + return 0; + case SC_SHAPESHIFT: + case SC_PROPERTYWALK: + break; + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + if( sce->val4 && !val4 )//you cannot override master guild aura + return 0; + break; + case SC_JOINTBEAT: + val2 |= sce->val2; // stackable ailments + default: + if(sce->val1 > val1) + return 1; //Return true to not mess up skill animations. [Skotlex] + } + } + + vd = status_get_viewdata(bl); + calc_flag = StatusChangeFlagTable[type]; + if(!(flag&4)) //&4 - Do not parse val settings when loading SCs + switch(type) + { + case SC_DECREASEAGI: + case SC_INCREASEAGI: + val2 = 2 + val1; //Agi change + break; + case SC_ENDURE: + val2 = 7; // Hit-count [Celest] + if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) && !map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && !val4 ) + { + struct map_session_data *tsd; + if( sd ) + { + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); + } + //val4 signals infinite endure (if val4 == 2 it is infinite endure from Berserk) + if( val4 ) + tick = -1; + break; + case SC_AUTOBERSERK: + if (status->hp < status->max_hp>>2 && + (!sc->data[SC_PROVOKE] || sc->data[SC_PROVOKE]->val2==0)) + sc_start4(bl,SC_PROVOKE,100,10,1,0,0,60000); + tick = -1; + break; + case SC_SIGNUMCRUCIS: + val2 = 10 + 4*val1; //Def reduction + tick = -1; + clif_emotion(bl,E_SWT); + break; + case SC_MAXIMIZEPOWER: + tick_time = val2 = tick>0?tick:60000; + tick = -1; // duration sent to the client should be infinite + break; + case SC_EDP: // [Celest] + val2 = val1 + 2; //Chance to Poison enemies. +#ifndef RENEWAL_EDP + val3 = 50*(val1+1); //Damage increase (+50 +50*lv%) +#endif + if( sd )//[Ind] - iROwiki says each level increases its duration by 3 seconds + tick += pc_checkskill(sd,GC_RESEARCHNEWPOISON)*3000; + break; + case SC_POISONREACT: + val2=(val1+1)/2 + val1/10; // Number of counters [Skotlex] + val3=50; // + 5*val1; //Chance to counter. [Skotlex] + break; + case SC_MAGICROD: + val2 = val1*20; //SP gained + break; + case SC_KYRIE: + val2 = (int64)status->max_hp * (val1 * 2 + 10) / 100; //%Max HP to absorb + val3 = (val1 / 2 + 5); //Hits + break; + case SC_MAGICPOWER: + //val1: Skill lv + val2 = 1; //Lasts 1 invocation + val3 = 5*val1; //Matk% increase + val4 = 0; // 0 = ready to be used, 1 = activated and running + break; + case SC_SACRIFICE: + val2 = 5; //Lasts 5 hits + tick = -1; + break; + case SC_ENCPOISON: + val2= 250+50*val1; //Poisoning Chance (2.5+0.5%) in 1/10000 rate + case SC_ASPERSIO: + case SC_FIREWEAPON: + case SC_WATERWEAPON: + case SC_WINDWEAPON: + case SC_EARTHWEAPON: + case SC_SHADOWWEAPON: + case SC_GHOSTWEAPON: + skill_enchant_elemental_end(bl,type); + break; + case SC_ELEMENTALCHANGE: + // val1 : Element Lvl (if called by skill lvl 1, takes random value between 1 and 4) + // val2 : Element (When no element, random one is picked) + // val3 : 0 = called by skill 1 = called by script (fixed level) + if( !val2 ) val2 = rnd()%ELE_MAX; + + if( val1 == 1 && val3 == 0 ) + val1 = 1 + rnd()%4; + else if( val1 > 4 ) + val1 = 4; // Max Level + val3 = 0; // Not need to keep this info. + break; + case SC_PROVIDENCE: + val2=val1*5; //Race/Ele resist + break; + case SC_REFLECTSHIELD: + val2=10+val1*3; // %Dmg reflected + if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) ) + { + struct map_session_data *tsd; + if( sd ) + { + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + break; + case SC_STRIPWEAPON: + if (!sd) //Watk reduction + val2 = 25; + break; + case SC_STRIPSHIELD: + if (!sd) //Def reduction + val2 = 15; + break; + case SC_STRIPARMOR: + if (!sd) //Vit reduction + val2 = 40; + break; + case SC_STRIPHELM: + if (!sd) //Int reduction + val2 = 40; + break; + case SC_AUTOSPELL: + //Val1 Skill LV of Autospell + //Val2 Skill ID to cast + //Val3 Max Lv to cast + val4 = 5 + val1*2; //Chance of casting + break; + case SC_VOLCANO: + val2 = val1*10; //Watk increase +#ifndef RENEWAL + if (status->def_ele != ELE_FIRE) + val2 = 0; +#endif + break; + case SC_VIOLENTGALE: + val2 = val1*3; //Flee increase + #ifndef RENEWAL + if (status->def_ele != ELE_WIND) + val2 = 0; + #endif + break; + case SC_DELUGE: + val2 = deluge_eff[val1-1]; //HP increase +#ifndef RENEWAL + if(status->def_ele != ELE_WATER) + val2 = 0; +#endif + break; + case SC_SUITON: + if (!val2 || (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_NINJA)) { + //No penalties. + val2 = 0; //Agi penalty + val3 = 0; //Walk speed penalty + break; + } + val3 = 50; + val2 = 3*((val1+1)/3); + if (val1 > 4) val2--; + break; + case SC_ONEHAND: + case SC_TWOHANDQUICKEN: + val2 = 300; + if (val1 > 10) //For boss casted skills [Skotlex] + val2 += 20*(val1-10); + break; + case SC_MERC_QUICKEN: + val2 = 300; + break; +#ifndef RENEWAL + case SC_SPEARQUICKEN: + val2 = 200+10*val1; + break; +#endif + case SC_DANCING: + //val1 : Skill ID + LV + //val2 : Skill Group of the Dance. + //val3 : Brings the skill_lv (merged into val1 here) + //val4 : Partner + if (val1 == CG_MOONLIT) + clif_status_change(bl,SI_MOONLIT,1,tick,0, 0, 0); + val1|= (val3<<16); + val3 = tick/1000; //Tick duration + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_LONGING: + val2 = 500-100*val1; //Aspd penalty. + break; + case SC_EXPLOSIONSPIRITS: + val2 = 75 + 25*val1; //Cri bonus + break; + + case SC_ASPDPOTION0: + case SC_ASPDPOTION1: + case SC_ASPDPOTION2: + case SC_ASPDPOTION3: + val2 = 50*(2+type-SC_ASPDPOTION0); + break; + + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + if (!vd) return 0; + //Store previous values as they could be removed. + val1 = vd->class_; + val2 = vd->weapon; + val3 = vd->shield; + val4 = vd->cloth_color; + unit_stop_attack(bl); + clif_changelook(bl,LOOK_WEAPON,0); + clif_changelook(bl,LOOK_SHIELD,0); + clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); + clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); + break; + case SC_NOCHAT: + // [GodLesZ] FIXME: is this correct? a hardcoded interval of 60sec? what about configuration ?_? + tick = 60000; + val1 = battle_config.manner_system; //Mute filters. + if (sd) + { + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + } + break; + + case SC_STONE: + val3 = tick/1000; //Petrified HP-damage iterations. + if(val3 < 1) val3 = 1; + tick = val4; //Petrifying time. + tick = max(tick, 1000); //Min time + calc_flag = 0; //Actual status changes take effect on petrified state. + break; + + case SC_DPOISON: + //Lose 10/15% of your life as long as it doesn't brings life below 25% + if (status->hp > status->max_hp>>2) { + int diff = status->max_hp*(bl->type==BL_PC?10:15)/100; + if (status->hp - diff < status->max_hp>>2) + diff = status->hp - (status->max_hp>>2); + if( val2 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(val2); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,diff); + } + status_zap(bl, diff, 0); + } + // fall through + case SC_POISON: + val3 = tick/1000; //Damage iterations + if(val3 < 1) val3 = 1; + tick_time = 1000; // [GodLesZ] tick time + //val4: HP damage + if (bl->type == BL_PC) + val4 = (type == SC_DPOISON) ? 3 + status->max_hp/50 : 3 + status->max_hp*3/200; + else + val4 = (type == SC_DPOISON) ? 3 + status->max_hp/100 : 3 + status->max_hp/200; + + break; + case SC_CONFUSION: + clif_emotion(bl,E_WHAT); + break; + case SC_BLEEDING: + val4 = tick/10000; + if (!val4) val4 = 1; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + if( val1 == 0 ) return 0; + // val1 = heal percent/amout + // val2 = seconds between heals + // val4 = total of heals + if( val2 < 1 ) val2 = 1; + if( (val4 = tick/(val2 * 1000)) < 1 ) + val4 = 1; + tick_time = val2 * 1000; // [GodLesZ] tick time + break; + case SC_BOSSMAPINFO: + if( sd != NULL ) + { + struct mob_data *boss_md = map_getmob_boss(bl->m); // Search for Boss on this Map + if( boss_md == NULL || boss_md->bl.prev == NULL ) + { // No MVP on this map - MVP is dead + clif_bossmapinfo(sd->fd, boss_md, 1); + return 0; // No need to start SC + } + val1 = boss_md->bl.id; + if( (val4 = tick/1000) < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + } + break; + case SC_HIDING: + val2 = tick/1000; + tick_time = 1000; // [GodLesZ] tick time + val3 = 0; // unused, previously speed adjustment + val4 = val1+3; //Seconds before SP substraction happen. + break; + case SC_CHASEWALK: + val2 = tick>0?tick:10000; //Interval at which SP is drained. + val3 = 35 - 5 * val1; //Speed adjustment. + if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE) + val3 -= 40; + val4 = 10+val1*2; //SP cost. + if (map_flag_gvg(bl->m) || map[bl->m].flag.battleground) val4 *= 5; + break; + case SC_CLOAKING: + if (!sd) //Monsters should be able to walk with no penalties. [Skotlex] + val1 = 10; + tick_time = val2 = tick>0?tick:60000; //SP consumption rate. + tick = -1; // duration sent to the client should be infinite + val3 = 0; // unused, previously walk speed adjustment + //val4&1 signals the presence of a wall. + //val4&2 makes cloak not end on normal attacks [Skotlex] + //val4&4 makes cloak not end on using skills + if (bl->type == BL_PC || (bl->type == BL_MOB && ((TBL_MOB*)bl)->special_state.clone) ) //Standard cloaking. + val4 |= battle_config.pc_cloak_check_type&7; + else + val4 |= battle_config.monster_cloak_check_type&7; + break; + case SC_SIGHT: /* splash status */ + case SC_RUWACH: + case SC_SIGHTBLASTER: + val3 = skill_get_splash(val2, val1); //Val2 should bring the skill-id. + val2 = tick/250; + tick_time = 10; // [GodLesZ] tick time + break; + + //Permanent effects. + case SC_AETERNA: + case SC_MODECHANGE: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_BROKENWEAPON: + case SC_BROKENARMOR: + case SC_READYSTORM: + case SC_READYDOWN: + case SC_READYCOUNTER: + case SC_READYTURN: + case SC_DODGE: + case SC_PUSH_CART: + tick = -1; + break; + + case SC_AUTOGUARD: + if( !(flag&1) ) + { + struct map_session_data *tsd; + int i,t; + for( i = val2 = 0; i < val1; i++) + { + t = 5-(i>>1); + val2 += (t < 0)? 1:t; + } + + if( bl->type&(BL_PC|BL_MER) ) + { + if( sd ) + { + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + break; + + case SC_DEFENDER: + if (!(flag&1)) + { + val2 = 5 + 15*val1; //Damage reduction + val3 = 0; // unused, previously speed adjustment + val4 = 250 - 50*val1; //Aspd adjustment + + if (sd) + { + struct map_session_data *tsd; + int i; + for (i = 0; i < 5; i++) + { //See if there are devoted characters, and pass the status to them. [Skotlex] + if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i]))) + status_change_start(&tsd->bl,type,10000,val1,5+val1*5,val3,val4,tick,1); + } + } + } + break; + + case SC_TENSIONRELAX: + if (sd) { + pc_setsit(sd); + clif_sitting(&sd->bl); + } + val2 = 12; //SP cost + val4 = 10000; //Decrease at 10secs intervals. + val3 = tick/val4; + tick = -1; // duration sent to the client should be infinite + tick_time = val4; // [GodLesZ] tick time + break; + case SC_PARRYING: + val2 = 20 + val1*3; //Block Chance + break; + + case SC_WINDWALK: + val2 = (val1+1)/2; // Flee bonus is 1/1/2/2/3/3/4/4/5/5 + break; + + case SC_JOINTBEAT: + if( val2&BREAK_NECK ) + sc_start(bl,SC_BLEEDING,100,val1,skill_get_time2(status_sc2skill(type),val1)); + break; + + case SC_BERSERK: + if (!sc->data[SC_ENDURE] || !sc->data[SC_ENDURE]->val4) + sc_start4(bl, SC_ENDURE, 100,10,0,0,2, tick); + case SC__BLOODYLUST: + //HP healing is performing after the calc_status call. + //Val2 holds HP penalty + if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); + if (!val4) val4 = 10000; //Val4 holds damage interval + val3 = tick/val4; //val3 holds skill duration + tick_time = val4; // [GodLesZ] tick time + break; + + case SC_GOSPEL: + if(val4 == BCT_SELF) { // self effect + val2 = tick/10000; + tick_time = 10000; // [GodLesZ] tick time + status_change_clear_buffs(bl,3); //Remove buffs/debuffs + } + break; + + case SC_MARIONETTE: + { + int stat; + + val3 = 0; + val4 = 0; + stat = ( sd ? sd->status.str : status_get_base_status(bl)->str ) / 2; val3 |= cap_value(stat,0,0xFF)<<16; + stat = ( sd ? sd->status.agi : status_get_base_status(bl)->agi ) / 2; val3 |= cap_value(stat,0,0xFF)<<8; + stat = ( sd ? sd->status.vit : status_get_base_status(bl)->vit ) / 2; val3 |= cap_value(stat,0,0xFF); + stat = ( sd ? sd->status.int_: status_get_base_status(bl)->int_) / 2; val4 |= cap_value(stat,0,0xFF)<<16; + stat = ( sd ? sd->status.dex : status_get_base_status(bl)->dex ) / 2; val4 |= cap_value(stat,0,0xFF)<<8; + stat = ( sd ? sd->status.luk : status_get_base_status(bl)->luk ) / 2; val4 |= cap_value(stat,0,0xFF); + break; + } + case SC_MARIONETTE2: + { + int stat,max_stat; + // fetch caster information + struct block_list *pbl = map_id2bl(val1); + struct status_change *psc = pbl?status_get_sc(pbl):NULL; + struct status_change_entry *psce = psc?psc->data[SC_MARIONETTE]:NULL; + // fetch target's stats + struct status_data* status = status_get_status_data(bl); // battle status + + if (!psce) + return 0; + + val3 = 0; + val4 = 0; + max_stat = battle_config.max_parameter; //Cap to 99 (default) + stat = (psce->val3 >>16)&0xFF; stat = min(stat, max_stat - status->str ); val3 |= cap_value(stat,0,0xFF)<<16; + stat = (psce->val3 >> 8)&0xFF; stat = min(stat, max_stat - status->agi ); val3 |= cap_value(stat,0,0xFF)<<8; + stat = (psce->val3 >> 0)&0xFF; stat = min(stat, max_stat - status->vit ); val3 |= cap_value(stat,0,0xFF); + stat = (psce->val4 >>16)&0xFF; stat = min(stat, max_stat - status->int_); val4 |= cap_value(stat,0,0xFF)<<16; + stat = (psce->val4 >> 8)&0xFF; stat = min(stat, max_stat - status->dex ); val4 |= cap_value(stat,0,0xFF)<<8; + stat = (psce->val4 >> 0)&0xFF; stat = min(stat, max_stat - status->luk ); val4 |= cap_value(stat,0,0xFF); + break; + } + case SC_REJECTSWORD: + val2 = 15*val1; //Reflect chance + val3 = 3; //Reflections + tick = -1; + break; + + case SC_MEMORIZE: + val2 = 5; //Memorized casts. + tick = -1; + break; + + case SC_GRAVITATION: + val2 = 50*val1; //aspd reduction + break; + + case SC_REGENERATION: + if (val1 == 1) + val2 = 2; + else + val2 = val1; //HP Regerenation rate: 200% 200% 300% + val3 = val1; //SP Regeneration Rate: 100% 200% 300% + //if val4 comes set, this blocks regen rather than increase it. + break; + + case SC_DEVOTION: + { + struct block_list *d_bl; + struct status_change *d_sc; + + if( (d_bl = map_id2bl(val1)) && (d_sc = status_get_sc(d_bl)) && d_sc->count ) + { // Inherits Status From Source + const enum sc_type types[] = { SC_AUTOGUARD, SC_DEFENDER, SC_REFLECTSHIELD, SC_ENDURE }; + enum sc_type type2; + int i = (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)?2:3; + while( i >= 0 ) + { + type2 = types[i]; + if( d_sc->data[type2] ) + sc_start(bl, type2, 100, d_sc->data[type2]->val1, skill_get_time(status_sc2skill(type2),d_sc->data[type2]->val1)); + i--; + } + } + break; + } + + case SC_COMA: //Coma. Sends a char to 1HP. If val2, do not zap sp + if( val3 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(val3); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,status->hp - 1); + } + status_zap(bl, status->hp-1, val2?0:status->sp); + return 1; + break; + case SC_CLOSECONFINE2: + { + struct block_list *src = val2?map_id2bl(val2):NULL; + struct status_change *sc2 = src?status_get_sc(src):NULL; + struct status_change_entry *sce2 = sc2?sc2->data[SC_CLOSECONFINE]:NULL; + if (src && sc2) { + if (!sce2) //Start lock on caster. + sc_start4(src,SC_CLOSECONFINE,100,val1,1,0,0,tick+1000); + else { //Increase count of locked enemies and refresh time. + (sce2->val2)++; + delete_timer(sce2->timer, status_change_timer); + sce2->timer = add_timer(gettick()+tick+1000, status_change_timer, src->id, SC_CLOSECONFINE); + } + } else //Status failed. + return 0; + } + break; + case SC_KAITE: + val2 = 1+val1/5; //Number of bounces: 1 + skill_lv/5 + break; + case SC_KAUPE: + switch (val1) { + case 3: //33*3 + 1 -> 100% + val2++; + case 1: + case 2: //33, 66% + val2 += 33*val1; + val3 = 1; //Dodge 1 attack total. + break; + default: //Custom. For high level mob usage, higher level means more blocks. [Skotlex] + val2 = 100; + val3 = val1-2; + break; + } + break; + + case SC_COMBO: { + //val1: Skill ID + //val2: When given, target (for autotargetting skills) + //val3: When set, this combo time should NOT delay attack/movement + //val3: TK: Last used kick + //val4: TK: Combo time + struct unit_data *ud = unit_bl2ud(bl); + if (ud && !val3) { + tick += 300 * battle_config.combo_delay_rate/100; + ud->attackabletime = gettick()+tick; + unit_set_walkdelay(bl, gettick(), tick, 1); + } + val3 = 0; + val4 = tick; + } + break; + case SC_EARTHSCROLL: + val2 = 11-val1; //Chance to consume: 11-skill_lv% + break; + case SC_RUN: + val4 = gettick(); //Store time at which you started running. + tick = -1; + break; + case SC_KAAHI: + val2 = 200*val1; //HP heal + val3 = 5*val1; //SP cost + val4 = INVALID_TIMER; //Kaahi Timer. + break; + case SC_BLESSING: + if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) + val2 = val1; + else + val2 = 0; //0 -> Half stat. + break; + case SC_TRICKDEAD: + if (vd) vd->dead_sit = 1; + tick = -1; + break; + case SC_CONCENTRATE: + val2 = 2 + val1; + if (sd) { //Store the card-bonus data that should not count in the % + val3 = sd->param_bonus[1]; //Agi + val4 = sd->param_bonus[4]; //Dex + } else { + val3 = val4 = 0; + } + break; + case SC_MAXOVERTHRUST: + val2 = 20*val1; //Power increase + break; + case SC_OVERTHRUST: + //val2 holds if it was casted on self, or is bonus received from others + val3 = 5*val1; //Power increase + if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) + tick += tick / 10; + break; + case SC_ADRENALINE2: + case SC_ADRENALINE: + val3 = (val2) ? 300 : 200; // aspd increase + case SC_WEAPONPERFECTION: + if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) + tick += tick / 10; + break; + case SC_CONCENTRATION: + val2 = 5*val1; //Batk/Watk Increase + val3 = 10*val1; //Hit Increase + val4 = 5*val1; //Def reduction + break; + case SC_ANGELUS: + val2 = 5*val1; //def increase + break; + case SC_IMPOSITIO: + val2 = 5*val1; //watk increase + break; + case SC_MELTDOWN: + val2 = 100*val1; //Chance to break weapon + val3 = 70*val1; //Change to break armor + break; + case SC_TRUESIGHT: + val2 = 10*val1; //Critical increase + val3 = 3*val1; //Hit increase + break; + case SC_SUN_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk)/2; //def increase + break; + case SC_MOON_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk)/10; //flee increase + break; + case SC_STAR_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk); //Aspd increase + break; + case SC_QUAGMIRE: + val2 = (sd?5:10)*val1; //Agi/Dex decrease. + break; + + // gs_something1 [Vicious] + case SC_GATLINGFEVER: + val2 = 20*val1; //Aspd increase + val3 = 20+10*val1; //Batk increase + val4 = 5*val1; //Flee decrease + break; + + case SC_FLING: + if (bl->type == BL_PC) + val2 = 0; //No armor reduction to players. + else + val2 = 5*val1; //Def reduction + val3 = 5*val1; //Def2 reduction + break; + case SC_PROVOKE: + //val2 signals autoprovoke. + val3 = 2+3*val1; //Atk increase + val4 = 5+5*val1; //Def reduction. + break; + case SC_AVOID: + //val2 = 10*val1; //Speed change rate. + break; + case SC_DEFENCE: + val2 = 2*val1; //Def bonus + break; + case SC_BLOODLUST: + val2 = 20+10*val1; //Atk rate change. + val3 = 3*val1; //Leech chance + val4 = 20; //Leech percent + break; + case SC_FLEET: + val2 = 30*val1; //Aspd change + val3 = 5+5*val1; //bAtk/wAtk rate change + break; + case SC_MINDBREAKER: + val2 = 20*val1; //matk increase. + val3 = 12*val1; //mdef2 reduction. + break; + case SC_SKA: + val2 = tick/1000; + val3 = rnd()%100; //Def changes randomly every second... + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_JAILED: + //Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time. + tick = val1>0?1000:250; + if (sd) + { + if (sd->mapindex != val2) + { + int pos = (bl->x&0xFFFF)|(bl->y<<16), //Current Coordinates + map = sd->mapindex; //Current Map + //1. Place in Jail (val2 -> Jail Map, val3 -> x, val4 -> y + pc_setpos(sd,(unsigned short)val2,val3,val4, CLR_TELEPORT); + //2. Set restore point (val3 -> return map, val4 return coords + val3 = map; + val4 = pos; + } else if (!val3 || val3 == sd->mapindex) { //Use save point. + val3 = sd->status.save_point.map; + val4 = (sd->status.save_point.x&0xFFFF) + |(sd->status.save_point.y<<16); + } + } + break; + case SC_UTSUSEMI: + val2=(val1+1)/2; // number of hits blocked + val3=skill_get_blewcount(NJ_UTSUSEMI, val1); //knockback value. + break; + case SC_BUNSINJYUTSU: + val2=(val1+1)/2; // number of hits blocked + break; + case SC_CHANGE: + val2= 30*val1; //Vit increase + val3= 20*val1; //Int increase + break; + case SC_SWOO: + if(status->mode&MD_BOSS) + tick /= 5; //TODO: Reduce skill's duration. But for how long? + break; + case SC_SPIDERWEB: + if( bl->type == BL_PC ) + tick /= 2; + break; + case SC_ARMOR: + //NPC_DEFENDER: + val2 = 80; //Damage reduction + //Attack requirements to be blocked: + val3 = BF_LONG; //Range + val4 = BF_WEAPON|BF_MISC; //Type + break; + case SC_ENCHANTARMS: + //end previous enchants + skill_enchant_elemental_end(bl,type); + //Make sure the received element is valid. + if (val2 >= ELE_MAX) + val2 = val2%ELE_MAX; + else if (val2 < 0) + val2 = rnd()%ELE_MAX; + break; + case SC_CRITICALWOUND: + val2 = 20*val1; //Heal effectiveness decrease + break; + case SC_MAGICMIRROR: + case SC_SLOWCAST: + val2 = 20*val1; //Magic reflection/cast rate + break; + + case SC_ARMORCHANGE: + if (val2 == NPC_ANTIMAGIC) + { //Boost mdef + val2 =-20; + val3 = 20; + } else { //Boost def + val2 = 20; + val3 =-20; + } + val2*=val1; //20% per level + val3*=val1; + break; + case SC_EXPBOOST: + case SC_JEXPBOOST: + if (val1 < 0) + val1 = 0; + break; + case SC_INCFLEE2: + case SC_INCCRI: + val2 = val1*10; //Actual boost (since 100% = 1000) + break; + case SC_SUFFRAGIUM: + val2 = 15 * val1; //Speed cast decrease + break; + case SC_INCHEALRATE: + if (val1 < 1) + val1 = 1; + break; + case SC_HALLUCINATION: + val2 = 5+val1; //Factor by which displayed damage is increased by + break; + case SC_DOUBLECAST: + val2 = 30+10*val1; //Trigger rate + break; + case SC_KAIZEL: + val2 = 10*val1; //% of life to be revived with + break; + // case SC_ARMOR_ELEMENT: + // case SC_ARMOR_RESIST: + // Mod your resistance against elements: + // val1 = water | val2 = earth | val3 = fire | val4 = wind + // break; + //case ????: + //Place here SCs that have no SCB_* data, no skill associated, no ICON + //associated, and yet are not wrong/unknown. [Skotlex] + //break; + + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HITUP: + val2 = 15 * val1; + break; + case SC_MERC_HPUP: + case SC_MERC_SPUP: + val2 = 5 * val1; + break; + case SC_REBIRTH: + val2 = 20*val1; //% of life to be revived with + break; + + case SC_MANU_DEF: + case SC_MANU_ATK: + case SC_MANU_MATK: + val2 = 1; // Manuk group + break; + case SC_SPL_DEF: + case SC_SPL_ATK: + case SC_SPL_MATK: + val2 = 2; // Splendide group + break; + /** + * General + **/ + case SC_FEAR: + val2 = 2; + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_BURNING: + val4 = tick / 2000; // Total Ticks to Burn!! + tick_time = 2000; // [GodLesZ] tick time + break; + /** + * Rune Knight + **/ + case SC_DEATHBOUND: + val2 = 500 + 100 * val1; + break; + case SC_FIGHTINGSPIRIT: + val_flag |= 1|2; + break; + case SC_ABUNDANCE: + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_GIANTGROWTH: + val2 = 10; // Triple damage success rate. + break; + /** + * Arch Bishop + **/ + case SC_RENOVATIO: + val4 = tick / 5000; + tick_time = 5000; + break; + case SC_SECRAMENT: + val2 = 10 * val1; + break; + case SC_VENOMIMPRESS: + val2 = 10 * val1; + val_flag |= 1|2; + break; + case SC_POISONINGWEAPON: + val_flag |= 1|2|4; + break; + case SC_WEAPONBLOCKING: + val2 = 10 + 2 * val1; // Chance + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + val_flag |= 1|2; + break; + case SC_TOXIN: + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_MAGICMUSHROOM: + val4 = tick / 4000; + tick_time = 4000; // [GodLesZ] tick time + break; + case SC_PYREXIA: + status_change_start(bl,SC_BLIND,10000,val1,0,0,0,30000,11); // Blind status that last for 30 seconds + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_LEECHESEND: + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_OBLIVIONCURSE: + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_ROLLINGCUTTER: + val_flag |= 1; + break; + case SC_CLOAKINGEXCEED: + val2 = ( val1 + 1 ) / 2; // Hits + val3 = 90 + val1 * 10; // Walk speed + val_flag |= 1|2|4; + if (bl->type == BL_PC) + val4 |= battle_config.pc_cloak_check_type&7; + else + val4 |= battle_config.monster_cloak_check_type&7; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_HALLUCINATIONWALK: + val2 = 50 * val1; // Evasion rate of physical attacks. Flee + val3 = 10 * val1; // Evasion rate of magical attacks. + val_flag |= 1|2|4; + break; + case SC_WHITEIMPRISON: + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + break; + case SC_FREEZING: + status_change_end(bl, SC_BURNING, INVALID_TIMER); + break; + case SC_READING_SB: + // val2 = sp reduction per second + tick_time = 5000; // [GodLesZ] tick time + break; + case SC_SPHERE_1: + case SC_SPHERE_2: + case SC_SPHERE_3: + case SC_SPHERE_4: + case SC_SPHERE_5: + if( !sd ) + return 0; // Should only work on players. + val4 = tick / 1000; + if( val4 < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + val_flag |= 1; + break; + case SC_SHAPESHIFT: + switch( val1 ) + { + case 1: val2 = ELE_FIRE; break; + case 2: val2 = ELE_EARTH; break; + case 3: val2 = ELE_WIND; break; + case 4: val2 = ELE_WATER; break; + } + break; + case SC_ELECTRICSHOCKER: + case SC_CRYSTALIZE: + case SC_MEIKYOUSISUI: + val4 = tick / 1000; + if( val4 < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_CAMOUFLAGE: + val4 = tick/1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_WUGDASH: + val4 = gettick(); //Store time at which you started running. + tick = -1; + break; + case SC__SHADOWFORM: { + struct map_session_data * s_sd = map_id2sd(val2); + if( s_sd ) + s_sd->shadowform_id = bl->id; + val4 = tick / 1000; + val_flag |= 1|2|4; + tick_time = 1000; // [GodLesZ] tick time + } + break; + case SC__STRIPACCESSORY: + if (!sd) + val2 = 20; + break; + case SC__INVISIBILITY: + val2 = 50 - 10 * val1; // ASPD + val3 = 20 * val1; // CRITICAL + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + val_flag |= 1|2; + break; + case SC__ENERVATION: + val2 = 20 + 10 * val1; // ATK Reduction + val_flag |= 1|2; + if( sd ) pc_delspiritball(sd,sd->spiritball,0); + break; + case SC__GROOMY: + val2 = 20 + 10 * val1; //ASPD. Need to confirm if Movement Speed reduction is the same. [Jobbie] + val3 = 20 * val1; //HIT + val_flag |= 1|2|4; + if( sd ) + { // Removes Animals + if( pc_isriding(sd) ) pc_setriding(sd, 0); + if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + if( pc_iswug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUG); + if( pc_isridingwug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUGRIDER); + if( pc_isfalcon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_FALCON); + if( sd->status.pet_id > 0 ) pet_menu(sd, 3); + if( merc_is_hom_active(sd->hd) ) merc_hom_vaporize(sd,1); + if( sd->md ) merc_delete(sd->md,3); + } + break; + case SC__LAZINESS: + val2 = 10 + 10 * val1; // Cast reduction + val3 = 10 * val1; // Flee Reduction + val_flag |= 1|2|4; + break; + case SC__UNLUCKY: + val2 = 10 * val1; // Crit and Flee2 Reduction + val_flag |= 1|2|4; + break; + case SC__WEAKNESS: + val2 = 10 * val1; + val_flag |= 1|2; + // bypasses coating protection and MADO + sc_start(bl,SC_STRIPWEAPON,100,val1,tick); + sc_start(bl,SC_STRIPSHIELD,100,val1,tick); + break; + break; + case SC_GN_CARTBOOST: + if( val1 < 3 ) + val2 = 50; + else if( val1 < 5 ) + val2 = 75; + else + val2 = 100; + break; + case SC_PROPERTYWALK: + val_flag |= 1|2; + val3 = 0; + break; + case SC_WARMER: + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); + break; + case SC_STRIKING: + val1 = 6 - val1;//spcost = 6 - level (lvl1:5 ... lvl 5: 1) + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_BLOODSUCKER: + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_VACUUM_EXTREME: + tick -= (status->str / 20) * 1000; + val4 = val3 = tick / 100; + tick_time = 100; // [GodLesZ] tick time + break; + case SC_SWINGDANCE: + val2 = 4 * val1; // Walk speed and aspd reduction. + break; + case SC_SYMPHONYOFLOVER: + case SC_RUSHWINDMILL: + case SC_ECHOSONG: + val2 = 6 * val1; + val2 += val3; //Adding 1% * Lesson Bonus + val2 += (int)(val4*2/10); //Adding 0.2% per JobLevel + break; + case SC_MOONLITSERENADE: + val2 = 10 * val1; + break; + case SC_HARMONIZE: + val2 = 5 + 5 * val1; + break; + case SC_VOICEOFSIREN: + val4 = tick / 2000; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_DEEPSLEEP: + val4 = tick / 2000; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_SIRCLEOFNATURE: + val2 = 1 + val1; //SP consume + val3 = 40 * val1; //HP recovery + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_SONGOFMANA: + val3 = 10 + (2 * val2); + val4 = tick/3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_SATURDAYNIGHTFEVER: + if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); + if (!val4) val4 = 3000; + val3 = tick/val4; + tick_time = val4; // [GodLesZ] tick time + break; + case SC_GLOOMYDAY: + val2 = 20 + 5 * val1; // Flee reduction. + val3 = 15 + 5 * val1; // ASPD reduction. + if( sd && rand()%100 < val1 ){ // (Skill Lv) % + val4 = 1; // reduce walk speed by half. + if( pc_isriding(sd) ) pc_setriding(sd, 0); + if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + } + break; + case SC_GLOOMYDAY_SK: + // Random number between [15 ~ (Voice Lesson Skill Level x 5) + (Skill Level x 10)] %. + val2 = 15 + rand()%( (sd?pc_checkskill(sd, WM_LESSON)*5:0) + val1*10 ); + break; + case SC_SITDOWN_FORCE: + case SC_BANANA_BOMB_SITDOWN: + if( sd && !pc_issit(sd) ) + { + pc_setsit(sd); + skill_sit(sd,1); + clif_sitting(bl); + } + break; + case SC_DANCEWITHWUG: + val3 = (5 * val1) + (1 * val2); //Still need official value. + break; + case SC_LERADSDEW: + val3 = (5 * val1) + (1 * val2); + break; + case SC_MELODYOFSINK: + val3 = (5 * val1) + (1 * val2); + break; + case SC_BEYONDOFWARCRY: + val3 = (5 * val1) + (1 * val2); + break; + case SC_UNLIMITEDHUMMINGVOICE: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud == NULL ) return 0; + ud->state.skillcastcancel = 0; + val3 = 15 - (2 * val2); + } + break; + case SC_REFLECTDAMAGE: + val2 = 15 + 5 * val1; + val3 = (val1==5)?20:(val1+4)*2; // SP consumption + val4 = tick/10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_FORCEOFVANGUARD: // This is not the official way to handle it but I think we should use it. [pakpil] + val2 = 20 + 12 * (val1 - 1); // Chance + val3 = 5 + (2 * val1); // Max rage counters + tick = -1; //endless duration in the client + tick_time = 6000; // [GodLesZ] tick time + val_flag |= 1|2|4; + break; + case SC_EXEEDBREAK: + val1 *= 150; // 150 * skill_lv + if( sd && sd->inventory_data[sd->equip_index[EQI_HAND_R]] ) { // Chars. + val1 += (sd->inventory_data[sd->equip_index[EQI_HAND_R]]->weight/10 * sd->inventory_data[sd->equip_index[EQI_HAND_R]]->wlv * status_get_lv(bl) / 100); + val1 += 15 * (sd ? sd->status.job_level:50) + 100; + } + else // Mobs + val1 += (400 * status_get_lv(bl) / 100) + (15 * (status_get_lv(bl) / 2)); // About 1138% at mob_lvl 99. Is an aproximation to a standard weapon. [pakpil] + break; + case SC_PRESTIGE: // Bassed on suggested formula in iRO Wiki and some test, still need more test. [pakpil] + val2 = ((status->int_ + status->luk) / 6) + 5; // Chance to evade magic damage. + val1 *= 15; // Defence added + if( sd ) + val1 += 10 * pc_checkskill(sd,CR_DEFENDER); + val_flag |= 1|2; + break; + case SC_BANDING: + tick_time = 5000; // [GodLesZ] tick time + val_flag |= 1; + break; + case SC_SHIELDSPELL_DEF: + case SC_SHIELDSPELL_MDEF: + case SC_SHIELDSPELL_REF: + val_flag |= 1|2; + break; + case SC_MAGNETICFIELD: + val3 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_INSPIRATION: + if( sd ) + { + val2 = (40 * val1) + (3 * sd->status.job_level); // ATK bonus + val3 = (sd->status.job_level / 10) * 2 + 12; // All stat bonus + } + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + status_change_clear_buffs(bl,3); //Remove buffs/debuffs + break; + case SC_SPELLFIST: + case SC_CURSEDCIRCLE_ATKER: + val_flag |= 1|2|4; + break; + case SC_CRESCENTELBOW: + val2 = 94 + val1; + val_flag |= 1|2; + break; + case SC_LIGHTNINGWALK: // [(Job Level / 2) + (40 + 5 * Skill Level)] % + val1 = (sd?sd->status.job_level:2)/2 + 40 + 5 * val1; + val_flag |= 1; + break; + case SC_RAISINGDRAGON: + val3 = tick / 5000; + tick_time = 5000; // [GodLesZ] tick time + break; + case SC_GT_CHANGE: + {// take note there is no def increase as skill desc says. [malufett] + struct block_list * src; + val3 = status->agi * val1 / 60; // ASPD increase: [(Target AGI x Skill Level) / 60] % + if( (src = map_id2bl(val2)) ) + val4 = ( 200/status_get_int(src) ) * val1;// MDEF decrease: MDEF [(200 / Caster INT) x Skill Level] + } + break; + case SC_GT_REVITALIZE: + {// take note there is no vit,aspd,speed increase as skill desc says. [malufett] + struct block_list * src; + val3 = val1 * 30 + 150; // Natural HP recovery increase: [(Skill Level x 30) + 50] % + if( (src = map_id2bl(val2)) ) // the stat def is not shown in the status window and it is process differently + val4 = ( status_get_vit(src)/4 ) * val1; // STAT DEF increase: [(Caster VIT / 4) x Skill Level] + } + break; + case SC_PYROTECHNIC_OPTION: + val_flag |= 1|2|4; + break; + case SC_HEATER_OPTION: + val2 = 120; // Watk. TODO: Renewal (Atk2) + val3 = 33; // % Increase effects. + val4 = 3; // Change into fire element. + val_flag |= 1|2|4; + break; + case SC_TROPIC_OPTION: + val2 = 180; // Watk. TODO: Renewal (Atk2) + val3 = MG_FIREBOLT; + break; + case SC_AQUAPLAY_OPTION: + val2 = 40; + val_flag |= 1|2|4; + break; + case SC_COOLER_OPTION: + val2 = 80; // % Freezing chance + val3 = 33; // % increased damage + val4 = 1; // Change into water elemet + val_flag |= 1|2|4; + break; + case SC_CHILLY_AIR_OPTION: + val2 = 120; // Matk. TODO: Renewal (Matk1) + val3 = MG_COLDBOLT; + val_flag |= 1|2; + break; + case SC_GUST_OPTION: + val_flag |= 1|2; + break; + case SC_WIND_STEP_OPTION: + val2 = 50; // % Increase speed and flee. + break; + case SC_BLAST_OPTION: + val2 = 20; + val3 = ELE_WIND; + val_flag |= 1|2|4; + break; + case SC_WILD_STORM_OPTION: + val2 = MG_LIGHTNINGBOLT; + val_flag |= 1|2; + break; + case SC_PETROLOGY_OPTION: + val2 = 5; + val3 = 50; + val_flag |= 1|2|4; + break; + case SC_CURSED_SOIL_OPTION: + val2 = 10; + val3 = 33; + val4 = 2; + val_flag |= 1|2|4; + break; + case SC_UPHEAVAL_OPTION: + val2 = WZ_EARTHSPIKE; + val_flag |= 1|2; + break; + case SC_CIRCLE_OF_FIRE_OPTION: + val2 = 300; + val_flag |= 1|2; + break; + case SC_FIRE_CLOAK_OPTION: + case SC_WATER_DROP_OPTION: + case SC_WIND_CURTAIN_OPTION: + case SC_STONE_SHIELD_OPTION: + val2 = 20; // Elemental modifier. Not confirmed. + break; + case SC_CIRCLE_OF_FIRE: + case SC_FIRE_CLOAK: + case SC_WATER_DROP: + case SC_WATER_SCREEN: + case SC_WIND_CURTAIN: + case SC_WIND_STEP: + case SC_STONE_SHIELD: + case SC_SOLID_SKIN: + val2 = 10; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_WATER_BARRIER: + val2 = 40; // Increasement. Mdef1 ??? + val3 = 20; // Reductions. Atk2, Flee1, Matk1 ???? + val_flag |= 1|2|4; + break; + case SC_ZEPHYR: + val2 = 22; // Flee. + break; + case SC_TIDAL_WEAPON: + val2 = 20; // Increase Elemental's attack. + break; + case SC_ROCK_CRUSHER: + case SC_ROCK_CRUSHER_ATK: + case SC_POWER_OF_GAIA: + val2 = 33; + break; + case SC_MELON_BOMB: + case SC_BANANA_BOMB: + val1 = 15; + break; + case SC_STOMACHACHE: + val2 = 8; // SP consume. + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_KYOUGAKU: + val2 = 2*val1 + rand()%val1; + clif_status_change(bl,SI_ACTIVE_MONSTER_TRANSFORM,1,0,1002,0,0); + break; + case SC_KAGEMUSYA: + val3 = val1 * 2; + case SC_IZAYOI: + val2 = tick/1000; + tick_time = 1000; + break; + case SC_ZANGETSU: + if( (status_get_hp(bl)+status_get_sp(bl)) % 2 == 0) + val2 = status_get_lv(bl) / 2 + 50; + else + val2 -= 50; + break; + case SC_GENSOU: + { + int hp = status_get_hp(bl), lv = 5; + short per = 100 / (status_get_max_hp(bl) / hp); + + if( per <= 15 ) + lv = 1; + else if( per <= 30 ) + lv = 2; + else if( per <= 50 ) + lv = 3; + else if( per <= 75 ) + lv = 4; + if( hp % 2 == 0) + status_heal(bl, hp * (6-lv) * 4 / 100, status_get_sp(bl) * (6-lv) * 3 / 100, 1); + else + status_zap(bl, hp * (lv*4) / 100, status_get_sp(bl) * (lv*3) / 100); + } + break; + case SC_ANGRIFFS_MODUS: + val2 = 50 + 20 * val1; //atk bonus + val3 = 40 + 20 * val1; // Flee reduction. + val4 = tick/1000; // hp/sp reduction timer + tick_time = 1000; + break; + case SC_GOLDENE_FERSE: + val2 = 10 + 10*val1; //max hp bonus + val3 = 6 + 4 * val1; // Aspd Bonus + val4 = 2 + 2 * val1; // Chance of holy attack + break; + case SC_OVERED_BOOST: + val2 = 300 + 40*val1; //flee bonus + val3 = 179 + 2*val1; //aspd bonus + break; + case SC_GRANITIC_ARMOR: + val2 = 2*val1; //dmg reduction + val3 = 6*val1; //dmg on status end + break; + case SC_MAGMA_FLOW: + val2 = 3*val1; //activation chance + break; + case SC_PYROCLASTIC: + val2 += 10*val1; //atk bonus + break; + case SC_PARALYSIS: //[Lighta] need real info + val2 = 2*val1; //def reduction + val3 = 500*val1; //varcast augmentation + break; + case SC_PAIN_KILLER: //[Lighta] need real info + val2 = 2*val1; //aspd reduction % + val3 = 2*val1; //dmg reduction % + if(sc->data[SC_PARALYSIS]) + sc_start(bl, SC_ENDURE, 100, val1, tick); //start endure for same duration + break; + case SC_STYLE_CHANGE: //[Lighta] need real info + tick = -1; + if(val2 == MH_MD_FIGHTING) val2 = MH_MD_GRAPPLING; + else val2 = MH_MD_FIGHTING; + break; + default: + if( calc_flag == SCB_NONE && StatusSkillChangeTable[type] == 0 && StatusIconChangeTable[type] == 0 ) + { //Status change with no calc, no icon, and no skill associated...? + ShowError("UnknownStatusChange [%d]\n", type); + return 0; + } + } + else //Special considerations when loading SC data. + switch( type ) + { + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + clif_changelook(bl,LOOK_WEAPON,0); + clif_changelook(bl,LOOK_SHIELD,0); + clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); + clif_changelook(bl,LOOK_CLOTHES_COLOR,val4); + break; + case SC_KAAHI: + val4 = INVALID_TIMER; + break; + } + + //Those that make you stop attacking/walking.... + switch (type) { + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + case SC_STONE: + case SC_DEEPSLEEP: + if (sd && pc_issit(sd)) //Avoid sprite sync problems. + pc_setstand(sd); + case SC_TRICKDEAD: + status_change_end(bl, SC_DANCING, INVALID_TIMER); + // Cancel cast when get status [LuzZza] + if (battle_config.sc_castcancel&bl->type) + unit_skillcastcancel(bl, 0); + case SC_WHITEIMPRISON: + unit_stop_attack(bl); + case SC_STOP: + case SC_CONFUSION: + case SC_CLOSECONFINE: + case SC_CLOSECONFINE2: + case SC_ANKLE: + case SC_SPIDERWEB: + case SC_ELECTRICSHOCKER: + case SC_BITE: + case SC_THORNSTRAP: + case SC__MANHOLE: + case SC_CRYSTALIZE: + case SC_CURSEDCIRCLE_ATKER: + case SC_CURSEDCIRCLE_TARGET: + case SC_FEAR: + case SC_NETHERWORLD: + case SC_MEIKYOUSISUI: + case SC_KYOUGAKU: + case SC_PARALYSIS: + unit_stop_walking(bl,1); + break; + case SC_HIDING: + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC_CHASEWALK: + case SC_WEIGHT90: + case SC_CAMOUFLAGE: + case SC_VOICEOFSIREN: + unit_stop_attack(bl); + break; + case SC_SILENCE: + if (battle_config.sc_castcancel&bl->type) + unit_skillcastcancel(bl, 0); + break; + } + + // Set option as needed. + opt_flag = 1; + switch(type) + { + //OPT1 + case SC_STONE: sc->opt1 = OPT1_STONEWAIT; break; + case SC_FREEZE: sc->opt1 = OPT1_FREEZE; break; + case SC_STUN: sc->opt1 = OPT1_STUN; break; + case SC_DEEPSLEEP: opt_flag = 0; + case SC_SLEEP: sc->opt1 = OPT1_SLEEP; break; + case SC_BURNING: sc->opt1 = OPT1_BURNING; break; // Burning need this to be showed correctly. [pakpil] + case SC_WHITEIMPRISON: sc->opt1 = OPT1_IMPRISON; break; + case SC_CRYSTALIZE: sc->opt1 = OPT1_CRYSTALIZE; break; + //OPT2 + case SC_POISON: sc->opt2 |= OPT2_POISON; break; + case SC_CURSE: sc->opt2 |= OPT2_CURSE; break; + case SC_SILENCE: sc->opt2 |= OPT2_SILENCE; break; + + case SC_SIGNUMCRUCIS: + sc->opt2 |= OPT2_SIGNUMCRUCIS; + break; + + case SC_BLIND: sc->opt2 |= OPT2_BLIND; break; + case SC_ANGELUS: sc->opt2 |= OPT2_ANGELUS; break; + case SC_BLEEDING: sc->opt2 |= OPT2_BLEEDING; break; + case SC_DPOISON: sc->opt2 |= OPT2_DPOISON; break; + //OPT3 + case SC_TWOHANDQUICKEN: + case SC_ONEHAND: + case SC_SPEARQUICKEN: + case SC_CONCENTRATION: + case SC_MERC_QUICKEN: + sc->opt3 |= OPT3_QUICKEN; + opt_flag = 0; + break; + case SC_MAXOVERTHRUST: + case SC_OVERTHRUST: + case SC_SWOO: //Why does it shares the same opt as Overthrust? Perhaps we'll never know... + sc->opt3 |= OPT3_OVERTHRUST; + opt_flag = 0; + break; + case SC_ENERGYCOAT: + case SC_SKE: + sc->opt3 |= OPT3_ENERGYCOAT; + opt_flag = 0; + break; + case SC_INCATKRATE: + //Simulate Explosion Spirits effect for NPC_POWERUP [Skotlex] + if (bl->type != BL_MOB) { + opt_flag = 0; + break; + } + case SC_EXPLOSIONSPIRITS: + sc->opt3 |= OPT3_EXPLOSIONSPIRITS; + opt_flag = 0; + break; + case SC_STEELBODY: + case SC_SKA: + sc->opt3 |= OPT3_STEELBODY; + opt_flag = 0; + break; + case SC_BLADESTOP: + sc->opt3 |= OPT3_BLADESTOP; + opt_flag = 0; + break; + case SC_AURABLADE: + sc->opt3 |= OPT3_AURABLADE; + opt_flag = 0; + break; + case SC_BERSERK: + opt_flag = 0; +// case SC__BLOODYLUST: + sc->opt3 |= OPT3_BERSERK; + break; +// case ???: // doesn't seem to do anything +// sc->opt3 |= OPT3_LIGHTBLADE; +// opt_flag = 0; +// break; + case SC_DANCING: + if ((val1&0xFFFF) == CG_MOONLIT) + sc->opt3 |= OPT3_MOONLIT; + opt_flag = 0; + break; + case SC_MARIONETTE: + case SC_MARIONETTE2: + sc->opt3 |= OPT3_MARIONETTE; + opt_flag = 0; + break; + case SC_ASSUMPTIO: + sc->opt3 |= OPT3_ASSUMPTIO; + opt_flag = 0; + break; + case SC_WARM: //SG skills [Komurka] + sc->opt3 |= OPT3_WARM; + opt_flag = 0; + break; + case SC_KAITE: + sc->opt3 |= OPT3_KAITE; + opt_flag = 0; + break; + case SC_BUNSINJYUTSU: + sc->opt3 |= OPT3_BUNSIN; + opt_flag = 0; + break; + case SC_SPIRIT: + sc->opt3 |= OPT3_SOULLINK; + opt_flag = 0; + break; + case SC_CHANGEUNDEAD: + sc->opt3 |= OPT3_UNDEAD; + opt_flag = 0; + break; +// case ???: // from DA_CONTRACT (looks like biolab mobs aura) +// sc->opt3 |= OPT3_CONTRACT; +// opt_flag = 0; +// break; + //OPTION + case SC_HIDING: + sc->option |= OPTION_HIDE; + opt_flag = 2; + break; + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC__INVISIBILITY: + sc->option |= OPTION_CLOAK; + opt_flag = 2; + break; + case SC_CHASEWALK: + sc->option |= OPTION_CHASEWALK|OPTION_CLOAK; + opt_flag = 2; + break; + case SC_SIGHT: + sc->option |= OPTION_SIGHT; + break; + case SC_RUWACH: + sc->option |= OPTION_RUWACH; + break; + case SC_WEDDING: + sc->option |= OPTION_WEDDING; + break; + case SC_XMAS: + sc->option |= OPTION_XMAS; + break; + case SC_SUMMER: + sc->option |= OPTION_SUMMER; + break; + case SC_ORCISH: + sc->option |= OPTION_ORCISH; + break; + case SC_FUSION: + sc->option |= OPTION_FLYING; + break; + default: + opt_flag = 0; + } + + //On Aegis, when turning on a status change, first goes the option packet, then the sc packet. + if(opt_flag) + clif_changeoption(bl); + + if (calc_flag&SCB_DYE) + { //Reset DYE color + if (vd && vd->cloth_color) + { + val4 = vd->cloth_color; + clif_changelook(bl,LOOK_CLOTHES_COLOR,0); + } + calc_flag&=~SCB_DYE; + } + + clif_status_change(bl,StatusIconChangeTable[type],1,tick,(val_flag&1)?val1:1,(val_flag&2)?val2:0,(val_flag&4)?val3:0); + + /** + * used as temporary storage for scs with interval ticks, so that the actual duration is sent to the client first. + **/ + if( tick_time ) + tick = tick_time; + + //Don't trust the previous sce assignment, in case the SC ended somewhere between there and here. + if((sce=sc->data[type])) {// reuse old sc + if( sce->timer != INVALID_TIMER ) + delete_timer(sce->timer, status_change_timer); + sc_isnew = false; + } else {// new sc + ++(sc->count); + sce = sc->data[type] = ers_alloc(sc_data_ers, struct status_change_entry); + } + sce->val1 = val1; + sce->val2 = val2; + sce->val3 = val3; + sce->val4 = val4; + if (tick >= 0) + sce->timer = add_timer(gettick() + tick, status_change_timer, bl->id, type); + else + sce->timer = INVALID_TIMER; //Infinite duration + + if (calc_flag) + status_calc_bl(bl,calc_flag); + + if ( sc_isnew && StatusChangeStateTable[type] ) /* non-zero */ + status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],true); + + + if(sd && sd->pd) + pet_sc_check(sd, type); //Skotlex: Pet Status Effect Healing + + switch (type) { + case SC__BLOODYLUST: + case SC_BERSERK: + if (!(sce->val2)) { //don't heal if already set + status_heal(bl, status->max_hp, 0, 1); //Do not use percent_heal as this healing must override BERSERK's block. + status_set_sp(bl, 0, 0); //Damage all SP + } + sce->val2 = 5 * status->max_hp / 100; + break; + case SC_CHANGE: + status_percent_heal(bl, 100, 100); + break; + case SC_RUN: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud ) + ud->state.running = unit_run(bl); + } + break; + case SC_BOSSMAPINFO: + clif_bossmapinfo(sd->fd, map_id2boss(sce->val1), 0); // First Message + break; + case SC_MERC_HPUP: + status_percent_heal(bl, 100, 0); // Recover Full HP + break; + case SC_MERC_SPUP: + status_percent_heal(bl, 0, 100); // Recover Full SP + break; + /** + * Ranger + **/ + case SC_WUGDASH: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud ) + ud->state.running = unit_wugdash(bl, sd); + } + break; + case SC_COMBO: + switch (sce->val1) { + case TK_STORMKICK: + clif_skill_nodamage(bl,bl,TK_READYSTORM,1,1); + break; + case TK_DOWNKICK: + clif_skill_nodamage(bl,bl,TK_READYDOWN,1,1); + break; + case TK_TURNKICK: + clif_skill_nodamage(bl,bl,TK_READYTURN,1,1); + break; + case TK_COUNTER: + clif_skill_nodamage(bl,bl,TK_READYCOUNTER,1,1); + break; + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + if (sd) + clif_skillinfo(sd,MO_EXTREMITYFIST, INF_SELF_SKILL); + break; + case TK_JUMPKICK: + if (sd) + clif_skillinfo(sd,TK_JUMPKICK, INF_SELF_SKILL); + break; + case MO_TRIPLEATTACK: + if (sd && pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + clif_skillinfo(sd,SR_DRAGONCOMBO, INF_SELF_SKILL); + break; + case SR_FALLENEMPIRE: + if (sd){ + clif_skillinfo(sd,SR_GATEOFHELL, INF_SELF_SKILL); + clif_skillinfo(sd,SR_TIGERCANNON, INF_SELF_SKILL); + } + break; + } + break; + case SC_RAISINGDRAGON: + sce->val2 = status->max_hp / 100;// Officially tested its 1%hp drain. [Jobbie] + break; + } + + if( opt_flag&2 && sd && sd->touching_id ) + npc_touchnext_areanpc(sd,false); // run OnTouch_ on next char in range + + return 1; +} + +/*========================================== + * Ending all status except those listed. + * @TODO maybe usefull for dispel instead reseting a liste there. + * type: + * 0 - PC killed -> Place here statuses that do not dispel on death. + * 1 - If for some reason status_change_end decides to still keep the status when quitting. + * 2 - Do clif + * 3 - Do not remove some permanent/time-independent effects + *------------------------------------------*/ +int status_change_clear(struct block_list* bl, int type) +{ + struct status_change* sc; + int i; + + sc = status_get_sc(bl); + + if (!sc || !sc->count) + return 0; + + for(i = 0; i < SC_MAX; i++) + { + if(!sc->data[i]) + continue; + + if(type == 0) + switch (i) + { //Type 0: PC killed -> Place here statuses that do not dispel on death. + case SC_ELEMENTALCHANGE://Only when its Holy or Dark that it doesn't dispell on death + if( sc->data[i]->val2 != ELE_HOLY && sc->data[i]->val2 != ELE_DARK ) + break; + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_EDP: + case SC_MELTDOWN: + case SC_XMAS: + case SC_SUMMER: + case SC_NOCHAT: + case SC_FUSION: + case SC_EARTHSCROLL: + case SC_READYSTORM: + case SC_READYDOWN: + case SC_READYCOUNTER: + case SC_READYTURN: + case SC_DODGE: + case SC_JAILED: + case SC_EXPBOOST: + case SC_ITEMBOOST: + case SC_HELLPOWER: + case SC_JEXPBOOST: + case SC_AUTOTRADE: + case SC_WHISTLE: + case SC_ASSNCROS: + case SC_POEMBRAGI: + case SC_APPLEIDUN: + case SC_HUMMING: + case SC_DONTFORGETME: + case SC_FORTUNE: + case SC_SERVICE4U: + case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: + case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: + case SC_DEF_RATE: + case SC_MDEF_RATE: + case SC_INCHEALRATE: + case SC_INCFLEE2: + case SC_INCHIT: + case SC_ATKPOTION: + case SC_MATKPOTION: + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + case SC_PUSH_CART: + continue; + + } + + if( type == 3 ) + { + switch (i) + {// TODO: This list may be incomplete + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_NOCHAT: + case SC_PUSH_CART: + continue; + } + } + + status_change_end(bl, (sc_type)i, INVALID_TIMER); + + if( type == 1 && sc->data[i] ) + { //If for some reason status_change_end decides to still keep the status when quitting. [Skotlex] + (sc->count)--; + if (sc->data[i]->timer != INVALID_TIMER) + delete_timer(sc->data[i]->timer, status_change_timer); + ers_free(sc_data_ers, sc->data[i]); + sc->data[i] = NULL; + } + } + + sc->opt1 = 0; + sc->opt2 = 0; + sc->opt3 = 0; + sc->option &= OPTION_MASK; + + if( type == 0 || type == 2 ) + clif_changeoption(bl); + + return 1; +} + +/*========================================== + * Special condition we want to effectuate, check before ending a status. + *------------------------------------------*/ +int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const char* file, int line) +{ + struct map_session_data *sd; + struct status_change *sc; + struct status_change_entry *sce; + struct status_data *status; + struct view_data *vd; + int opt_flag=0, calc_flag; + + nullpo_ret(bl); + + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if(type < 0 || type >= SC_MAX || !sc || !(sce = sc->data[type])) + return 0; + + sd = BL_CAST(BL_PC,bl); + + if (sce->timer != tid && tid != INVALID_TIMER) + return 0; + + if (tid == INVALID_TIMER) { + if (type == SC_ENDURE && sce->val4) + //Do not end infinite endure. + return 0; + if (sce->timer != INVALID_TIMER) //Could be a SC with infinite duration + delete_timer(sce->timer,status_change_timer); + if (sc->opt1) + switch (type) { + //"Ugly workaround" [Skotlex] + //delays status change ending so that a skill that sets opt1 fails to + //trigger when it also removed one + case SC_STONE: + sce->val3 = 0; //Petrify time counter. + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + if (sce->val1) { + //Removing the 'level' shouldn't affect anything in the code + //since these SC are not affected by it, and it lets us know + //if we have already delayed this attack or not. + sce->val1 = 0; + sce->timer = add_timer(gettick()+10, status_change_timer, bl->id, type); + return 1; + } + } + } + + (sc->count)--; + + if ( StatusChangeStateTable[type] ) + status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],false); + + sc->data[type] = NULL; + + vd = status_get_viewdata(bl); + calc_flag = StatusChangeFlagTable[type]; + switch(type){ + case SC_GRANITIC_ARMOR:{ + int dammage = status->max_hp*sce->val3/100; + if(status->hp < dammage) //to not kill him + dammage = status->hp-1; + status_damage(NULL, bl, dammage,0,0,1); + break; + } + case SC_PYROCLASTIC: + if(bl->type == BL_PC) + skill_break_equip(bl,EQP_WEAPON,10000,BCT_SELF); + break; + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + if (!vd) break; + if (sd) + { //Load data from sd->status.* as the stored values could have changed. + //Must remove OPTION to prevent class being rechanged. + sc->option &= type==SC_WEDDING?~OPTION_WEDDING:type==SC_XMAS?~OPTION_XMAS:~OPTION_SUMMER; + clif_changeoption(&sd->bl); + status_set_viewdata(bl, sd->status.class_); + } else { + vd->class_ = sce->val1; + vd->weapon = sce->val2; + vd->shield = sce->val3; + vd->cloth_color = sce->val4; + } + clif_changelook(bl,LOOK_BASE,vd->class_); + clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); + clif_changelook(bl,LOOK_WEAPON,vd->weapon); + clif_changelook(bl,LOOK_SHIELD,vd->shield); + if(sd) clif_skillinfoblock(sd); + break; + case SC_RUN: + { + struct unit_data *ud = unit_bl2ud(bl); + bool begin_spurt = true; + if (ud) { + if(!ud->state.running) + begin_spurt = false; + ud->state.running = 0; + if (ud->walktimer != INVALID_TIMER) + unit_stop_walking(bl,1); + } + if (begin_spurt && sce->val1 >= 7 && + DIFF_TICK(gettick(), sce->val4) <= 1000 && + (!sd || (sd->weapontype1 == 0 && sd->weapontype2 == 0)) + ) + sc_start(bl,SC_SPURT,100,sce->val1,skill_get_time2(status_sc2skill(type), sce->val1)); + } + break; + case SC_AUTOBERSERK: + if (sc->data[SC_PROVOKE] && sc->data[SC_PROVOKE]->val2 == 1) + status_change_end(bl, SC_PROVOKE, INVALID_TIMER); + break; + + case SC_ENDURE: + case SC_DEFENDER: + case SC_REFLECTSHIELD: + case SC_AUTOGUARD: + { + struct map_session_data *tsd; + if( bl->type == BL_PC ) + { // Clear Status from others + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) && tsd->sc.data[type] ) + status_change_end(&tsd->bl, type, INVALID_TIMER); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag ) + { // Clear Status from Master + tsd = ((TBL_MER*)bl)->master; + if( tsd && tsd->sc.data[type] ) + status_change_end(&tsd->bl, type, INVALID_TIMER); + } + } + break; + case SC_DEVOTION: + { + struct block_list *d_bl = map_id2bl(sce->val1); + if( d_bl ) + { + if( d_bl->type == BL_PC ) + ((TBL_PC*)d_bl)->devotion[sce->val2] = 0; + else if( d_bl->type == BL_MER ) + ((TBL_MER*)d_bl)->devotion_flag = 0; + clif_devotion(d_bl, NULL); + } + + status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); + status_change_end(bl, SC_DEFENDER, INVALID_TIMER); + status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); + status_change_end(bl, SC_ENDURE, INVALID_TIMER); + } + break; + + case SC_BLADESTOP: + if(sce->val4) + { + int tid = sce->val4; + struct block_list *tbl = map_id2bl(tid); + struct status_change *tsc = status_get_sc(tbl); + sce->val4 = 0; + if(tbl && tsc && tsc->data[SC_BLADESTOP]) + { + tsc->data[SC_BLADESTOP]->val4 = 0; + status_change_end(tbl, SC_BLADESTOP, INVALID_TIMER); + } + clif_bladestop(bl, tid, 0); + } + break; + case SC_DANCING: + { + const char* prevfile = "<unknown>"; + int prevline = 0; + struct map_session_data *dsd; + struct status_change_entry *dsc; + struct skill_unit_group *group; + + if( sd ) + { + if( sd->delunit_prevfile ) + {// initially this is NULL, when a character logs in + prevfile = sd->delunit_prevfile; + prevline = sd->delunit_prevline; + } + else + { + prevfile = "<none>"; + } + sd->delunit_prevfile = file; + sd->delunit_prevline = line; + } + + if(sce->val4 && sce->val4 != BCT_SELF && (dsd=map_id2sd(sce->val4))) + {// end status on partner as well + dsc = dsd->sc.data[SC_DANCING]; + if(dsc) { + + //This will prevent recursive loops. + dsc->val2 = dsc->val4 = 0; + + status_change_end(&dsd->bl, SC_DANCING, INVALID_TIMER); + } + } + + if(sce->val2) + {// erase associated land skill + group = skill_id2group(sce->val2); + + if( group == NULL ) + { + ShowDebug("status_change_end: SC_DANCING is missing skill unit group (val1=%d, val2=%d, val3=%d, val4=%d, timer=%d, tid=%d, char_id=%d, map=%s, x=%d, y=%d, prev=%s:%d, from=%s:%d). Please report this! (#3504)\n", + sce->val1, sce->val2, sce->val3, sce->val4, sce->timer, tid, + sd ? sd->status.char_id : 0, + mapindex_id2name(map_id2index(bl->m)), bl->x, bl->y, + prevfile, prevline, + file, line); + } + + sce->val2 = 0; + skill_delunitgroup(group); + } + + if((sce->val1&0xFFFF) == CG_MOONLIT) + clif_status_change(bl,SI_MOONLIT,0,0,0,0,0); + + status_change_end(bl, SC_LONGING, INVALID_TIMER); + } + break; + case SC_NOCHAT: + if (sd && sd->status.manner < 0 && tid != INVALID_TIMER) + sd->status.manner = 0; + if (sd && tid == INVALID_TIMER) + { + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + } + break; + case SC_SPLASHER: + { + struct block_list *src=map_id2bl(sce->val3); + if(src && tid != INVALID_TIMER) + skill_castend_damage_id(src, bl, sce->val2, sce->val1, gettick(), SD_LEVEL ); + } + break; + case SC_CLOSECONFINE2: + { + struct block_list *src = sce->val2?map_id2bl(sce->val2):NULL; + struct status_change *sc2 = src?status_get_sc(src):NULL; + if (src && sc2 && sc2->data[SC_CLOSECONFINE]) { + //If status was already ended, do nothing. + //Decrease count + if (--(sc2->data[SC_CLOSECONFINE]->val1) <= 0) //No more holds, free him up. + status_change_end(src, SC_CLOSECONFINE, INVALID_TIMER); + } + } + case SC_CLOSECONFINE: + if (sce->val2 > 0) { + //Caster has been unlocked... nearby chars need to be unlocked. + int range = 1 + +skill_get_range2(bl, status_sc2skill(type), sce->val1) + +skill_get_range2(bl, TF_BACKSLIDING, 1); //Since most people use this to escape the hold.... + map_foreachinarea(status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,BL_CHAR,bl,sce,type,gettick()); + } + break; + case SC_COMBO: + if( sd ) + switch (sce->val1) { + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + clif_skillinfo(sd, MO_EXTREMITYFIST, 0); + break; + case TK_JUMPKICK: + clif_skillinfo(sd, TK_JUMPKICK, 0); + break; + case MO_TRIPLEATTACK: + if (pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + clif_skillinfo(sd, SR_DRAGONCOMBO, 0); + break; + case SR_FALLENEMPIRE: + clif_skillinfo(sd, SR_GATEOFHELL, 0); + clif_skillinfo(sd, SR_TIGERCANNON, 0); + break; + } + break; + + case SC_MARIONETTE: + case SC_MARIONETTE2: /// Marionette target + if (sce->val1) + { // check for partner and end their marionette status as well + enum sc_type type2 = (type == SC_MARIONETTE) ? SC_MARIONETTE2 : SC_MARIONETTE; + struct block_list *pbl = map_id2bl(sce->val1); + struct status_change* sc2 = pbl?status_get_sc(pbl):NULL; + + if (sc2 && sc2->data[type2]) + { + sc2->data[type2]->val1 = 0; + status_change_end(pbl, type2, INVALID_TIMER); + } + } + break; + + case SC_BERSERK: + case SC_SATURDAYNIGHTFEVER: + //If val2 is removed, no HP penalty (dispelled?) [Skotlex] + if (status->hp > 100 && sce->val2) + status_set_hp(bl, 100, 0); + if(sc->data[SC_ENDURE] && sc->data[SC_ENDURE]->val4 == 2) + { + sc->data[SC_ENDURE]->val4 = 0; + status_change_end(bl, SC_ENDURE, INVALID_TIMER); + } + case SC__BLOODYLUST: + sc_start4(bl, SC_REGENERATION, 100, 10,0,0,(RGN_HP|RGN_SP), skill_get_time(LK_BERSERK, sce->val1)); + if( type == SC_SATURDAYNIGHTFEVER ) //Sit down force of Saturday Night Fever has the duration of only 3 seconds. + sc_start(bl,SC_SITDOWN_FORCE,100,sce->val1,skill_get_time2(WM_SATURDAY_NIGHT_FEVER,sce->val1)); + break; + case SC_GOSPEL: + if (sce->val3) { //Clear the group. + struct skill_unit_group* group = skill_id2group(sce->val3); + sce->val3 = 0; + skill_delunitgroup(group); + } + break; + case SC_HERMODE: + if(sce->val3 == BCT_SELF) + skill_clear_unitgroup(bl); + break; + case SC_BASILICA: //Clear the skill area. [Skotlex] + skill_clear_unitgroup(bl); + break; + case SC_TRICKDEAD: + if (vd) vd->dead_sit = 0; + break; + case SC_WARM: + case SC__MANHOLE: + if (sce->val4) { //Clear the group. + struct skill_unit_group* group = skill_id2group(sce->val4); + sce->val4 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_KAAHI: + //Delete timer if it exists. + if (sce->val4 != INVALID_TIMER) + delete_timer(sce->val4,kaahi_heal_timer); + break; + case SC_JAILED: + if(tid == INVALID_TIMER) + break; + //natural expiration. + if(sd && sd->mapindex == sce->val2) + pc_setpos(sd,(unsigned short)sce->val3,sce->val4&0xFFFF, sce->val4>>16, CLR_TELEPORT); + break; //guess hes not in jail :P + case SC_CHANGE: + if (tid == INVALID_TIMER) + break; + // "lose almost all their HP and SP" on natural expiration. + status_set_hp(bl, 10, 0); + status_set_sp(bl, 10, 0); + break; + case SC_AUTOTRADE: + if (tid == INVALID_TIMER) + break; + // Note: vending/buying is closed by unit_remove_map, no + // need to do it here. + map_quit(sd); + // Because map_quit calls status_change_end with tid -1 + // from here it's not neccesary to continue + return 1; + break; + case SC_STOP: + if( sce->val2 ) + { + struct block_list* tbl = map_id2bl(sce->val2); + sce->val2 = 0; + if( tbl && (sc = status_get_sc(tbl)) && sc->data[SC_STOP] && sc->data[SC_STOP]->val2 == bl->id ) + status_change_end(tbl, SC_STOP, INVALID_TIMER); + } + break; + /** + * 3rd Stuff + **/ + case SC_MILLENNIUMSHIELD: + clif_millenniumshield(sd,0); + break; + case SC_HALLUCINATIONWALK: + sc_start(bl,SC_HALLUCINATIONWALK_POSTDELAY,100,sce->val1,skill_get_time2(GC_HALLUCINATIONWALK,sce->val1)); + break; + case SC_WHITEIMPRISON: + { + struct block_list* src = map_id2bl(sce->val2); + if( tid == -1 || !src) + break; // Terminated by Damage + status_fix_damage(src,bl,400*sce->val1,clif_damage(bl,bl,gettick(),0,0,400*sce->val1,0,0,0)); + } + break; + case SC_WUGDASH: + { + struct unit_data *ud = unit_bl2ud(bl); + if (ud) { + ud->state.running = 0; + if (ud->walktimer != -1) + unit_stop_walking(bl,1); + } + } + break; + case SC_ADORAMUS: + status_change_end(bl, SC_BLIND, INVALID_TIMER); + break; + case SC__SHADOWFORM: { + struct map_session_data *s_sd = map_id2sd(sce->val2); + if( !s_sd ) + break; + s_sd->shadowform_id = 0; + } + break; + case SC_SITDOWN_FORCE: + if( sd && pc_issit(sd) ) { + pc_setstand(sd); + clif_standing(bl); + } + break; + case SC_NEUTRALBARRIER_MASTER: + case SC_STEALTHFIELD_MASTER: + if( sce->val2 ) { + struct skill_unit_group* group = skill_id2group(sce->val2); + sce->val2 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_BANDING: + if(sce->val4) { + struct skill_unit_group *group = skill_id2group(sce->val4); + sce->val4 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_CURSEDCIRCLE_ATKER: + if( sce->val2 ) // used the default area size cause there is a chance the caster could knock back and can't clear the target. + map_foreachinrange(status_change_timer_sub, bl, battle_config.area_size,BL_CHAR, bl, sce, SC_CURSEDCIRCLE_TARGET, gettick()); + break; + case SC_RAISINGDRAGON: + if( sd && sce->val2 && !pc_isdead(sd) ) { + int i; + i = min(sd->spiritball,5); + pc_delspiritball(sd, sd->spiritball, 0); + status_change_end(bl, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + while( i > 0 ) { + pc_addspiritball(sd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), 5); + --i; + } + } + break; + case SC_CURSEDCIRCLE_TARGET: + { + struct block_list *src = map_id2bl(sce->val2); + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] && --(sc->data[SC_CURSEDCIRCLE_ATKER]->val2) == 0 ){ + status_change_end(src, SC_CURSEDCIRCLE_ATKER, INVALID_TIMER); + clif_bladestop(bl, sce->val2, 0); + } + } + break; + case SC_BLOODSUCKER: + if( sce->val2 ){ + struct block_list *src = map_id2bl(sce->val2); + if(src){ + struct status_change *sc = status_get_sc(src); + sc->bs_counter--; + } + } + break; + case SC_VACUUM_EXTREME: + if(sc && sc->cant.move > 0) sc->cant.move--; + break; + case SC_KYOUGAKU: + clif_status_load(bl, SI_KYOUGAKU, 0); // Avoid client crash + clif_status_load(bl, SI_ACTIVE_MONSTER_TRANSFORM, 0); + break; + case SC_INTRAVISION: + calc_flag = SCB_ALL;/* required for overlapping */ + break; + } + + opt_flag = 1; + switch(type){ + case SC_STONE: + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + case SC_DEEPSLEEP: + case SC_BURNING: + case SC_WHITEIMPRISON: + case SC_CRYSTALIZE: + sc->opt1 = 0; + break; + + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + sc->opt2 &= ~(1<<(type-SC_POISON)); + break; + case SC_DPOISON: + sc->opt2 &= ~OPT2_DPOISON; + break; + case SC_SIGNUMCRUCIS: + sc->opt2 &= ~OPT2_SIGNUMCRUCIS; + break; + + case SC_HIDING: + sc->option &= ~OPTION_HIDE; + opt_flag|= 2|4; //Check for warp trigger + AoE trigger + break; + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC__INVISIBILITY: + sc->option &= ~OPTION_CLOAK; + case SC_CAMOUFLAGE: + opt_flag|= 2; + break; + case SC_CHASEWALK: + sc->option &= ~(OPTION_CHASEWALK|OPTION_CLOAK); + opt_flag|= 2; + break; + case SC_SIGHT: + sc->option &= ~OPTION_SIGHT; + break; + case SC_WEDDING: + sc->option &= ~OPTION_WEDDING; + break; + case SC_XMAS: + sc->option &= ~OPTION_XMAS; + break; + case SC_SUMMER: + sc->option &= ~OPTION_SUMMER; + break; + case SC_ORCISH: + sc->option &= ~OPTION_ORCISH; + break; + case SC_RUWACH: + sc->option &= ~OPTION_RUWACH; + break; + case SC_FUSION: + sc->option &= ~OPTION_FLYING; + break; + //opt3 + case SC_TWOHANDQUICKEN: + case SC_ONEHAND: + case SC_SPEARQUICKEN: + case SC_CONCENTRATION: + case SC_MERC_QUICKEN: + sc->opt3 &= ~OPT3_QUICKEN; + opt_flag = 0; + break; + case SC_OVERTHRUST: + case SC_MAXOVERTHRUST: + case SC_SWOO: + sc->opt3 &= ~OPT3_OVERTHRUST; + if( type == SC_SWOO ) + opt_flag = 8; + else + opt_flag = 0; + break; + case SC_ENERGYCOAT: + case SC_SKE: + sc->opt3 &= ~OPT3_ENERGYCOAT; + opt_flag = 0; + break; + case SC_INCATKRATE: //Simulated Explosion spirits effect. + if (bl->type != BL_MOB) + { + opt_flag = 0; + break; + } + case SC_EXPLOSIONSPIRITS: + sc->opt3 &= ~OPT3_EXPLOSIONSPIRITS; + opt_flag = 0; + break; + case SC_STEELBODY: + case SC_SKA: + sc->opt3 &= ~OPT3_STEELBODY; + opt_flag = 0; + break; + case SC_BLADESTOP: + sc->opt3 &= ~OPT3_BLADESTOP; + opt_flag = 0; + break; + case SC_AURABLADE: + sc->opt3 &= ~OPT3_AURABLADE; + opt_flag = 0; + break; + case SC_BERSERK: + opt_flag = 0; +// case SC__BLOODYLUST: + sc->opt3 &= ~OPT3_BERSERK; + break; +// case ???: // doesn't seem to do anything +// sc->opt3 &= ~OPT3_LIGHTBLADE; +// opt_flag = 0; +// break; + case SC_DANCING: + if ((sce->val1&0xFFFF) == CG_MOONLIT) + sc->opt3 &= ~OPT3_MOONLIT; + opt_flag = 0; + break; + case SC_MARIONETTE: + case SC_MARIONETTE2: + sc->opt3 &= ~OPT3_MARIONETTE; + opt_flag = 0; + break; + case SC_ASSUMPTIO: + sc->opt3 &= ~OPT3_ASSUMPTIO; + opt_flag = 0; + break; + case SC_WARM: //SG skills [Komurka] + sc->opt3 &= ~OPT3_WARM; + opt_flag = 0; + break; + case SC_KAITE: + sc->opt3 &= ~OPT3_KAITE; + opt_flag = 0; + break; + case SC_BUNSINJYUTSU: + sc->opt3 &= ~OPT3_BUNSIN; + opt_flag = 0; + break; + case SC_SPIRIT: + sc->opt3 &= ~OPT3_SOULLINK; + opt_flag = 0; + break; + case SC_CHANGEUNDEAD: + sc->opt3 &= ~OPT3_UNDEAD; + opt_flag = 0; + break; +// case ???: // from DA_CONTRACT (looks like biolab mobs aura) +// sc->opt3 &= ~OPT3_CONTRACT; +// opt_flag = 0; +// break; + default: + opt_flag = 0; + } + + if (calc_flag&SCB_DYE) + { //Restore DYE color + if (vd && !vd->cloth_color && sce->val4) + clif_changelook(bl,LOOK_CLOTHES_COLOR,sce->val4); + calc_flag&=~SCB_DYE; + } + + //On Aegis, when turning off a status change, first goes the sc packet, then the option packet. + clif_status_change(bl,StatusIconChangeTable[type],0,0,0,0,0); + + if( opt_flag&8 ) //bugreport:681 + clif_changeoption2(bl); + else if(opt_flag) + clif_changeoption(bl); + + if (calc_flag) + status_calc_bl(bl,calc_flag); + + if(opt_flag&4) //Out of hiding, invoke on place. + skill_unit_move(bl,gettick(),1); + + if(opt_flag&2 && sd && map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC)) + npc_touch_areanpc(sd,bl->m,bl->x,bl->y); //Trigger on-touch event. + + ers_free(sc_data_ers, sce); + return 1; +} + +int kaahi_heal_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl; + struct status_change *sc; + struct status_change_entry *sce; + struct status_data *status; + int hp; + + if(!((bl=map_id2bl(id))&& + (sc=status_get_sc(bl)) && + (sce = sc->data[SC_KAAHI]))) + return 0; + + if(sce->val4 != tid) { + ShowError("kaahi_heal_timer: Timer mismatch: %d != %d\n", tid, sce->val4); + sce->val4 = INVALID_TIMER; + return 0; + } + + status=status_get_status_data(bl); + if(!status_charge(bl, 0, sce->val3)) { + sce->val4 = INVALID_TIMER; + return 0; + } + + hp = status->max_hp - status->hp; + if (hp > sce->val2) + hp = sce->val2; + if (hp) + status_heal(bl, hp, 0, 2); + sce->val4 = INVALID_TIMER; + return 1; +} + +/*========================================== + * For recusive status, like for each 5s we drop sp etc. + * Reseting the end timer. + *------------------------------------------*/ +int status_change_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + enum sc_type type = (sc_type)data; + struct block_list *bl; + struct map_session_data *sd; + struct status_data *status; + struct status_change *sc; + struct status_change_entry *sce; + + bl = map_id2bl(id); + if(!bl) + { + ShowDebug("status_change_timer: Null pointer id: %d data: %d\n", id, data); + return 0; + } + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if(!(sc && (sce = sc->data[type]))) + { + ShowDebug("status_change_timer: Null pointer id: %d data: %d bl-type: %d\n", id, data, bl->type); + return 0; + } + + if( sce->timer != tid ) + { + ShowError("status_change_timer: Mismatch for type %d: %d != %d (bl id %d)\n",type,tid,sce->timer, bl->id); + return 0; + } + + sd = BL_CAST(BL_PC, bl); + +// set the next timer of the sce (don't assume the status still exists) +#define sc_timer_next(t,f,i,d) \ + if( (sce=sc->data[type]) ) \ + sce->timer = add_timer(t,f,i,d); \ + else \ + ShowError("status_change_timer: Unexpected NULL status change id: %d data: %d\n", id, data) + + switch(type) + { + case SC_MAXIMIZEPOWER: + case SC_CLOAKING: + if(!status_charge(bl, 0, 1)) + break; //Not enough SP to continue. + sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); + return 0; + + case SC_CHASEWALK: + if(!status_charge(bl, 0, sce->val4)) + break; //Not enough SP to continue. + + if (!sc->data[SC_INCSTR]) { + sc_start(bl, SC_INCSTR,100,1<<(sce->val1-1), + (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE?10:1) //SL bonus -> x10 duration + *skill_get_time2(status_sc2skill(type),sce->val1)); + } + sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); + return 0; + break; + + case SC_SKA: + if(--(sce->val2)>0){ + sce->val3 = rnd()%100; //Random defense. + sc_timer_next(1000+tick, status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_HIDING: + if(--(sce->val2)>0){ + + if(sce->val2 % sce->val4 == 0 && !status_charge(bl, 0, 1)) + break; //Fail if it's time to substract SP and there isn't. + + sc_timer_next(1000+tick, status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_SIGHT: + case SC_RUWACH: + case SC_SIGHTBLASTER: + if(type == SC_SIGHTBLASTER) + map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR|BL_SKILL, bl, sce, type, tick); + else + map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR, bl, sce, type, tick); + + if( --(sce->val2)>0 ){ + sce->val4 += 250; // use for Shadow Form 2 seconds checking. + sc_timer_next(250+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_PROVOKE: + if(sce->val2) { //Auto-provoke (it is ended in status_heal) + sc_timer_next(1000*60+tick,status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_STONE: + if(sc->opt1 == OPT1_STONEWAIT && sce->val3) { + sce->val4 = 0; + unit_stop_walking(bl,1); + unit_stop_attack(bl); + sc->opt1 = OPT1_STONE; + clif_changeoption(bl); + sc_timer_next(1000+tick,status_change_timer, bl->id, data ); + status_calc_bl(bl, StatusChangeFlagTable[type]); + return 0; + } + if(--(sce->val3) > 0) { + if(++(sce->val4)%5 == 0 && status->hp > status->max_hp/4) + status_percent_damage(NULL, bl, 1, 0, false); + sc_timer_next(1000+tick,status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_POISON: + if(status->hp <= max(status->max_hp>>2, sce->val4)) //Stop damaging after 25% HP left. + break; + case SC_DPOISON: + if (--(sce->val3) > 0) { + if (!sc->data[SC_SLOWPOISON]) { + if( sce->val2 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(sce->val2); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,sce->val4); + } + map_freeblock_lock(); + status_zap(bl, sce->val4, 0); + if (sc->data[type]) { // Check if the status still last ( can be dead since then ). + sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + } + return 0; + } + break; + + case SC_TENSIONRELAX: + if(status->max_hp > status->hp && --(sce->val3) > 0){ + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_KNOWLEDGE: + if (!sd) break; + if(bl->m == sd->feel_map[0].m || + bl->m == sd->feel_map[1].m || + bl->m == sd->feel_map[2].m) + { //Timeout will be handled by pc_setpos + sce->timer = INVALID_TIMER; + return 0; + } + break; + + case SC_BLEEDING: + if (--(sce->val4) >= 0) { + int hp = rnd()%600 + 200; + map_freeblock_lock(); + status_fix_damage(NULL, bl, sd||hp<status->hp?hp:status->hp-1, 1); + if( sc->data[type] ) { + if( status->hp == 1 ) { + map_freeblock_unlock(); + break; + } + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + if( sd && --(sce->val4) >= 0 ) + { + // val1 < 0 = per max% | val1 > 0 = exact amount + int hp = 0; + if( status->hp < status->max_hp ) + hp = (sce->val1 < 0) ? (int)(sd->status.max_hp * -1 * sce->val1 / 100.) : sce->val1 ; + status_heal(bl, hp, 0, 2); + sc_timer_next((sce->val2 * 1000) + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_BOSSMAPINFO: + if( sd && --(sce->val4) >= 0 ) + { + struct mob_data *boss_md = map_id2boss(sce->val1); + if( boss_md && sd->bl.m == boss_md->bl.m ) + { + clif_bossmapinfo(sd->fd, boss_md, 1); // Update X - Y on minimap + if (boss_md->bl.prev != NULL) { + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + } + } + break; + + case SC_DANCING: //SP consumption by time of dancing skills + { + int s = 0; + int sp = 1; + if (--sce->val3 <= 0) + break; + switch(sce->val1&0xFFFF){ + case BD_RICHMANKIM: + case BD_DRUMBATTLEFIELD: + case BD_RINGNIBELUNGEN: + case BD_SIEGFRIED: + case BA_DISSONANCE: + case BA_ASSASSINCROSS: + case DC_UGLYDANCE: + s=3; + break; + case BD_LULLABY: + case BD_ETERNALCHAOS: + case BD_ROKISWEIL: + case DC_FORTUNEKISS: + s=4; + break; + case CG_HERMODE: + case BD_INTOABYSS: + case BA_WHISTLE: + case DC_HUMMING: + case BA_POEMBRAGI: + case DC_SERVICEFORYOU: + s=5; + break; + case BA_APPLEIDUN: + #ifdef RENEWAL + s=5; + #else + s=6; + #endif + break; + case CG_MOONLIT: + //Moonlit's cost is 4sp*skill_lv [Skotlex] + sp= 4*(sce->val1>>16); + //Upkeep is also every 10 secs. + case DC_DONTFORGETME: + s=10; + break; + } + if( s != 0 && sce->val3 % s == 0 ) + { + if (sc->data[SC_LONGING]) + sp*= 3; + if (!status_charge(bl, 0, sp)) + break; + } + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC__BLOODYLUST: + case SC_BERSERK: + // 5% every 10 seconds [DracoRPG] + if( --( sce->val3 ) > 0 && status_charge(bl, sce->val2, 0) && status->hp > 100 ) + { + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_NOCHAT: + if(sd){ + sd->status.manner++; + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + if (sd->status.manner < 0) + { //Every 60 seconds your manner goes up by 1 until it gets back to 0. + sc_timer_next(60000+tick, status_change_timer, bl->id, data); + return 0; + } + } + break; + + case SC_SPLASHER: + // custom Venom Splasher countdown timer + //if (sce->val4 % 1000 == 0) { + // char timer[10]; + // snprintf (timer, 10, "%d", sce->val4/1000); + // clif_message(bl, timer); + //} + if((sce->val4 -= 500) > 0) { + sc_timer_next(500 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_MARIONETTE: + case SC_MARIONETTE2: + { + struct block_list *pbl = map_id2bl(sce->val1); + if( pbl && check_distance_bl(bl, pbl, 7) ) + { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + } + break; + + case SC_GOSPEL: + if(sce->val4 == BCT_SELF && --(sce->val2) > 0) + { + int hp, sp; + hp = (sce->val1 > 5) ? 45 : 30; + sp = (sce->val1 > 5) ? 35 : 20; + if(!status_charge(bl, hp, sp)) + break; + sc_timer_next(10000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_JAILED: + if(sce->val1 == INT_MAX || --(sce->val1) > 0) + { + sc_timer_next(60000+tick, status_change_timer, bl->id,data); + return 0; + } + break; + + case SC_BLIND: + if(sc->data[SC_FOGWALL]) + { //Blind lasts forever while you are standing on the fog. + sc_timer_next(5000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_ABUNDANCE: + if(--(sce->val4) > 0) { + status_heal(bl,0,60,0); + sc_timer_next(10000+tick, status_change_timer, bl->id, data); + } + break; + + case SC_PYREXIA: + if( --(sce->val4) >= 0 ) { + map_freeblock_lock(); + clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,100,0,0,0); + status_fix_damage(NULL,bl,100,0); + if( sc->data[type] ) { + sc_timer_next(3000+tick,status_change_timer,bl->id,data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_LEECHESEND: + if( --(sce->val4) >= 0 ) { + int damage = status->max_hp/100; // {Target VIT x (New Poison Research Skill Level - 3)} + (Target HP/100) + damage += status->vit * (sce->val1 - 3); + unit_skillcastcancel(bl,2); + map_freeblock_lock(); + status_damage(bl, bl, damage, 0, clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,damage,1,0,0), 1); + if( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_MAGICMUSHROOM: + if( --(sce->val4) >= 0 ) { + bool flag = 0; + int damage = status->max_hp * 3 / 100; + if( status->hp <= damage ) + damage = status->hp - 1; // Cannot Kill + + if( damage > 0 ) { // 3% Damage each 4 seconds + map_freeblock_lock(); + status_zap(bl,damage,0); + flag = !sc->data[type]; // Killed? Should not + map_freeblock_unlock(); + } + + if( !flag ) { // Random Skill Cast + if (sd && !pc_issit(sd)) { //can't cast if sit + int mushroom_skill_id = 0, i; + unit_stop_attack(bl); + unit_skillcastcancel(bl,1); + do { + i = rnd() % MAX_SKILL_MAGICMUSHROOM_DB; + mushroom_skill_id = skill_magicmushroom_db[i].skill_id; + } + while( mushroom_skill_id == 0 ); + + switch( skill_get_casttype(mushroom_skill_id) ) { // Magic Mushroom skills are buffs or area damage + case CAST_GROUND: + skill_castend_pos2(bl,bl->x,bl->y,mushroom_skill_id,1,tick,0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(bl,bl,mushroom_skill_id,1,tick,0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(bl,bl,mushroom_skill_id,1,tick,0); + break; + } + } + + clif_emotion(bl,E_HEH); + sc_timer_next(4000+tick,status_change_timer,bl->id,data); + } + return 0; + } + break; + + case SC_TOXIN: + if( --(sce->val4) >= 0 ) + { //Damage is every 10 seconds including 3%sp drain. + map_freeblock_lock(); + clif_damage(bl,bl,tick,status_get_amotion(bl),1,1,0,0,0); + status_damage(NULL, bl, 1, status->max_sp * 3 / 100, 0, 0); //cancel dmg only if cancelable + if( sc->data[type] ) { + sc_timer_next(10000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_OBLIVIONCURSE: + if( --(sce->val4) >= 0 ) + { + clif_emotion(bl,E_WHAT); + sc_timer_next(3000 + tick, status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_WEAPONBLOCKING: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0,3) ) + break; + sc_timer_next(3000+tick,status_change_timer,bl->id,data); + return 0; + } + break; + + case SC_CLOAKINGEXCEED: + if(!status_charge(bl,0,10-sce->val1)) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_RENOVATIO: + if( --(sce->val4) >= 0 ) + { + int heal = status->max_hp * 3 / 100; + if( sc && sc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + status_heal(bl, heal, 0, 2); + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_BURNING: + if( --(sce->val4) >= 0 ) + { + struct block_list *src = map_id2bl(sce->val3); + int damage = 1000 + 3 * status_get_max_hp(bl) / 100; // Deals fixed (1000 + 3%*MaxHP) + + map_freeblock_lock(); + clif_damage(bl,bl,tick,0,0,damage,1,9,0); //damage is like endure effect with no walk delay + status_damage(src, bl, damage, 0, 0, 1); + + if( sc->data[type]){ // Target still lives. [LimitLine] + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_FEAR: + if( --(sce->val4) >= 0 ) + { + if( sce->val2 > 0 ) + sce->val2--; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SPHERE_1: + case SC_SPHERE_2: + case SC_SPHERE_3: + case SC_SPHERE_4: + case SC_SPHERE_5: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, 1) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_READING_SB: + if( !status_charge(bl, 0, sce->val2) ){ + int i; + for(i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) // Also remove stored spell as well. + status_change_end(bl, (sc_type)i, INVALID_TIMER); + break; + } + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_ELECTRICSHOCKER: + if( --(sce->val4) >= 0 ) + { + status_charge(bl, 0, status->max_sp / 100 * sce->val1 ); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CAMOUFLAGE: + if(--(sce->val4) > 0){ + status_charge(bl,0,7 - sce->val1); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC__REPRODUCE: + if(!status_charge(bl, 0, 1)) + break; + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + + case SC__SHADOWFORM: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, sce->val1 - (sce->val1 - 1)) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC__INVISIBILITY: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, (status->sp * 6 - sce->val1) / 100) )// 6% - skill_lv. + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_STRIKING: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0, sce->val1 ) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_VACUUM_EXTREME: + if( --(sce->val4) >= 0 ){ + if( !unit_is_walking(bl) && !sce->val2 ){ + sc->cant.move++; + sce->val2 = 1; + } + sc_timer_next(100 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_BLOODSUCKER: + if( --(sce->val4) >= 0 ) { + struct block_list *src = map_id2bl(sce->val2); + int damage; + if( !src || (src && (status_isdead(src) || src->m != bl->m || distance_bl(src, bl) >= 12)) ) + break; + map_freeblock_lock(); + damage = 200 + 100 * sce->val1 + status_get_int(src); + status_damage(src, bl, damage, 0, clif_damage(bl,bl,tick,status->amotion,status->dmotion+200,damage,1,0,0), 1); + unit_skillcastcancel(bl,1); + if ( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + status_heal(src, damage*(5 + 5 * sce->val1)/100, 0, 0); // 5 + 5% per level + return 0; + } + break; + + case SC_VOICEOFSIREN: + if( --(sce->val4) >= 0 ) + { + clif_emotion(bl,E_LV); + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_DEEPSLEEP: + if( --(sce->val4) >= 0 ) + { // Recovers 1% HP/SP every 2 seconds. + status_heal(bl, status->max_hp / 100, status->max_sp / 100, 2); + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SIRCLEOFNATURE: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0,sce->val2) ) + break; + status_heal(bl, sce->val3, 0, 1); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SONGOFMANA: + if( --(sce->val4) >= 0 ) + { + status_heal(bl,0,sce->val3,3); + sc_timer_next(3000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + + case SC_SATURDAYNIGHTFEVER: + // 1% HP/SP drain every val4 seconds [Jobbie] + if( --(sce->val3) >= 0 ) + { + int hp = status->hp / 100; + int sp = status->sp / 100; + if( !status_charge(bl, hp, sp) ) + break; + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CRYSTALIZE: + if( --(sce->val4) >= 0 ) + { // Drains 2% of HP and 1% of SP every seconds. + if( bl->type != BL_MOB) // doesn't work on mobs + status_charge(bl, status->max_hp * 2 / 100, status->max_sp / 100); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_FORCEOFVANGUARD: + if( !status_charge(bl,0,20) ) + break; + sc_timer_next(6000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_BANDING: + if( status_charge(bl, 0, 7 - sce->val1) ) + { + if( sd ) pc_banding(sd, sce->val1); + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_REFLECTDAMAGE: + if( --(sce->val4) >= 0 ) { + if( !status_charge(bl,0,sce->val3) ) + break; + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_OVERHEAT_LIMITPOINT: + if( --(sce->val1) > 0 ) { // Cooling + sc_timer_next(30000 + tick, status_change_timer, bl->id, data); + } + break; + + case SC_OVERHEAT: + { + int damage = status->max_hp / 100; // Suggestion 1% each second + if( damage >= status->hp ) damage = status->hp - 1; // Do not kill, just keep you with 1 hp minimum + map_freeblock_lock(); + status_fix_damage(NULL,bl,damage,clif_damage(bl,bl,tick,0,0,damage,0,0,0)); + if( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + } + break; + + case SC_MAGNETICFIELD: + { + if( --(sce->val3) <= 0 ) + break; // Time out + if( sce->val2 == bl->id ) + { + if( !status_charge(bl,0,14 + (3 * sce->val1)) ) + break; // No more SP status should end, and in the next second will end for the other affected players + } + else + { + struct block_list *src = map_id2bl(sce->val2); + struct status_change *ssc; + if( !src || (ssc = status_get_sc(src)) == NULL || !ssc->data[SC_MAGNETICFIELD] ) + break; // Source no more under Magnetic Field + } + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + break; + + case SC_INSPIRATION: + if(--(sce->val4) >= 0) + { + int hp = status->max_hp * (7-sce->val1) / 100; + int sp = status->max_sp * (9-sce->val1) / 100; + + if( !status_charge(bl,hp,sp) ) break; + + sc_timer_next(1000+tick,status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_RAISINGDRAGON: + // 1% every 5 seconds [Jobbie] + if( --(sce->val3)>0 && status_charge(bl, sce->val2, 0) ) + { + if( !sc->data[type] ) return 0; + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CIRCLE_OF_FIRE: + case SC_FIRE_CLOAK: + case SC_WATER_DROP: + case SC_WATER_SCREEN: + case SC_WIND_CURTAIN: + case SC_WIND_STEP: + case SC_STONE_SHIELD: + case SC_SOLID_SKIN: + if( !status_charge(bl,0,sce->val2) ){ + struct block_list *s_bl = battle_get_master(bl); + if( s_bl ) + status_change_end(s_bl,type+1,INVALID_TIMER); + status_change_end(bl,type,INVALID_TIMER); + break; + } + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_STOMACHACHE: + if( --(sce->val4) > 0 ){ + status_charge(bl,0,sce->val2); // Reduce 8 every 10 seconds. + if( sd && !pc_issit(sd) ) // Force to sit every 10 seconds. + { + pc_stop_walking(sd,1|4); + pc_stop_attack(sd); + pc_setsit(sd); + clif_sitting(bl); + } + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + /* they only end by status_change_end */ + sc_timer_next(600000 + tick, status_change_timer, bl->id, data); + return 0; + case SC_MEIKYOUSISUI: + if( --(sce->val4) > 0 ){ + status_heal(bl, status->max_hp * (sce->val1+1) / 100, status->max_sp * sce->val1 / 100, 0); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_IZAYOI: + case SC_KAGEMUSYA: + if( --(sce->val2) > 0 ){ + if(!status_charge(bl, 0, 1)) break; + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_ANGRIFFS_MODUS: + if(--(sce->val4) >= 0) { //drain hp/sp + if( !status_charge(bl,100,20) ) break; + sc_timer_next(1000+tick,status_change_timer,bl->id, data); + return 0; + } + break; + } + + // default for all non-handled control paths is to end the status + return status_change_end( bl,type,tid ); +#undef sc_timer_next +} + +/*========================================== + * Foreach iteration of repetitive status + *------------------------------------------*/ +int status_change_timer_sub(struct block_list* bl, va_list ap) +{ + struct status_change* tsc; + + struct block_list* src = va_arg(ap,struct block_list*); + struct status_change_entry* sce = va_arg(ap,struct status_change_entry*); + enum sc_type type = (sc_type)va_arg(ap,int); //gcc: enum args get promoted to int + unsigned int tick = va_arg(ap,unsigned int); + + if (status_isdead(bl)) + return 0; + + tsc = status_get_sc(bl); + + switch( type ) { + case SC_SIGHT: /* Reveal hidden ennemy on 3*3 range */ + if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking + rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + case SC_CONCENTRATE: + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + break; + case SC_RUWACH: /* Reveal hidden target and deal little dammages if ennemy */ + if (tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || + tsc->data[SC_CAMOUFLAGE] || tsc->data[SC_CLOAKINGEXCEED] || + tsc->data[SC__INVISIBILITY])) { + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + if(battle_check_target( src, bl, BCT_ENEMY ) > 0) + skill_attack(BF_MAGIC,src,src,bl,AL_RUWACH,1,tick,0); + } + if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking + rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + break; + case SC_SIGHTBLASTER: + if (battle_check_target( src, bl, BCT_ENEMY ) > 0 && + status_check_skilluse(src, bl, WZ_SIGHTBLASTER, 2)) + { + skill_attack(BF_MAGIC,src,src,bl,WZ_SIGHTBLASTER,1,tick,0); + if (sce && !(bl->type&BL_SKILL)) //The hit is not counted if it's against a trap + sce->val2 = 0; //This signals it to end. + } + break; + case SC_CLOSECONFINE: + //Lock char has released the hold on everyone... + if (tsc && tsc->data[SC_CLOSECONFINE2] && tsc->data[SC_CLOSECONFINE2]->val2 == src->id) { + tsc->data[SC_CLOSECONFINE2]->val2 = 0; + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); + } + break; + case SC_CURSEDCIRCLE_TARGET: + if( tsc && tsc->data[SC_CURSEDCIRCLE_TARGET] && tsc->data[SC_CURSEDCIRCLE_TARGET]->val2 == src->id ) { + clif_bladestop(bl, tsc->data[SC_CURSEDCIRCLE_TARGET]->val2, 0); + status_change_end(bl, type, INVALID_TIMER); + } + break; + } + return 0; +} + +/*========================================== + * Clears buffs/debuffs of a character. + * type&1 -> buffs, type&2 -> debuffs + * type&4 -> especific debuffs(implemented with refresh) + *------------------------------------------*/ +int status_change_clear_buffs (struct block_list* bl, int type) +{ + int i; + struct status_change *sc= status_get_sc(bl); + + if (!sc || !sc->count) + return 0; + + if (type&6) //Debuffs + for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) + status_change_end(bl, (sc_type)i, INVALID_TIMER); + + for( i = SC_COMMON_MAX+1; i < SC_MAX; i++ ) + { + if(!sc->data[i]) + continue; + + switch (i) { + //Stuff that cannot be removed + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_COMBO: + case SC_SMA: + case SC_DANCING: + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + case SC_GUILDAURA: + case SC_SAFETYWALL: + case SC_PNEUMA: + case SC_NOCHAT: + case SC_JAILED: + case SC_ANKLE: + case SC_BLADESTOP: + case SC_CP_WEAPON: + case SC_CP_SHIELD: + case SC_CP_ARMOR: + case SC_CP_HELM: + case SC_STRFOOD: + case SC_AGIFOOD: + case SC_VITFOOD: + case SC_INTFOOD: + case SC_DEXFOOD: + case SC_LUKFOOD: + case SC_HITFOOD: + case SC_FLEEFOOD: + case SC_BATKFOOD: + case SC_WATKFOOD: + case SC_MATKFOOD: + case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: + case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: + case SC_EXPBOOST: + case SC_JEXPBOOST: + case SC_ITEMBOOST: + case SC_ELECTRICSHOCKER: + case SC__MANHOLE: + case SC_GIANTGROWTH: + case SC_MILLENNIUMSHIELD: + case SC_REFRESH: + case SC_STONEHARDSKIN: + case SC_VITALITYACTIVATION: + case SC_FIGHTINGSPIRIT: + case SC_ABUNDANCE: + case SC_CURSEDCIRCLE_ATKER: + case SC_CURSEDCIRCLE_TARGET: + continue; + + //Debuffs that can be removed. + case SC_DEEPSLEEP: + case SC_BURNING: + case SC_FREEZING: + case SC_CRYSTALIZE: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC_MARSHOFABYSS: + case SC_MANDRAGORA: + if(!(type&4)) + continue; + break; + case SC_HALLUCINATION: + case SC_QUAGMIRE: + case SC_SIGNUMCRUCIS: + case SC_DECREASEAGI: + case SC_SLOWDOWN: + case SC_MINDBREAKER: + case SC_WINKCHARM: + case SC_STOP: + case SC_ORCISH: + case SC_STRIPWEAPON: + case SC_STRIPSHIELD: + case SC_STRIPARMOR: + case SC_STRIPHELM: + case SC_BITE: + case SC_ADORAMUS: + case SC_VACUUM_EXTREME: + case SC_FEAR: + case SC_MAGNETICFIELD: + case SC_NETHERWORLD: + if (!(type&2)) + continue; + break; + //The rest are buffs that can be removed. + case SC__BLOODYLUST: + case SC_BERSERK: + case SC_SATURDAYNIGHTFEVER: + if (!(type&1)) + continue; + sc->data[i]->val2 = 0; + break; + default: + if (!(type&1)) + continue; + break; + } + status_change_end(bl, (sc_type)i, INVALID_TIMER); + } + return 0; +} + +int status_change_spread( struct block_list *src, struct block_list *bl ) { + int i, flag = 0; + struct status_change *sc = status_get_sc(src); + const struct TimerData *timer; + unsigned int tick; + struct status_change_data data; + + if( !sc || !sc->count ) + return 0; + + tick = gettick(); + + for( i = SC_COMMON_MIN; i < SC_MAX; i++ ) { + if( !sc->data[i] || i == SC_COMMON_MAX ) + continue; + + switch( i ) { + //Debuffs that can be spreaded. + // NOTE: We'll add/delte SCs when we are able to confirm it. + case SC_CURSE: + case SC_SILENCE: + case SC_CONFUSION: + case SC_BLIND: + case SC_NOCHAT: + case SC_HALLUCINATION: + case SC_SIGNUMCRUCIS: + case SC_DECREASEAGI: + case SC_SLOWDOWN: + case SC_MINDBREAKER: + case SC_WINKCHARM: + case SC_STOP: + case SC_ORCISH: + //case SC_STRIPWEAPON://Omg I got infected and had the urge to strip myself physically. + //case SC_STRIPSHIELD://No this is stupid and shouldnt be spreadable at all. + //case SC_STRIPARMOR:// Disabled until I can confirm if it does or not. [Rytech] + //case SC_STRIPHELM: + //case SC__STRIPACCESSORY: + case SC_BITE: + case SC_FREEZING: + case SC_VENOMBLEED: + case SC_DEATHHURT: + case SC_PARALYSE: + if( sc->data[i]->timer != INVALID_TIMER ) { + timer = get_timer(sc->data[i]->timer); + if (timer == NULL || timer->func != status_change_timer || DIFF_TICK(timer->tick,tick) < 0) + continue; + data.tick = DIFF_TICK(timer->tick,tick); + } else + data.tick = INVALID_TIMER; + break; + // Special cases + case SC_POISON: + case SC_DPOISON: + data.tick = sc->data[i]->val3 * 1000; + break; + case SC_FEAR: + case SC_LEECHESEND: + data.tick = sc->data[i]->val4 * 1000; + break; + case SC_BURNING: + data.tick = sc->data[i]->val4 * 2000; + break; + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + data.tick = sc->data[i]->val4 * 3000; + break; + case SC_MAGICMUSHROOM: + data.tick = sc->data[i]->val4 * 4000; + break; + case SC_TOXIN: + case SC_BLEEDING: + data.tick = sc->data[i]->val4 * 10000; + break; + default: + continue; + break; + } + if( i ){ + data.val1 = sc->data[i]->val1; + data.val2 = sc->data[i]->val2; + data.val3 = sc->data[i]->val3; + data.val4 = sc->data[i]->val4; + status_change_start(bl,(sc_type)i,10000,data.val1,data.val2,data.val3,data.val4,data.tick,1|2|8); + flag = 1; + } + } + + return flag; +} + +//Natural regen related stuff. +static unsigned int natural_heal_prev_tick,natural_heal_diff_tick; +static int status_natural_heal(struct block_list* bl, va_list args) +{ + struct regen_data *regen; + struct status_data *status; + struct status_change *sc; + struct unit_data *ud; + struct view_data *vd = NULL; + struct regen_data_sub *sregen; + struct map_session_data *sd; + int val,rate,bonus = 0,flag; + + regen = status_get_regen_data(bl); + if (!regen) return 0; + status = status_get_status_data(bl); + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + sd = BL_CAST(BL_PC,bl); + + flag = regen->flag; + if (flag&RGN_HP && (status->hp >= status->max_hp || regen->state.block&1)) + flag&=~(RGN_HP|RGN_SHP); + if (flag&RGN_SP && (status->sp >= status->max_sp || regen->state.block&2)) + flag&=~(RGN_SP|RGN_SSP); + + if (flag && ( + status_isdead(bl) || + (sc && (sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || sc->data[SC__INVISIBILITY])) + )) + flag=0; + + if (sd) { + if (sd->hp_loss.value || sd->sp_loss.value) + pc_bleeding(sd, natural_heal_diff_tick); + if (sd->hp_regen.value || sd->sp_regen.value) + pc_regen(sd, natural_heal_diff_tick); + } + + if(flag&(RGN_SHP|RGN_SSP) && regen->ssregen && + (vd = status_get_viewdata(bl)) && vd->dead_sit == 2) + { //Apply sitting regen bonus. + sregen = regen->ssregen; + if(flag&(RGN_SHP)) + { //Sitting HP regen + val = natural_heal_diff_tick * sregen->rate.hp; + if (regen->state.overweight) + val>>=1; //Half as fast when overweight. + sregen->tick.hp += val; + while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.hp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) + { //Full + flag&=~(RGN_HP|RGN_SHP); + break; + } + } + } + if(flag&(RGN_SSP)) + { //Sitting SP regen + val = natural_heal_diff_tick * sregen->rate.sp; + if (regen->state.overweight) + val>>=1; //Half as fast when overweight. + sregen->tick.sp += val; + while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.sp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, 0, sregen->sp, 3) < sregen->sp) + { //Full + flag&=~(RGN_SP|RGN_SSP); + break; + } + } + } + } + + if (flag && regen->state.overweight) + flag=0; + + ud = unit_bl2ud(bl); + + if (flag&(RGN_HP|RGN_SHP|RGN_SSP) && ud && ud->walktimer != INVALID_TIMER) + { + flag&=~(RGN_SHP|RGN_SSP); + if(!regen->state.walk) + flag&=~RGN_HP; + } + + if (!flag) + return 0; + + if (flag&(RGN_HP|RGN_SP)) + { + if(!vd) vd = status_get_viewdata(bl); + if(vd && vd->dead_sit == 2) + bonus++; + if(regen->state.gc) + bonus++; + } + + //Natural Hp regen + if (flag&RGN_HP) + { + rate = natural_heal_diff_tick*(regen->rate.hp+bonus); + if (ud && ud->walktimer != INVALID_TIMER) + rate/=2; + // Homun HP regen fix (they should regen as if they were sitting (twice as fast) + if(bl->type==BL_HOM) rate *=2; + + regen->tick.hp += rate; + + if(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval) + { + val = 0; + do { + val += regen->hp; + regen->tick.hp -= battle_config.natural_healhp_interval; + } while(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval); + if (status_heal(bl, val, 0, 1) < val) + flag&=~RGN_SHP; //full. + } + } + + //Natural SP regen + if(flag&RGN_SP) + { + rate = natural_heal_diff_tick*(regen->rate.sp+bonus); + // Homun SP regen fix (they should regen as if they were sitting (twice as fast) + if(bl->type==BL_HOM) rate *=2; + + regen->tick.sp += rate; + + if(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval) + { + val = 0; + do { + val += regen->sp; + regen->tick.sp -= battle_config.natural_healsp_interval; + } while(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval); + if (status_heal(bl, 0, val, 1) < val) + flag&=~RGN_SSP; //full. + } + } + + if (!regen->sregen) + return flag; + + //Skill regen + sregen = regen->sregen; + + if(flag&RGN_SHP) + { //Skill HP regen + sregen->tick.hp += natural_heal_diff_tick * sregen->rate.hp; + + while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.hp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) + break; //Full + } + } + if(flag&RGN_SSP) + { //Skill SP regen + sregen->tick.sp += natural_heal_diff_tick * sregen->rate.sp; + while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + val = sregen->sp; + if (sd && sd->state.doridori) { + val*=2; + sd->state.doridori = 0; + if ((rate = pc_checkskill(sd,TK_SPTIME))) + sc_start(bl,status_skill2sc(TK_SPTIME), + 100,rate,skill_get_time(TK_SPTIME, rate)); + if ( + (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && + rnd()%10000 < battle_config.sg_angel_skill_ratio + ) { //Angel of the Sun/Moon/Star + clif_feel_hate_reset(sd); + pc_resethate(sd); + pc_resetfeel(sd); + } + } + sregen->tick.sp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, 0, val, 3) < val) + break; //Full + } + } + return flag; +} + +//Natural heal main timer. +static int status_natural_heal_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + natural_heal_diff_tick = DIFF_TICK(tick,natural_heal_prev_tick); + map_foreachregen(status_natural_heal); + natural_heal_prev_tick = tick; + return 0; +} + +/** + * Get the chance to upgrade a piece of equipment. + * @param wlv The weapon type of the item to refine (see see enum refine_type) + * @param refine The target refine level + * @return The chance to refine the item, in percent (0~100) + **/ +int status_get_refine_chance(enum refine_type wlv, int refine) { + + if ( refine < 0 || refine >= MAX_REFINE) + return 0; + + return refine_info[wlv].chance[refine]; +} + + +/*------------------------------------------ + * DB reading. + * job_db1.txt - weight, hp, sp, aspd + * job_db2.txt - job level stat bonuses + * size_fix.txt - size adjustment table for weapons + * refine_db.txt - refining data table + *------------------------------------------*/ +static bool status_readdb_job1(char* fields[], int columns, int current) +{// Job-specific values (weight, HP, SP, ASPD) + int idx, class_; + unsigned int i; + + class_ = atoi(fields[0]); + + if(!pcdb_checkid(class_)) + { + ShowWarning("status_readdb_job1: Invalid job class %d specified.\n", class_); + return false; + } + idx = pc_class2idx(class_); + + max_weight_base[idx] = atoi(fields[1]); + hp_coefficient[idx] = atoi(fields[2]); + hp_coefficient2[idx] = atoi(fields[3]); + sp_coefficient[idx] = atoi(fields[4]); +#ifdef RENEWAL_ASPD + for(i = 0; i <= MAX_WEAPON_TYPE; i++) +#else + for(i = 0; i < MAX_WEAPON_TYPE; i++) +#endif + { + aspd_base[idx][i] = atoi(fields[i+5]); + } + return true; +} + +static bool status_readdb_job2(char* fields[], int columns, int current) +{ + int idx, class_, i; + + class_ = atoi(fields[0]); + + if(!pcdb_checkid(class_)) + { + ShowWarning("status_readdb_job2: Invalid job class %d specified.\n", class_); + return false; + } + idx = pc_class2idx(class_); + + for(i = 1; i < columns; i++) + { + job_bonus[idx][i-1] = atoi(fields[i]); + } + return true; +} + +static bool status_readdb_sizefix(char* fields[], int columns, int current) +{ + unsigned int i; + + for(i = 0; i < MAX_WEAPON_TYPE; i++) + { + atkmods[current][i] = atoi(fields[i]); + } + return true; +} + +static bool status_readdb_refine(char* fields[], int columns, int current) +{ + int i, bonus_per_level, random_bonus, random_bonus_start_level; + + current = atoi(fields[0]); + + if (current < 0 || current >= REFINE_TYPE_MAX) + return false; + + bonus_per_level = atoi(fields[1]); + random_bonus_start_level = atoi(fields[2]); + random_bonus = atoi(fields[3]); + + for(i = 0; i < MAX_REFINE; i++) + { + char* delim; + + if (!(delim = strchr(fields[4+i], ':'))) + return false; + + *delim = '\0'; + + refine_info[current].chance[i] = atoi(fields[4+i]); + + if (i >= random_bonus_start_level - 1) + refine_info[current].randombonus_max[i] = random_bonus * (i - random_bonus_start_level + 2); + + refine_info[current].bonus[i] = bonus_per_level + atoi(delim+1); + if (i > 0) + refine_info[current].bonus[i] += refine_info[current].bonus[i-1]; + } + return true; +} + +/* +* Read status db +* job1.txt +* job2.txt +* size_fixe.txt +* refine_db.txt +*/ +int status_readdb(void) +{ + int i, j; + + // initialize databases to default + // + + // reset job_db1.txt data + memset(max_weight_base, 0, sizeof(max_weight_base)); + memset(hp_coefficient, 0, sizeof(hp_coefficient)); + memset(hp_coefficient2, 0, sizeof(hp_coefficient2)); + memset(sp_coefficient, 0, sizeof(sp_coefficient)); + memset(aspd_base, 0, sizeof(aspd_base)); + // reset job_db2.txt data + memset(job_bonus,0,sizeof(job_bonus)); // Job-specific stats bonus + + // size_fix.txt + for(i=0;i<ARRAYLENGTH(atkmods);i++) + for(j=0;j<MAX_WEAPON_TYPE;j++) + atkmods[i][j]=100; + + // refine_db.txt + for(i=0;i<ARRAYLENGTH(refine_info);i++) + { + for(j=0;j<MAX_REFINE; j++) + { + refine_info[i].chance[j] = 100; + refine_info[i].bonus[j] = 0; + refine_info[i].randombonus_max[j] = 0; + } + } + + // read databases + // + + +#ifdef RENEWAL_ASPD + sv_readdb(db_path, "re/job_db1.txt", ',', 6+MAX_WEAPON_TYPE, 6+MAX_WEAPON_TYPE, -1, &status_readdb_job1); +#else + sv_readdb(db_path, "pre-re/job_db1.txt", ',', 5+MAX_WEAPON_TYPE, 5+MAX_WEAPON_TYPE, -1, &status_readdb_job1); +#endif + sv_readdb(db_path, "job_db2.txt", ',', 1, 1+MAX_LEVEL, -1, &status_readdb_job2); + sv_readdb(db_path, "size_fix.txt", ',', MAX_WEAPON_TYPE, MAX_WEAPON_TYPE, ARRAYLENGTH(atkmods), &status_readdb_sizefix); + sv_readdb(db_path, DBPATH"refine_db.txt", ',', 4+MAX_REFINE, 4+MAX_REFINE, ARRAYLENGTH(refine_info), &status_readdb_refine); + + return 0; +} + +/*========================================== + * Status db init and destroy. + *------------------------------------------*/ +int do_init_status(void) +{ + add_timer_func_list(status_change_timer,"status_change_timer"); + add_timer_func_list(kaahi_heal_timer,"kaahi_heal_timer"); + add_timer_func_list(status_natural_heal_timer,"status_natural_heal_timer"); + initChangeTables(); + initDummyData(); + status_readdb(); + status_calc_sigma(); + natural_heal_prev_tick = gettick(); + sc_data_ers = ers_new(sizeof(struct status_change_entry),"status.c::sc_data_ers",ERS_OPT_NONE); + add_timer_interval(natural_heal_prev_tick + NATURAL_HEAL_INTERVAL, status_natural_heal_timer, 0, 0, NATURAL_HEAL_INTERVAL); + return 0; +} +void do_final_status(void) +{ + ers_destroy(sc_data_ers); +} diff --git a/src/map/status.h b/src/map/status.h new file mode 100644 index 000000000..44012566f --- /dev/null +++ b/src/map/status.h @@ -0,0 +1,1816 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _STATUS_H_ +#define _STATUS_H_ + +struct block_list; +struct mob_data; +struct pet_data; +struct homun_data; +struct mercenary_data; +struct status_change; + +/** + * Max Refine available to your server + * Changing this limit requires edits to refine_db.txt + **/ +#ifdef RENEWAL +# define MAX_REFINE 20 +#else +# define MAX_REFINE 10 +#endif + +enum refine_type { + REFINE_TYPE_ARMOR = 0, + REFINE_TYPE_WEAPON1 = 1, + REFINE_TYPE_WEAPON2 = 2, + REFINE_TYPE_WEAPON3 = 3, + REFINE_TYPE_WEAPON4 = 4, + + REFINE_TYPE_MAX = 5 +}; + +int status_get_refine_chance(enum refine_type wlv, int refine); + +// Status changes listing. These code are for use by the server. +typedef enum sc_type { + SC_NONE = -1, + + //First we enumerate common status ailments which are often used around. + SC_STONE = 0, + SC_COMMON_MIN = 0, // begin + SC_FREEZE, + SC_STUN, + SC_SLEEP, + SC_POISON, + SC_CURSE, + SC_SILENCE, + SC_CONFUSION, + SC_BLIND, + SC_BLEEDING, + SC_DPOISON, //10 + SC_COMMON_MAX = 10, // end + + //Next up, we continue on 20, to leave enough room for additional "common" ailments in the future. + SC_PROVOKE = 20, + SC_ENDURE, + SC_TWOHANDQUICKEN, + SC_CONCENTRATE, + SC_HIDING, + SC_CLOAKING, + SC_ENCPOISON, + SC_POISONREACT, + SC_QUAGMIRE, + SC_ANGELUS, + SC_BLESSING, //30 + SC_SIGNUMCRUCIS, + SC_INCREASEAGI, + SC_DECREASEAGI, + SC_SLOWPOISON, + SC_IMPOSITIO , + SC_SUFFRAGIUM, + SC_ASPERSIO, + SC_BENEDICTIO, + SC_KYRIE, + SC_MAGNIFICAT, //40 + SC_GLORIA, + SC_AETERNA, + SC_ADRENALINE, + SC_WEAPONPERFECTION, + SC_OVERTHRUST, + SC_MAXIMIZEPOWER, + SC_TRICKDEAD, + SC_LOUD, + SC_ENERGYCOAT, + SC_BROKENARMOR, //50 - NOTE: These two aren't used anywhere, and they have an icon... + SC_BROKENWEAPON, + SC_HALLUCINATION, + SC_WEIGHT50, + SC_WEIGHT90, + SC_ASPDPOTION0, + SC_ASPDPOTION1, + SC_ASPDPOTION2, + SC_ASPDPOTION3, + SC_SPEEDUP0, + SC_SPEEDUP1, //60 + SC_ATKPOTION, + SC_MATKPOTION, + SC_WEDDING, + SC_SLOWDOWN, + SC_ANKLE, + SC_KEEPING, + SC_BARRIER, + SC_STRIPWEAPON, + SC_STRIPSHIELD, + SC_STRIPARMOR, //70 + SC_STRIPHELM, + SC_CP_WEAPON, + SC_CP_SHIELD, + SC_CP_ARMOR, + SC_CP_HELM, + SC_AUTOGUARD, + SC_REFLECTSHIELD, + SC_SPLASHER, + SC_PROVIDENCE, + SC_DEFENDER, //80 + SC_MAGICROD, + SC_SPELLBREAKER, + SC_AUTOSPELL, + SC_SIGHTTRASHER, + SC_AUTOBERSERK, + SC_SPEARQUICKEN, + SC_AUTOCOUNTER, + SC_SIGHT, + SC_SAFETYWALL, + SC_RUWACH, //90 + SC_EXTREMITYFIST, + SC_EXPLOSIONSPIRITS, + SC_COMBO, + SC_BLADESTOP_WAIT, + SC_BLADESTOP, + SC_FIREWEAPON, + SC_WATERWEAPON, + SC_WINDWEAPON, + SC_EARTHWEAPON, + SC_VOLCANO, //100, + SC_DELUGE, + SC_VIOLENTGALE, + SC_WATK_ELEMENT, + SC_ARMOR, + SC_ARMOR_ELEMENT, + SC_NOCHAT, + SC_BABY, + SC_AURABLADE, + SC_PARRYING, + SC_CONCENTRATION, //110 + SC_TENSIONRELAX, + SC_BERSERK, + SC_FURY, + SC_GOSPEL, + SC_ASSUMPTIO, + SC_BASILICA, + SC_GUILDAURA, + SC_MAGICPOWER, + SC_EDP, + SC_TRUESIGHT, //120 + SC_WINDWALK, + SC_MELTDOWN, + SC_CARTBOOST, + SC_CHASEWALK, + SC_REJECTSWORD, + SC_MARIONETTE, + SC_MARIONETTE2, + SC_CHANGEUNDEAD, + SC_JOINTBEAT, + SC_MINDBREAKER, //130 + SC_MEMORIZE, + SC_FOGWALL, + SC_SPIDERWEB, + SC_DEVOTION, + SC_SACRIFICE, + SC_STEELBODY, + SC_ORCISH, + SC_READYSTORM, + SC_READYDOWN, + SC_READYTURN, //140 + SC_READYCOUNTER, + SC_DODGE, + SC_RUN, + SC_SHADOWWEAPON, + SC_ADRENALINE2, + SC_GHOSTWEAPON, + SC_KAIZEL, + SC_KAAHI, + SC_KAUPE, + SC_ONEHAND, //150 + SC_PRESERVE, + SC_BATTLEORDERS, + SC_REGENERATION, + SC_DOUBLECAST, + SC_GRAVITATION, + SC_MAXOVERTHRUST, + SC_LONGING, + SC_HERMODE, + SC_SHRINK, + SC_SIGHTBLASTER, //160 + SC_WINKCHARM, + SC_CLOSECONFINE, + SC_CLOSECONFINE2, + SC_DANCING, + SC_ELEMENTALCHANGE, + SC_RICHMANKIM, + SC_ETERNALCHAOS, + SC_DRUMBATTLE, + SC_NIBELUNGEN, + SC_ROKISWEIL, //170 + SC_INTOABYSS, + SC_SIEGFRIED, + SC_WHISTLE, + SC_ASSNCROS, + SC_POEMBRAGI, + SC_APPLEIDUN, + SC_MODECHANGE, + SC_HUMMING, + SC_DONTFORGETME, + SC_FORTUNE, //180 + SC_SERVICE4U, + SC_STOP, //Prevents inflicted chars from walking. [Skotlex] + SC_SPURT, + SC_SPIRIT, + SC_COMA, //Not a real SC_, it makes a char's HP/SP hit 1. + SC_INTRAVISION, + SC_INCALLSTATUS, + SC_INCSTR, + SC_INCAGI, + SC_INCVIT, //190 + SC_INCINT, + SC_INCDEX, + SC_INCLUK, + SC_INCHIT, + SC_INCHITRATE, + SC_INCFLEE, + SC_INCFLEERATE, + SC_INCMHPRATE, + SC_INCMSPRATE, + SC_INCATKRATE, //200 + SC_INCMATKRATE, + SC_INCDEFRATE, + SC_STRFOOD, + SC_AGIFOOD, + SC_VITFOOD, + SC_INTFOOD, + SC_DEXFOOD, + SC_LUKFOOD, + SC_HITFOOD, + SC_FLEEFOOD, //210 + SC_BATKFOOD, + SC_WATKFOOD, + SC_MATKFOOD, + SC_SCRESIST, //Increases resistance to status changes. + SC_XMAS, // Xmas Suit [Valaris] + SC_WARM, //SG skills [Komurka] + SC_SUN_COMFORT, + SC_MOON_COMFORT, + SC_STAR_COMFORT, + SC_FUSION, //220 + SC_SKILLRATE_UP, + SC_SKE, + SC_KAITE, + SC_SWOO, // [marquis007] + SC_SKA, // [marquis007] + SC_EARTHSCROLL, + SC_MIRACLE, //SG 'hidden' skill [Komurka] + SC_MADNESSCANCEL, + SC_ADJUSTMENT, + SC_INCREASING, //230 + SC_GATLINGFEVER, + SC_TATAMIGAESHI, + SC_UTSUSEMI, + SC_BUNSINJYUTSU, + SC_KAENSIN, + SC_SUITON, + SC_NEN, + SC_KNOWLEDGE, + SC_SMA, + SC_FLING, //240 + SC_AVOID, + SC_CHANGE, + SC_BLOODLUST, + SC_FLEET, + SC_SPEED, + SC_DEFENCE, + SC_INCASPDRATE, + SC_INCFLEE2 = 248, + SC_JAILED, + SC_ENCHANTARMS, //250 + SC_MAGICALATTACK, + SC_ARMORCHANGE, + SC_CRITICALWOUND, + SC_MAGICMIRROR, + SC_SLOWCAST, + SC_SUMMER, + SC_EXPBOOST, + SC_ITEMBOOST, + SC_BOSSMAPINFO, + SC_LIFEINSURANCE, //260 + SC_INCCRI, + //SC_INCDEF, + //SC_INCBASEATK = 263, + //SC_FASTCAST, + SC_MDEF_RATE = 265, + //SC_HPREGEN, + SC_INCHEALRATE = 267, + SC_PNEUMA, + SC_AUTOTRADE, + SC_KSPROTECTED, //270 + SC_ARMOR_RESIST = 271, + SC_SPCOST_RATE, + SC_COMMONSC_RESIST, + SC_SEVENWIND, + SC_DEF_RATE, + //SC_SPREGEN, + SC_WALKSPEED = 277, + + // Mercenary Only Bonus Effects + SC_MERC_FLEEUP, + SC_MERC_ATKUP, + SC_MERC_HPUP, //280 + SC_MERC_SPUP, + SC_MERC_HITUP, + SC_MERC_QUICKEN, + + SC_REBIRTH, + //SC_SKILLCASTRATE, //285 + //SC_DEFRATIOATK, + //SC_HPDRAIN, + //SC_SKILLATKBONUS, + SC_ITEMSCRIPT = 289, + SC_S_LIFEPOTION, //290 + SC_L_LIFEPOTION, + SC_JEXPBOOST, + //SC_IGNOREDEF, + SC_HELLPOWER = 294, + SC_INVINCIBLE, //295 + SC_INVINCIBLEOFF, + SC_MANU_ATK, + SC_MANU_DEF, + SC_SPL_ATK, + SC_SPL_DEF, //300 + SC_MANU_MATK, + SC_SPL_MATK, + SC_FOOD_STR_CASH, + SC_FOOD_AGI_CASH, + SC_FOOD_VIT_CASH, + SC_FOOD_DEX_CASH, + SC_FOOD_INT_CASH, + SC_FOOD_LUK_CASH,//308 + /** + * 3rd + **/ + SC_FEAR,//309 + SC_BURNING,//310 + SC_FREEZING,//311 + /** + * Rune Knight + **/ + SC_ENCHANTBLADE,//312 + SC_DEATHBOUND,//313 + SC_MILLENNIUMSHIELD, + SC_CRUSHSTRIKE,//315 + SC_REFRESH, + SC_REUSE_REFRESH, + SC_GIANTGROWTH, + SC_STONEHARDSKIN, + SC_VITALITYACTIVATION,//320 + SC_STORMBLAST, + SC_FIGHTINGSPIRIT, + SC_ABUNDANCE, + /** + * Arch Bishop + **/ + SC_ADORAMUS, + SC_EPICLESIS,//325 + SC_ORATIO, + SC_LAUDAAGNUS, + SC_LAUDARAMUS, + SC_RENOVATIO, + SC_EXPIATIO,//330 + SC_DUPLELIGHT, + SC_SECRAMENT, + /** + * Warlock + **/ + SC_WHITEIMPRISON, + SC_MARSHOFABYSS, + SC_RECOGNIZEDSPELL,//335 + SC_STASIS, + SC_SPHERE_1, + SC_SPHERE_2, + SC_SPHERE_3, + SC_SPHERE_4,//340 + SC_SPHERE_5, + SC_READING_SB, + SC_FREEZINGSPELL, + /** + * Ranger + **/ + SC_FEARBREEZE, + SC_ELECTRICSHOCKER,//345 + SC_WUGDASH, + SC_BITE, + SC_CAMOUFLAGE, + /** + * Mechanic + **/ + SC_ACCELERATION, + SC_HOVERING,//350 + SC_SHAPESHIFT, + SC_INFRAREDSCAN, + SC_ANALYZE, + SC_MAGNETICFIELD, + SC_NEUTRALBARRIER,//355 + SC_NEUTRALBARRIER_MASTER, + SC_STEALTHFIELD, + SC_STEALTHFIELD_MASTER, + SC_OVERHEAT, + SC_OVERHEAT_LIMITPOINT,//360 + /** + * Guillotine Cross + **/ + SC_VENOMIMPRESS, + SC_POISONINGWEAPON, + SC_WEAPONBLOCKING, + SC_CLOAKINGEXCEED, + SC_HALLUCINATIONWALK,//365 + SC_HALLUCINATIONWALK_POSTDELAY, + SC_ROLLINGCUTTER, + SC_TOXIN, + SC_PARALYSE, + SC_VENOMBLEED,//370 + SC_MAGICMUSHROOM, + SC_DEATHHURT, + SC_PYREXIA, + SC_OBLIVIONCURSE, + SC_LEECHESEND,//375 + /** + * Royal Guard + **/ + SC_REFLECTDAMAGE, + SC_FORCEOFVANGUARD, + SC_SHIELDSPELL_DEF, + SC_SHIELDSPELL_MDEF, + SC_SHIELDSPELL_REF,//380 + SC_EXEEDBREAK, + SC_PRESTIGE, + SC_BANDING, + SC_BANDING_DEFENCE, + SC_EARTHDRIVE,//385 + SC_INSPIRATION, + /** + * Sorcerer + **/ + SC_SPELLFIST, + SC_CRYSTALIZE, + SC_STRIKING, + SC_WARMER,//390 + SC_VACUUM_EXTREME, + SC_PROPERTYWALK, + /** + * Minstrel / Wanderer + **/ + SC_SWINGDANCE, + SC_SYMPHONYOFLOVER, + SC_MOONLITSERENADE,//395 + SC_RUSHWINDMILL, + SC_ECHOSONG, + SC_HARMONIZE, + SC_VOICEOFSIREN, + SC_DEEPSLEEP,//400 + SC_SIRCLEOFNATURE, + SC_GLOOMYDAY, + SC_GLOOMYDAY_SK, + SC_SONGOFMANA, + SC_DANCEWITHWUG,//405 + SC_SATURDAYNIGHTFEVER, + SC_LERADSDEW, + SC_MELODYOFSINK, + SC_BEYONDOFWARCRY, + SC_UNLIMITEDHUMMINGVOICE,//410 + SC_SITDOWN_FORCE, + SC_NETHERWORLD, + /** + * Sura + **/ + SC_CRESCENTELBOW, + SC_CURSEDCIRCLE_ATKER, + SC_CURSEDCIRCLE_TARGET, + SC_LIGHTNINGWALK,//416 + SC_RAISINGDRAGON, + SC_GT_ENERGYGAIN, + SC_GT_CHANGE, + SC_GT_REVITALIZE, + /** + * Genetic + **/ + SC_GN_CARTBOOST,//427 + SC_THORNSTRAP, + SC_BLOODSUCKER, + SC_SMOKEPOWDER, + SC_TEARGAS, + SC_MANDRAGORA,//426 + SC_STOMACHACHE, + SC_MYSTERIOUS_POWDER, + SC_MELON_BOMB, + SC_BANANA_BOMB, + SC_BANANA_BOMB_SITDOWN,//431 + SC_SAVAGE_STEAK, + SC_COCKTAIL_WARG_BLOOD, + SC_MINOR_BBQ, + SC_SIROMA_ICE_TEA, + SC_DROCERA_HERB_STEAMED,//436 + SC_PUTTI_TAILS_NOODLES, + SC_BOOST500, + SC_FULL_SWING_K, + SC_MANA_PLUS, + SC_MUSTLE_M,//441 + SC_LIFE_FORCE_F, + SC_EXTRACT_WHITE_POTION_Z, + SC_VITATA_500, + SC_EXTRACT_SALAMINE_JUICE, + /** + * Shadow Chaser + **/ + SC__REPRODUCE,//446 + SC__AUTOSHADOWSPELL, + SC__SHADOWFORM, + SC__BODYPAINT, + SC__INVISIBILITY, + SC__DEADLYINFECT,//451 + SC__ENERVATION, + SC__GROOMY, + SC__IGNORANCE, + SC__LAZINESS, + SC__UNLUCKY,//456 + SC__WEAKNESS, + SC__STRIPACCESSORY, + SC__MANHOLE, + SC__BLOODYLUST,//460 + /** + * Elemental Spirits + **/ + SC_CIRCLE_OF_FIRE, + SC_CIRCLE_OF_FIRE_OPTION, + SC_FIRE_CLOAK, + SC_FIRE_CLOAK_OPTION, + SC_WATER_SCREEN,//465 + SC_WATER_SCREEN_OPTION, + SC_WATER_DROP, + SC_WATER_DROP_OPTION, + SC_WATER_BARRIER, + SC_WIND_STEP,//470 + SC_WIND_STEP_OPTION, + SC_WIND_CURTAIN, + SC_WIND_CURTAIN_OPTION, + SC_ZEPHYR, + SC_SOLID_SKIN,//475 + SC_SOLID_SKIN_OPTION, + SC_STONE_SHIELD, + SC_STONE_SHIELD_OPTION, + SC_POWER_OF_GAIA, + SC_PYROTECHNIC,//480 + SC_PYROTECHNIC_OPTION, + SC_HEATER, + SC_HEATER_OPTION, + SC_TROPIC, + SC_TROPIC_OPTION,//485 + SC_AQUAPLAY, + SC_AQUAPLAY_OPTION, + SC_COOLER, + SC_COOLER_OPTION, + SC_CHILLY_AIR,//490 + SC_CHILLY_AIR_OPTION, + SC_GUST, + SC_GUST_OPTION, + SC_BLAST, + SC_BLAST_OPTION,//495 + SC_WILD_STORM, + SC_WILD_STORM_OPTION, + SC_PETROLOGY, + SC_PETROLOGY_OPTION, + SC_CURSED_SOIL,//500 + SC_CURSED_SOIL_OPTION, + SC_UPHEAVAL, + SC_UPHEAVAL_OPTION, + SC_TIDAL_WEAPON, + SC_TIDAL_WEAPON_OPTION,//505 + SC_ROCK_CRUSHER, + SC_ROCK_CRUSHER_ATK, + /* Guild Aura */ + SC_LEADERSHIP, + SC_GLORYWOUNDS, + SC_SOULCOLD, //508 + SC_HAWKEYES, + /* ... */ + SC_ODINS_POWER, + SC_RAID, + /* Sorcerer .extra */ + SC_FIRE_INSIGNIA, + SC_WATER_INSIGNIA, + SC_WIND_INSIGNIA, //516 + SC_EARTH_INSIGNIA, + /* new pushcart */ + SC_PUSH_CART, + /* Warlock Spell books */ + SC_SPELLBOOK1, + SC_SPELLBOOK2, + SC_SPELLBOOK3, + SC_SPELLBOOK4, + SC_SPELLBOOK5, + SC_SPELLBOOK6, +/** + * In official server there are only 7 maximum number of spell books that can be memorized + * To increase the maximum value just add another status type before SC_MAXSPELLBOOK (ex. SC_SPELLBOOK7, SC_SPELLBOOK8 and so on) + **/ + SC_MAXSPELLBOOK, + /* Max HP & SP */ + SC_INCMHP, + SC_INCMSP, + SC_PARTYFLEE, // 531 + /** + * Kagerou & Oboro [malufett] + **/ + SC_MEIKYOUSISUI, + SC_JYUMONJIKIRI, + SC_KYOUGAKU, + SC_IZAYOI, + SC_ZENKAI, + SC_KAGEHUMI, + SC_KYOMU, + SC_KAGEMUSYA, + SC_ZANGETSU, + SC_GENSOU, + SC_AKAITSUKI, + + //homon S + SC_STYLE_CHANGE, + SC_GOLDENE_FERSE, + SC_ANGRIFFS_MODUS, + SC_ERASER_CUTTER, + SC_OVERED_BOOST, + SC_LIGHT_OF_REGENE, + SC_ASH, + SC_GRANITIC_ARMOR, + SC_MAGMA_FLOW, + SC_PYROCLASTIC, + SC_PARALYSIS, + SC_PAIN_KILLER, + + +#ifdef RENEWAL + SC_EXTREMITYFIST2, +#endif + + SC_MAX, //Automatically updated max, used in for's to check we are within bounds. +} sc_type; + +// Official status change ids, used to display status icons on the client. +enum si_type { + SI_BLANK = -1, + SI_PROVOKE = 0, + SI_ENDURE = 1, + SI_TWOHANDQUICKEN = 2, + SI_CONCENTRATE = 3, + SI_HIDING = 4, + SI_CLOAKING = 5, + SI_ENCPOISON = 6, + SI_POISONREACT = 7, + SI_QUAGMIRE = 8, + SI_ANGELUS = 9, + SI_BLESSING = 10, + SI_SIGNUMCRUCIS = 11, + SI_INCREASEAGI = 12, + SI_DECREASEAGI = 13, + SI_SLOWPOISON = 14, + SI_IMPOSITIO = 15, + SI_SUFFRAGIUM = 16, + SI_ASPERSIO = 17, + SI_BENEDICTIO = 18, + SI_KYRIE = 19, + SI_MAGNIFICAT = 20, + SI_GLORIA = 21, + SI_AETERNA = 22, + SI_ADRENALINE = 23, + SI_WEAPONPERFECTION = 24, + SI_OVERTHRUST = 25, + SI_MAXIMIZEPOWER = 26, + SI_RIDING = 27, + SI_FALCON = 28, + SI_TRICKDEAD = 29, + SI_LOUD = 30, + SI_ENERGYCOAT = 31, + SI_BROKENARMOR = 32, + SI_BROKENWEAPON = 33, + SI_HALLUCINATION = 34, + SI_WEIGHT50 = 35, + SI_WEIGHT90 = 36, + SI_ASPDPOTION0 = 37, + SI_ASPDPOTION1 = 38, + SI_ASPDPOTION2 = 39, + SI_ASPDPOTIONINFINITY = 40, + SI_SPEEDPOTION1 = 41, +// SI_MOVHASTE_INFINITY = 42, +// SI_AUTOCOUNTER = 43, +// SI_SPLASHER = 44, +// SI_ANKLESNARE = 45, + SI_ACTIONDELAY = 46, +// SI_NOACTION = 47, +// SI_IMPOSSIBLEPICKUP = 48, +// SI_BARRIER = 49, + SI_STRIPWEAPON = 50, + SI_STRIPSHIELD = 51, + SI_STRIPARMOR = 52, + SI_STRIPHELM = 53, + SI_CP_WEAPON = 54, + SI_CP_SHIELD = 55, + SI_CP_ARMOR = 56, + SI_CP_HELM = 57, + SI_AUTOGUARD = 58, + SI_REFLECTSHIELD = 59, +// SI_DEVOTION = 60, + SI_PROVIDENCE = 61, + SI_DEFENDER = 62, +// SI_MAGICROD = 63, +// SI_WEAPONPROPERTY = 64, + SI_AUTOSPELL = 65, +// SI_SPECIALZONE = 66, +// SI_MASK = 67, + SI_SPEARQUICKEN = 68, +// SI_BDPLAYING = 69, +// SI_WHISTLE = 70, +// SI_ASSASSINCROSS = 71, +// SI_POEMBRAGI = 72, +// SI_APPLEIDUN = 73, +// SI_HUMMING = 74, +// SI_DONTFORGETME = 75, +// SI_FORTUNEKISS = 76, +// SI_SERVICEFORYOU = 77, +// SI_RICHMANKIM = 78, +// SI_ETERNALCHAOS = 79, +// SI_DRUMBATTLEFIELD = 80, +// SI_RINGNIBELUNGEN = 81, +// SI_ROKISWEIL = 82, +// SI_INTOABYSS = 83, +// SI_SIEGFRIED = 84, +// SI_BLADESTOP = 85, + SI_EXPLOSIONSPIRITS = 86, + SI_STEELBODY = 87, + SI_EXTREMITYFIST = 88, +// SI_COMBOATTACK = 89, + SI_FIREWEAPON = 90, + SI_WATERWEAPON = 91, + SI_WINDWEAPON = 92, + SI_EARTHWEAPON = 93, +// SI_MAGICATTACK = 94, + SI_STOP = 95, +// SI_WEAPONBRAKER = 96, + SI_UNDEAD = 97, +// SI_POWERUP = 98, +// SI_AGIUP = 99, +// SI_SIEGEMODE = 100, +// SI_INVISIBLE = 101, +// SI_STATUSONE = 102, + SI_AURABLADE = 103, + SI_PARRYING = 104, + SI_CONCENTRATION = 105, + SI_TENSIONRELAX = 106, + SI_BERSERK = 107, +// SI_SACRIFICE = 108, +// SI_GOSPEL = 109, + SI_ASSUMPTIO = 110, +// SI_BASILICA = 111, + SI_LANDENDOW = 112, + SI_MAGICPOWER = 113, + SI_EDP = 114, + SI_TRUESIGHT = 115, + SI_WINDWALK = 116, + SI_MELTDOWN = 117, + SI_CARTBOOST = 118, +// SI_CHASEWALK = 119, + SI_REJECTSWORD = 120, + SI_MARIONETTE = 121, + SI_MARIONETTE2 = 122, + SI_MOONLIT = 123, + SI_BLEEDING = 124, + SI_JOINTBEAT = 125, +// SI_MINDBREAKER = 126, +// SI_MEMORIZE = 127, +// SI_FOGWALL = 128, +// SI_SPIDERWEB = 129, + SI_BABY = 130, +// SI_SUB_WEAPONPROPERTY = 131, + SI_AUTOBERSERK = 132, + SI_RUN = 133, + SI_BUMP = 134, + SI_READYSTORM = 135, +// SI_STORMKICK_READY = 136, + SI_READYDOWN = 137, +// SI_DOWNKICK_READY = 138, + SI_READYTURN = 139, +// SI_TURNKICK_READY = 140, + SI_READYCOUNTER = 141, +// SI_COUNTER_READY = 142, + SI_DODGE = 143, +// SI_DODGE_READY = 144, + SI_SPURT = 145, + SI_SHADOWWEAPON = 146, + SI_ADRENALINE2 = 147, + SI_GHOSTWEAPON = 148, + SI_SPIRIT = 149, + SI_PLUSATTACKPOWER = 150, + SI_PLUSMAGICPOWER = 151, + SI_DEVIL = 152, + SI_KAITE = 153, +// SI_SWOO = 154, +// SI_STAR2 = 155, + SI_KAIZEL = 156, + SI_KAAHI = 157, + SI_KAUPE = 158, + SI_SMA = 159, + SI_NIGHT = 160, + SI_ONEHAND = 161, +// SI_FRIEND = 162, +// SI_FRIENDUP = 163, +// SI_SG_WARM = 164, + SI_WARM = 165, +// 166 | The three show the exact same display: ultra red character (165, 166, 167) +// 167 | Their names would be SI_SG_SUN_WARM, SI_SG_MOON_WARM, SI_SG_STAR_WARM +// SI_EMOTION = 168, + SI_SUN_COMFORT = 169, + SI_MOON_COMFORT = 170, + SI_STAR_COMFORT = 171, +// SI_EXPUP = 172, +// SI_GDSKILL_BATTLEORDER = 173, +// SI_GDSKILL_REGENERATION = 174, +// SI_GDSKILL_POSTDELAY = 175, +// SI_RESISTHANDICAP = 176, +// SI_MAXHPPERCENT = 177, +// SI_MAXSPPERCENT = 178, +// SI_DEFENCE = 179, +// SI_SLOWDOWN = 180, + SI_PRESERVE = 181, + SI_INCSTR = 182, +// SI_NOT_EXTREMITYFIST = 183, + SI_INTRAVISION = 184, +// SI_MOVESLOW_POTION = 185, + SI_DOUBLECAST = 186, +// SI_GRAVITATION = 187, + SI_MAXOVERTHRUST = 188, +// SI_LONGING = 189, +// SI_HERMODE = 190, + SI_TAROT = 191, // the icon allows no doubt... but what is it really used for ?? [DracoRPG] +// SI_HLIF_AVOID = 192, +// SI_HFLI_FLEET = 193, +// SI_HFLI_SPEED = 194, +// SI_HLIF_CHANGE = 195, +// SI_HAMI_BLOODLUST = 196, + SI_SHRINK = 197, + SI_SIGHTBLASTER = 198, + SI_WINKCHARM = 199, + SI_CLOSECONFINE = 200, + SI_CLOSECONFINE2 = 201, +// SI_DISABLEMOVE = 202, + SI_MADNESSCANCEL = 203, //[blackhole89] + SI_GATLINGFEVER = 204, + SI_EARTHSCROLL = 205, + SI_UTSUSEMI = 206, + SI_BUNSINJYUTSU = 207, + SI_NEN = 208, + SI_ADJUSTMENT = 209, + SI_ACCURACY = 210, +// SI_NJ_SUITON = 211, +// SI_PET = 212, +// SI_MENTAL = 213, +// SI_EXPMEMORY = 214, +// SI_PERFORMANCE = 215, +// SI_GAIN = 216, +// SI_GRIFFON = 217, +// SI_DRIFT = 218, +// SI_WALLSHIFT = 219, +// SI_REINCARNATION = 220, +// SI_PATTACK = 221, +// SI_PSPEED = 222, +// SI_PDEFENSE = 223, +// SI_PCRITICAL = 224, +// SI_RANKING = 225, +// SI_PTRIPLE = 226, +// SI_DENERGY = 227, +// SI_WAVE1 = 228, +// SI_WAVE2 = 229, +// SI_WAVE3 = 230, +// SI_WAVE4 = 231, +// SI_DAURA = 232, +// SI_DFREEZER = 233, +// SI_DPUNISH = 234, +// SI_DBARRIER = 235, +// SI_DWARNING = 236, +// SI_MOUSEWHEEL = 237, +// SI_DGAUGE = 238, +// SI_DACCEL = 239, +// SI_DBLOCK = 240, + SI_FOODSTR = 241, + SI_FOODAGI = 242, + SI_FOODVIT = 243, + SI_FOODDEX = 244, + SI_FOODINT = 245, + SI_FOODLUK = 246, + SI_FOODFLEE = 247, + SI_FOODHIT = 248, + SI_FOODCRI = 249, + SI_EXPBOOST = 250, + SI_LIFEINSURANCE = 251, + SI_ITEMBOOST = 252, + SI_BOSSMAPINFO = 253, +// SI_DA_ENERGY = 254, +// SI_DA_FIRSTSLOT = 255, +// SI_DA_HEADDEF = 256, +// SI_DA_SPACE = 257, +// SI_DA_TRANSFORM = 258, +// SI_DA_ITEMREBUILD = 259, +// SI_DA_ILLUSION = 260, //All mobs display as Turtle General +// SI_DA_DARKPOWER = 261, +// SI_DA_EARPLUG = 262, +// SI_DA_CONTRACT = 263, //Bio Mob effect on you and SI_TRICKDEAD icon +// SI_DA_BLACK = 264, //For short time blurry screen +// SI_DA_MAGICCART = 265, +// SI_CRYSTAL = 266, +// SI_DA_REBUILD = 267, +// SI_DA_EDARKNESS = 268, +// SI_DA_EGUARDIAN = 269, +// SI_DA_TIMEOUT = 270, + SI_FOOD_STR_CASH = 271, + SI_FOOD_AGI_CASH = 272, + SI_FOOD_VIT_CASH = 273, + SI_FOOD_DEX_CASH = 274, + SI_FOOD_INT_CASH = 275, + SI_FOOD_LUK_CASH = 276, + SI_MERC_FLEEUP = 277, + SI_MERC_ATKUP = 278, + SI_MERC_HPUP = 279, + SI_MERC_SPUP = 280, + SI_MERC_HITUP = 281, + SI_SLOWCAST = 282, +// SI_MAGICMIRROR = 283, +// SI_STONESKIN = 284, +// SI_ANTIMAGIC = 285, + SI_CRITICALWOUND = 286, +// SI_NPC_DEFENDER = 287, +// SI_NOACTION_WAIT = 288, + SI_MOVHASTE_HORSE = 289, + SI_DEF_RATE = 290, + SI_MDEF_RATE = 291, + SI_INCHEALRATE = 292, + SI_S_LIFEPOTION = 293, + SI_L_LIFEPOTION = 294, + SI_INCCRI = 295, + SI_PLUSAVOIDVALUE = 296, +// SI_ATKER_ASPD = 297, +// SI_TARGET_ASPD = 298, +// SI_ATKER_MOVESPEED = 299, + SI_ATKER_BLOOD = 300, + SI_TARGET_BLOOD = 301, + SI_ARMOR_PROPERTY = 302, +// SI_REUSE_LIMIT_A = 303, + SI_HELLPOWER = 304, +// SI_STEAMPACK = 305, +// SI_REUSE_LIMIT_B = 306, +// SI_REUSE_LIMIT_C = 307, +// SI_REUSE_LIMIT_D = 308, +// SI_REUSE_LIMIT_E = 309, +// SI_REUSE_LIMIT_F = 310, + SI_INVINCIBLE = 311, + SI_CASH_PLUSONLYJOBEXP = 312, + SI_PARTYFLEE = 313, +// SI_ANGEL_PROTECT = 314, + SI_ENDURE_MDEF = 315, + SI_ENCHANTBLADE = 316, + SI_DEATHBOUND = 317, + SI_REFRESH = 318, + SI_GIANTGROWTH = 319, + SI_STONEHARDSKIN = 320, + SI_VITALITYACTIVATION = 321, + SI_FIGHTINGSPIRIT = 322, + SI_ABUNDANCE = 323, + SI_REUSE_MILLENNIUMSHIELD = 324, + SI_REUSE_CRUSHSTRIKE = 325, + SI_REUSE_REFRESH = 326, + SI_REUSE_STORMBLAST = 327, + SI_VENOMIMPRESS = 328, + SI_EPICLESIS = 329, + SI_ORATIO = 330, + SI_LAUDAAGNUS = 331, + SI_LAUDARAMUS = 332, + SI_CLOAKINGEXCEED = 333, + SI_HALLUCINATIONWALK = 334, + SI_HALLUCINATIONWALK_POSTDELAY = 335, + SI_RENOVATIO = 336, + SI_WEAPONBLOCKING = 337, + SI_WEAPONBLOCKING_POSTDELAY = 338, + SI_ROLLINGCUTTER = 339, + SI_EXPIATIO = 340, + SI_POISONINGWEAPON = 341, + SI_TOXIN = 342, + SI_PARALYSE = 343, + SI_VENOMBLEED = 344, + SI_MAGICMUSHROOM = 345, + SI_DEATHHURT = 346, + SI_PYREXIA = 347, + SI_OBLIVIONCURSE = 348, + SI_LEECHESEND = 349, + SI_DUPLELIGHT = 350, + SI_FROSTMISTY = 351, + SI_FEARBREEZE = 352, + SI_ELECTRICSHOCKER = 353, + SI_MARSHOFABYSS = 354, + SI_RECOGNIZEDSPELL = 355, + SI_STASIS = 356, + SI_WUGRIDER = 357, + SI_WUGDASH = 358, + SI_WUGBITE = 359, + SI_CAMOUFLAGE = 360, + SI_ACCELERATION = 361, + SI_HOVERING = 362, + SI_SPHERE_1 = 363, + SI_SPHERE_2 = 364, + SI_SPHERE_3 = 365, + SI_SPHERE_4 = 366, + SI_SPHERE_5 = 367, + SI_MVPCARD_TAOGUNKA = 368, + SI_MVPCARD_MISTRESS = 369, + SI_MVPCARD_ORCHERO = 370, + SI_MVPCARD_ORCLORD = 371, + SI_OVERHEAT_LIMITPOINT = 372, + SI_OVERHEAT = 373, + SI_SHAPESHIFT = 374, + SI_INFRAREDSCAN = 375, + SI_MAGNETICFIELD = 376, + SI_NEUTRALBARRIER = 377, + SI_NEUTRALBARRIER_MASTER = 378, + SI_STEALTHFIELD = 379, + SI_STEALTHFIELD_MASTER = 380, + SI_MANU_ATK = 381, + SI_MANU_DEF = 382, + SI_SPL_ATK = 383, + SI_SPL_DEF = 384, + SI_REPRODUCE = 385, + SI_MANU_MATK = 386, + SI_SPL_MATK = 387, + SI_STR_SCROLL = 388, + SI_INT_SCROLL = 389, + SI_LG_REFLECTDAMAGE = 390, + SI_FORCEOFVANGUARD = 391, + SI_BUCHEDENOEL = 392, + SI_AUTOSHADOWSPELL = 393, + SI_SHADOWFORM = 394, + SI_RAID = 395, + SI_SHIELDSPELL_DEF = 396, + SI_SHIELDSPELL_MDEF = 397, + SI_SHIELDSPELL_REF = 398, + SI_BODYPAINT = 399, + SI_EXEEDBREAK = 400, + SI_ADORAMUS = 401, + SI_PRESTIGE = 402, + SI_INVISIBILITY = 403, + SI_DEADLYINFECT = 404, + SI_BANDING = 405, + SI_EARTHDRIVE = 406, + SI_INSPIRATION = 407, + SI_ENERVATION = 408, + SI_GROOMY = 409, + SI_RAISINGDRAGON = 410, + SI_IGNORANCE = 411, + SI_LAZINESS = 412, + SI_LIGHTNINGWALK = 413, + SI_ACARAJE = 414, + SI_UNLUCKY = 415, + SI_CURSEDCIRCLE_ATKER = 416, + SI_CURSEDCIRCLE_TARGET = 417, + SI_WEAKNESS = 418, + SI_CRESCENTELBOW = 419, + SI_NOEQUIPACCESSARY = 420, + SI_STRIPACCESSARY = 421, + SI_MANHOLE = 422, + SI_POPECOOKIE = 423, + SI_FALLENEMPIRE = 424, + SI_GENTLETOUCH_ENERGYGAIN = 425, + SI_GENTLETOUCH_CHANGE = 426, + SI_GENTLETOUCH_REVITALIZE = 427, + SI_BLOODYLUST = 428, + SI_SWINGDANCE = 429, + SI_SYMPHONYOFLOVERS = 430, + SI_PROPERTYWALK = 431, + SI_SPELLFIST = 432, + SI_NETHERWORLD = 433, + SI_VOICEOFSIREN = 434, + SI_DEEPSLEEP = 435, + SI_SIRCLEOFNATURE = 436, + SI_COLD = 437, + SI_GLOOMYDAY = 438, + SI_SONGOFMANA = 439, + SI_CLOUDKILL = 440, + SI_DANCEWITHWUG = 441, + SI_RUSHWINDMILL = 442, + SI_ECHOSONG = 443, + SI_HARMONIZE = 444, + SI_STRIKING = 445, + SI_WARMER = 446, + SI_MOONLITSERENADE = 447, + SI_SATURDAYNIGHTFEVER = 448, + SI_SITDOWN_FORCE = 449, + SI_ANALYZE = 450, + SI_LERADSDEW = 451, + SI_MELODYOFSINK = 452, + SI_WARCRYOFBEYOND = 453, + SI_UNLIMITEDHUMMINGVOICE = 454, + SI_SPELLBOOK1 = 455, + SI_SPELLBOOK2 = 456, + SI_SPELLBOOK3 = 457, + SI_FREEZE_SP = 458, + SI_GN_TRAINING_SWORD = 459, + SI_GN_REMODELING_CART = 460, + SI_CARTSBOOST = 461, + SI_FIXEDCASTINGTM_REDUCE = 462, + SI_THORNTRAP = 463, + SI_BLOODSUCKER = 464, + SI_SPORE_EXPLOSION = 465, + SI_DEMONIC_FIRE = 466, + SI_FIRE_EXPANSION_SMOKE_POWDER = 467, + SI_FIRE_EXPANSION_TEAR_GAS = 468, + SI_BLOCKING_PLAY = 469, + SI_MANDRAGORA = 470, + SI_ACTIVATE = 471, + SI_SECRAMENT = 472, + SI_ASSUMPTIO2 = 473, + SI_TK_SEVENWIND = 474, + SI_LIMIT_ODINS_RECALL = 475, + SI_STOMACHACHE = 476, + SI_MYSTERIOUS_POWDER = 477, + SI_MELON_BOMB = 478, + SI_BANANA_BOMB_SITDOWN_POSTDELAY = 479, + SI_PROMOTE_HEALTH_RESERCH = 480, + SI_ENERGY_DRINK_RESERCH = 481, + SI_EXTRACT_WHITE_POTION_Z = 482, + SI_VITATA_500 = 483, + SI_EXTRACT_SALAMINE_JUICE = 484, + SI_BOOST500 = 485, + SI_FULL_SWING_K = 486, + SI_MANA_PLUS = 487, + SI_MUSTLE_M = 488, + SI_LIFE_FORCE_F = 489, + SI_VACUUM_EXTREME = 490, + SI_SAVAGE_STEAK = 491, + SI_COCKTAIL_WARG_BLOOD = 492, + SI_MINOR_BBQ = 493, + SI_SIROMA_ICE_TEA = 494, + SI_DROCERA_HERB_STEAMED = 495, + SI_PUTTI_TAILS_NOODLES = 496, + SI_BANANA_BOMB = 497, + SI_SUMMON_AGNI = 498, + SI_SPELLBOOK4 = 499, + SI_SPELLBOOK5 = 500, + SI_SPELLBOOK6 = 501, + SI_SPELLBOOK7 = 502, + SI_ELEMENTAL_AGGRESSIVE = 503, + SI_RETURN_TO_ELDICASTES = 504, + SI_BANDING_DEFENCE = 505, + SI_SKELSCROLL = 506, + SI_DISTRUCTIONSCROLL = 507, + SI_ROYALSCROLL = 508, + SI_IMMUNITYSCROLL = 509, + SI_MYSTICSCROLL = 510, + SI_BATTLESCROLL = 511, + SI_ARMORSCROLL = 512, + SI_FREYJASCROLL = 513, + SI_SOULSCROLL = 514, + SI_CIRCLE_OF_FIRE = 515, + SI_CIRCLE_OF_FIRE_OPTION = 516, + SI_FIRE_CLOAK = 517, + SI_FIRE_CLOAK_OPTION = 518, + SI_WATER_SCREEN = 519, + SI_WATER_SCREEN_OPTION = 520, + SI_WATER_DROP = 521, + SI_WATER_DROP_OPTION = 522, + SI_WIND_STEP = 523, + SI_WIND_STEP_OPTION = 524, + SI_WIND_CURTAIN = 525, + SI_WIND_CURTAIN_OPTION = 526, + SI_WATER_BARRIER = 527, + SI_ZEPHYR = 528, + SI_SOLID_SKIN = 529, + SI_SOLID_SKIN_OPTION = 530, + SI_STONE_SHIELD = 531, + SI_STONE_SHIELD_OPTION = 532, + SI_POWER_OF_GAIA = 533, + // SI_EL_WAIT = 534, + // SI_EL_PASSIVE = 535, + // SI_EL_DEFENSIVE = 536, + // SI_EL_OFFENSIVE = 537, + // SI_EL_COST = 538, + SI_PYROTECHNIC = 539, + SI_PYROTECHNIC_OPTION = 540, + SI_HEATER = 541, + SI_HEATER_OPTION = 542, + SI_TROPIC = 543, + SI_TROPIC_OPTION = 544, + SI_AQUAPLAY = 545, + SI_AQUAPLAY_OPTION = 546, + SI_COOLER = 547, + SI_COOLER_OPTION = 548, + SI_CHILLY_AIR = 549, + SI_CHILLY_AIR_OPTION = 550, + SI_GUST = 551, + SI_GUST_OPTION = 552, + SI_BLAST = 553, + SI_BLAST_OPTION = 554, + SI_WILD_STORM = 555, + SI_WILD_STORM_OPTION = 556, + SI_PETROLOGY = 557, + SI_PETROLOGY_OPTION = 558, + SI_CURSED_SOIL = 559, + SI_CURSED_SOIL_OPTION = 560, + SI_UPHEAVAL = 561, + SI_UPHEAVAL_OPTION = 562, + SI_TIDAL_WEAPON = 563, + SI_TIDAL_WEAPON_OPTION = 564, + SI_ROCK_CRUSHER = 565, + SI_ROCK_CRUSHER_ATK = 566, + SI_FIRE_INSIGNIA = 567, + SI_WATER_INSIGNIA = 568, + SI_WIND_INSIGNIA = 569, + SI_EARTH_INSIGNIA = 570, + SI_EQUIPED_FLOOR = 571, + SI_GUARDIAN_RECALL = 572, + SI_MORA_BUFF = 573, + SI_REUSE_LIMIT_G = 574, + SI_REUSE_LIMIT_H = 575, + SI_NEEDLE_OF_PARALYZE = 576, + SI_PAIN_KILLER = 577, + SI_G_LIFEPOTION = 578, + SI_VITALIZE_POTION = 579, + SI_LIGHT_OF_REGENE = 580, + SI_OVERED_BOOST = 581, + SI_SILENT_BREEZE = 582, + SI_ODINS_POWER = 583, + SI_STYLE_CHANGE = 584, + SI_SONIC_CLAW_POSTDELAY = 585, + // ID's 586 - 595 Currently Unused + SI_SILVERVEIN_RUSH_POSTDELAY = 596, + SI_MIDNIGHT_FRENZY_POSTDELAY = 597, + SI_GOLDENE_FERSE = 598, + SI_ANGRIFFS_MODUS = 599, + SI_TINDER_BREAKER = 600, + SI_TINDER_BREAKER_POSTDELAY = 601, + SI_CBC = 602, + SI_CBC_POSTDELAY = 603, + SI_EQC = 604, + SI_MAGMA_FLOW = 605, + SI_GRANITIC_ARMOR = 606, + SI_PYROCLASTIC = 607, + SI_VOLCANIC_ASH = 608, + SI_SPIRITS_SAVEINFO1 = 609, + SI_SPIRITS_SAVEINFO2 = 610, + SI_MAGIC_CANDY = 611, + SI_SEARCH_STORE_INFO = 612, + SI_ALL_RIDING = 613, + SI_ALL_RIDING_REUSE_LIMIT = 614, + SI_MACRO = 615, + SI_MACRO_POSTDELAY = 616, + SI_BEER_BOTTLE_CAP = 617, + SI_OVERLAPEXPUP = 618, + SI_PC_IZ_DUN05 = 619, + SI_CRUSHSTRIKE = 620, + SI_MONSTER_TRANSFORM = 621, + SI_SIT = 622, + SI_ONAIR = 623, + SI_MTF_ASPD = 624, + SI_MTF_RANGEATK = 625, + SI_MTF_MATK = 626, + SI_MTF_MLEATKED = 627, + SI_MTF_CRIDAMAGE = 628, + SI_REUSE_LIMIT_MTF = 629, + SI_MACRO_PERMIT = 630, + SI_MACRO_PLAY = 631, + SI_SKF_CAST = 632, + SI_SKF_ASPD = 633, + SI_SKF_ATK = 634, + SI_SKF_MATK = 635, + SI_REWARD_PLUSONLYJOBEXP = 636, + SI_HANDICAPSTATE_NORECOVER = 637, + SI_SET_NUM_DEF = 638, + SI_SET_NUM_MDEF = 639, + SI_SET_PER_DEF = 640, + SI_SET_PER_MDEF = 641, + SI_PARTYBOOKING_SEARCH_DEALY = 642, + SI_PARTYBOOKING_REGISTER_DEALY = 643, + SI_PERIOD_TIME_CHECK_DETECT_SKILL = 644, + SI_KO_JYUMONJIKIRI = 645, + SI_MEIKYOUSISUI = 646, + SI_ATTHASTE_CASH = 647, + SI_EQUIPPED_DIVINE_ARMOR = 648, + SI_EQUIPPED_HOLY_ARMOR = 649, + SI_2011RWC = 650, + SI_KYOUGAKU = 651, + SI_IZAYOI = 652, + SI_ZENKAI = 653, + SI_KG_KAGEHUMI = 654, + SI_KYOMU = 655, + SI_KAGEMUSYA = 656, + SI_ZANGETSU = 657, + SI_PHI_DEMON = 658, + SI_GENSOU = 659, + SI_AKAITSUKI = 660, + SI_TETANY = 661, + SI_GM_BATTLE = 662, + SI_GM_BATTLE2 = 663, + SI_2011RWC_SCROLL = 664, + SI_ACTIVE_MONSTER_TRANSFORM = 665, + SI_MYSTICPOWDER = 666, + SI_ECLAGE_RECALL = 667, + SI_ENTRY_QUEUE_APPLY_DELAY = 668, + SI_REUSE_LIMIT_ECL = 669, + SI_M_LIFEPOTION = 670, + SI_ENTRY_QUEUE_NOTIFY_ADMISSION_TIME_OUT = 671, + SI_UNKNOWN_NAME = 672, + SI_ON_PUSH_CART = 673, + SI_HAT_EFFECT = 674, + SI_FLOWER_LEAF = 675, + SI_RAY_OF_PROTECTION = 676, + SI_GLASTHEIM_ATK = 677, + SI_GLASTHEIM_DEF = 678, + SI_GLASTHEIM_HEAL = 679, + SI_GLASTHEIM_HIDDEN = 680, + SI_GLASTHEIM_STATE = 681, + SI_GLASTHEIM_ITEMDEF = 682, + SI_GLASTHEIM_HPSP = 683, + SI_HOMUN_SKILL_POSTDELAY = 684, + SI_ALMIGHTY = 685, + SI_GVG_GIANT = 686, + SI_GVG_GOLEM = 687, + SI_GVG_STUN = 688, + SI_GVG_STONE = 689, + SI_GVG_FREEZ = 690, + SI_GVG_SLEEP = 691, + SI_GVG_CURSE = 692, + SI_GVG_SILENCE = 693, + SI_GVG_BLIND = 694, + SI_CLIENT_ONLY_EQUIP_ARROW = 695, + SI_CLAN_INFO = 696, + SI_JP_EVENT01 = 697, + SI_JP_EVENT02 = 698, + SI_JP_EVENT03 = 699, + SI_JP_EVENT04 = 700, + SI_TELEPORT_FIXEDCASTINGDELAY = 701, + SI_GEFFEN_MAGIC1 = 702, + SI_GEFFEN_MAGIC2 = 703, + SI_GEFFEN_MAGIC3 = 704, + SI_QUEST_BUFF1 = 705, + SI_QUEST_BUFF2 = 706, + SI_QUEST_BUFF3 = 707, + SI_REUSE_LIMIT_RECALL = 708, + SI_SAVEPOSITION = 709, + SI_MAX, +}; + +// JOINTBEAT stackable ailments +enum e_joint_break +{ + BREAK_ANKLE = 0x01, // MoveSpeed reduced by 50% + BREAK_WRIST = 0x02, // ASPD reduced by 25% + BREAK_KNEE = 0x04, // MoveSpeed reduced by 30%, ASPD reduced by 10% + BREAK_SHOULDER = 0x08, // DEF reduced by 50% + BREAK_WAIST = 0x10, // DEF reduced by 25%, ATK reduced by 25% + BREAK_NECK = 0x20, // current attack does 2x damage, inflicts 'bleeding' for 30 seconds + BREAK_FLAGS = BREAK_ANKLE | BREAK_WRIST | BREAK_KNEE | BREAK_SHOULDER | BREAK_WAIST | BREAK_NECK, +}; + +extern int current_equip_item_index; +extern int current_equip_card_id; + +//Mode definitions to clear up code reading. [Skotlex] +enum e_mode +{ + MD_CANMOVE = 0x0001, + MD_LOOTER = 0x0002, + MD_AGGRESSIVE = 0x0004, + MD_ASSIST = 0x0008, + MD_CASTSENSOR_IDLE = 0x0010, + MD_BOSS = 0x0020, + MD_PLANT = 0x0040, + MD_CANATTACK = 0x0080, + MD_DETECTOR = 0x0100, + MD_CASTSENSOR_CHASE = 0x0200, + MD_CHANGECHASE = 0x0400, + MD_ANGRY = 0x0800, + MD_CHANGETARGET_MELEE = 0x1000, + MD_CHANGETARGET_CHASE = 0x2000, + MD_TARGETWEAK = 0x4000, + MD_MASK = 0xFFFF, +}; + +//Status change option definitions (options are what makes status changes visible to chars +//who were not on your field of sight when it happened) + +//opt1: Non stackable status changes. +enum { + OPT1_STONE = 1, //Petrified + OPT1_FREEZE, + OPT1_STUN, + OPT1_SLEEP, + //Aegis uses OPT1 = 5 to identify undead enemies (which also grants them immunity to the other opt1 changes) + OPT1_STONEWAIT=6, //Petrifying + OPT1_BURNING, + OPT1_IMPRISON, + OPT1_CRYSTALIZE, +}; + +//opt2: Stackable status changes. +enum { + OPT2_POISON = 0x0001, + OPT2_CURSE = 0x0002, + OPT2_SILENCE = 0x0004, + OPT2_SIGNUMCRUCIS = 0x0008, + OPT2_BLIND = 0x0010, + OPT2_ANGELUS = 0x0020, + OPT2_BLEEDING = 0x0040, + OPT2_DPOISON = 0x0080, + OPT2_FEAR = 0x0100, +}; + +//opt3: (SHOW_EFST_*) +enum { + OPT3_NORMAL = 0x00000000, + OPT3_QUICKEN = 0x00000001, + OPT3_OVERTHRUST = 0x00000002, + OPT3_ENERGYCOAT = 0x00000004, + OPT3_EXPLOSIONSPIRITS = 0x00000008, + OPT3_STEELBODY = 0x00000010, + OPT3_BLADESTOP = 0x00000020, + OPT3_AURABLADE = 0x00000040, + OPT3_BERSERK = 0x00000080, + OPT3_LIGHTBLADE = 0x00000100, + OPT3_MOONLIT = 0x00000200, + OPT3_MARIONETTE = 0x00000400, + OPT3_ASSUMPTIO = 0x00000800, + OPT3_WARM = 0x00001000, + OPT3_KAITE = 0x00002000, + OPT3_BUNSIN = 0x00004000, + OPT3_SOULLINK = 0x00008000, + OPT3_UNDEAD = 0x00010000, + OPT3_CONTRACT = 0x00020000, +}; + +enum { + OPTION_NOTHING = 0x00000000, + OPTION_SIGHT = 0x00000001, + OPTION_HIDE = 0x00000002, + OPTION_CLOAK = 0x00000004, + OPTION_FALCON = 0x00000010, + OPTION_RIDING = 0x00000020, + OPTION_INVISIBLE = 0x00000040, + OPTION_ORCISH = 0x00000800, + OPTION_WEDDING = 0x00001000, + OPTION_RUWACH = 0x00002000, + OPTION_CHASEWALK = 0x00004000, + OPTION_FLYING = 0x00008000, //Note that clientside Flying and Xmas are 0x8000 for clients prior to 2007. + OPTION_XMAS = 0x00010000, + OPTION_TRANSFORM = 0x00020000, + OPTION_SUMMER = 0x00040000, + OPTION_DRAGON1 = 0x00080000, + OPTION_WUG = 0x00100000, + OPTION_WUGRIDER = 0x00200000, + OPTION_MADOGEAR = 0x00400000, + OPTION_DRAGON2 = 0x00800000, + OPTION_DRAGON3 = 0x01000000, + OPTION_DRAGON4 = 0x02000000, + OPTION_DRAGON5 = 0x04000000, + OPTION_MOUNTING = 0x08000000, + +#ifndef NEW_CARTS + OPTION_CART1 = 0x00000008, + OPTION_CART2 = 0x00000080, + OPTION_CART3 = 0x00000100, + OPTION_CART4 = 0x00000200, + OPTION_CART5 = 0x00000400, + + /* compound constant for older carts */ + OPTION_CART = OPTION_CART1|OPTION_CART2|OPTION_CART3|OPTION_CART4|OPTION_CART5, +#endif + + // compound constants + OPTION_DRAGON = OPTION_DRAGON1|OPTION_DRAGON2|OPTION_DRAGON3|OPTION_DRAGON4|OPTION_DRAGON5, + OPTION_MASK = ~OPTION_INVISIBLE, +}; + +//Defines for the manner system [Skotlex] +enum manner_flags +{ + MANNER_NOCHAT = 0x01, + MANNER_NOSKILL = 0x02, + MANNER_NOCOMMAND = 0x04, + MANNER_NOITEM = 0x08, + MANNER_NOROOM = 0x10, +}; + +/* Status Change State Flags */ +enum scs_flag { + SCS_NOMOVECOND = 0x00000001, /* cond flag for nomove */ + SCS_NOMOVE = 0x00000002, /* unit unable to move */ + SCS_NOPICKITEMCOND = 0x00000004, /* cond flag for nopickitem */ + SCS_NOPICKITEM = 0x00000008, /* player unable to pick up items */ + SCS_NODROPITEMCOND = 0x00000010, /* cond flag for nodropitem */ + SCS_NODROPITEM = 0x00000020, /* player unable to drop items */ + SCS_NOCASTCOND = 0x00000040, /* cond flag for nocast */ + SCS_NOCAST = 0x00000080, /* unit unable to cast skills */ +}; + +//Define flags for the status_calc_bl function. [Skotlex] +enum scb_flag +{ + SCB_NONE = 0x00000000, + SCB_BASE = 0x00000001, + SCB_MAXHP = 0x00000002, + SCB_MAXSP = 0x00000004, + SCB_STR = 0x00000008, + SCB_AGI = 0x00000010, + SCB_VIT = 0x00000020, + SCB_INT = 0x00000040, + SCB_DEX = 0x00000080, + SCB_LUK = 0x00000100, + SCB_BATK = 0x00000200, + SCB_WATK = 0x00000400, + SCB_MATK = 0x00000800, + SCB_HIT = 0x00001000, + SCB_FLEE = 0x00002000, + SCB_DEF = 0x00004000, + SCB_DEF2 = 0x00008000, + SCB_MDEF = 0x00010000, + SCB_MDEF2 = 0x00020000, + SCB_SPEED = 0x00040000, + SCB_ASPD = 0x00080000, + SCB_DSPD = 0x00100000, + SCB_CRI = 0x00200000, + SCB_FLEE2 = 0x00400000, + SCB_ATK_ELE = 0x00800000, + SCB_DEF_ELE = 0x01000000, + SCB_MODE = 0x02000000, + SCB_SIZE = 0x04000000, + SCB_RACE = 0x08000000, + SCB_RANGE = 0x10000000, + SCB_REGEN = 0x20000000, + SCB_DYE = 0x40000000, // force cloth-dye change to 0 to avoid client crashes. + + SCB_BATTLE = 0x3FFFFFFE, + SCB_ALL = 0x3FFFFFFF +}; + +//Define to determine who gets HP/SP consumed on doing skills/etc. [Skotlex] +#define BL_CONSUME (BL_PC|BL_HOM|BL_MER|BL_ELEM) +//Define to determine who has regen +#define BL_REGEN (BL_PC|BL_HOM|BL_MER|BL_ELEM) +//Define to determine who will receive a clif_status_change packet for effects that require one to display correctly +#define BL_SCEFFECT (BL_PC|BL_HOM|BL_MER|BL_MOB|BL_ELEM) + +//Basic damage info of a weapon +//Required because players have two of these, one in status_data +//and another for their left hand weapon. +struct weapon_atk { + unsigned short atk, atk2; + unsigned short range; + unsigned char ele; +#ifdef RENEWAL + unsigned short matk; + unsigned char wlv; +#endif +}; + + +//For holding basic status (which can be modified by status changes) +struct status_data { + unsigned int + hp, sp, // see status_cpy before adding members before hp and sp + max_hp, max_sp; + unsigned short + str, agi, vit, int_, dex, luk, + batk, + matk_min, matk_max, + speed, + amotion, adelay, dmotion, + mode; + short + hit, flee, cri, flee2, + def2, mdef2, +#ifdef RENEWAL_ASPD + aspd_rate2, +#endif + aspd_rate; + /** + * defType is RENEWAL dependent and defined in src/map/config/data/const.h + **/ + defType def,mdef; + + unsigned char + def_ele, ele_lv, + size, race; + + struct weapon_atk rhw, lhw; //Right Hand/Left Hand Weapon. +}; + +//Additional regen data that only players have. +struct regen_data_sub { + unsigned short + hp,sp; + + //tick accumulation before healing. + struct { + unsigned int hp,sp; + } tick; + + //Regen rates (where every 1 means +100% regen) + struct { + unsigned char hp,sp; + } rate; +}; + +struct regen_data { + + unsigned short flag; //Marks what stuff you may heal or not. + unsigned short + hp,sp,shp,ssp; + + //tick accumulation before healing. + struct { + unsigned int hp,sp,shp,ssp; + } tick; + + //Regen rates (where every 1 means +100% regen) + struct { + unsigned char + hp,sp,shp,ssp; + } rate; + + struct { + unsigned walk:1; //Can you regen even when walking? + unsigned gc:1; //Tags when you should have double regen due to GVG castle + unsigned overweight :2; //overweight state (1: 50%, 2: 90%) + unsigned block :2; //Block regen flag (1: Hp, 2: Sp) + } state; + + //skill-regen, sitting-skill-regen (since not all chars with regen need it) + struct regen_data_sub *sregen, *ssregen; +}; + +struct status_change_entry { + int timer; + int val1,val2,val3,val4; +}; + +struct status_change { + unsigned int option;// effect state (bitfield) + unsigned int opt3;// skill state (bitfield) + unsigned short opt1;// body state + unsigned short opt2;// health state (bitfield) + unsigned char count; + //TODO: See if it is possible to implement the following SC's without requiring extra parameters while the SC is inactive. + unsigned char jb_flag; //Joint Beat type flag + struct { + unsigned char move; + unsigned char pickup; + unsigned char drop; + unsigned char cast; + } cant;/* status change state flags */ + //int sg_id; //ID of the previous Storm gust that hit you + short comet_x, comet_y; // Point where src casted Comet - required to calculate damage from this point +/** + * The Storm Gust counter was dropped in renewal + **/ +#ifndef RENEWAL + unsigned char sg_counter; //Storm gust counter (previous hits from storm gust) +#endif + unsigned char bs_counter; // Blood Sucker counter + struct status_change_entry *data[SC_MAX]; +}; + +// for looking up associated data +sc_type status_skill2sc(int skill); +int status_sc2skill(sc_type sc); +unsigned int status_sc2scb_flag(sc_type sc); +int status_type2relevant_bl_types(int type); + +int status_damage(struct block_list *src,struct block_list *target,int hp,int sp, int walkdelay, int flag); +//Define for standard HP damage attacks. +#define status_fix_damage(src, target, hp, walkdelay) status_damage(src, target, hp, 0, walkdelay, 0) +//Define for standard HP/SP damage triggers. +#define status_zap(bl, hp, sp) status_damage(NULL, bl, hp, sp, 0, 1) +//Define for standard HP/SP skill-related cost triggers (mobs require no HP/SP to use skills) +int status_charge(struct block_list* bl, int hp, int sp); +int status_percent_change(struct block_list *src,struct block_list *target,signed char hp_rate, signed char sp_rate, int flag); +//Easier handling of status_percent_change +#define status_percent_heal(bl, hp_rate, sp_rate) status_percent_change(NULL, bl, -(hp_rate), -(sp_rate), 0) +#define status_percent_damage(src, target, hp_rate, sp_rate, kill) status_percent_change(src, target, hp_rate, sp_rate, (kill)?1:2) +//Instant kill with no drops/exp/etc +#define status_kill(bl) status_percent_damage(NULL, bl, 100, 0, true) +//Used to set the hp/sp of an object to an absolute value (can't kill) +int status_set_hp(struct block_list *bl, unsigned int hp, int flag); +int status_set_sp(struct block_list *bl, unsigned int sp, int flag); +int status_heal(struct block_list *bl,int hp,int sp, int flag); +int status_revive(struct block_list *bl, unsigned char per_hp, unsigned char per_sp); + +struct regen_data *status_get_regen_data(struct block_list *bl); +struct status_data *status_get_status_data(struct block_list *bl); +struct status_data *status_get_base_status(struct block_list *bl); +const char * status_get_name(struct block_list *bl); +int status_get_class(struct block_list *bl); +int status_get_lv(struct block_list *bl); +#define status_get_range(bl) status_get_status_data(bl)->rhw.range +#define status_get_hp(bl) status_get_status_data(bl)->hp +#define status_get_max_hp(bl) status_get_status_data(bl)->max_hp +#define status_get_sp(bl) status_get_status_data(bl)->sp +#define status_get_max_sp(bl) status_get_status_data(bl)->max_sp +#define status_get_str(bl) status_get_status_data(bl)->str +#define status_get_agi(bl) status_get_status_data(bl)->agi +#define status_get_vit(bl) status_get_status_data(bl)->vit +#define status_get_int(bl) status_get_status_data(bl)->int_ +#define status_get_dex(bl) status_get_status_data(bl)->dex +#define status_get_luk(bl) status_get_status_data(bl)->luk +#define status_get_hit(bl) status_get_status_data(bl)->hit +#define status_get_flee(bl) status_get_status_data(bl)->flee +defType status_get_def(struct block_list *bl); +#define status_get_mdef(bl) status_get_status_data(bl)->mdef +#define status_get_flee2(bl) status_get_status_data(bl)->flee2 +#define status_get_def2(bl) status_get_status_data(bl)->def2 +#define status_get_mdef2(bl) status_get_status_data(bl)->mdef2 +#define status_get_critical(bl) status_get_status_data(bl)->cri +#define status_get_batk(bl) status_get_status_data(bl)->batk +#define status_get_watk(bl) status_get_status_data(bl)->rhw.atk +#define status_get_watk2(bl) status_get_status_data(bl)->rhw.atk2 +#define status_get_matk_max(bl) status_get_status_data(bl)->matk_max +#define status_get_matk_min(bl) status_get_status_data(bl)->matk_min +#define status_get_lwatk(bl) status_get_status_data(bl)->lhw.atk +#define status_get_lwatk2(bl) status_get_status_data(bl)->lhw.atk2 +unsigned short status_get_speed(struct block_list *bl); +#define status_get_adelay(bl) status_get_status_data(bl)->adelay +#define status_get_amotion(bl) status_get_status_data(bl)->amotion +#define status_get_dmotion(bl) status_get_status_data(bl)->dmotion +#define status_get_element(bl) status_get_status_data(bl)->def_ele +#define status_get_element_level(bl) status_get_status_data(bl)->ele_lv +unsigned char status_calc_attack_element(struct block_list *bl, struct status_change *sc, int element); +#define status_get_attack_sc_element(bl, sc) status_calc_attack_element(bl, sc, 0) +#define status_get_attack_element(bl) status_get_status_data(bl)->rhw.ele +#define status_get_attack_lelement(bl) status_get_status_data(bl)->lhw.ele +#define status_get_race(bl) status_get_status_data(bl)->race +#define status_get_size(bl) status_get_status_data(bl)->size +#define status_get_mode(bl) status_get_status_data(bl)->mode +int status_get_party_id(struct block_list *bl); +int status_get_guild_id(struct block_list *bl); +int status_get_emblem_id(struct block_list *bl); +int status_get_mexp(struct block_list *bl); +int status_get_race2(struct block_list *bl); + +struct view_data *status_get_viewdata(struct block_list *bl); +void status_set_viewdata(struct block_list *bl, int class_); +void status_change_init(struct block_list *bl); +struct status_change *status_get_sc(struct block_list *bl); + +int status_isdead(struct block_list *bl); +int status_isimmune(struct block_list *bl); + +int status_get_sc_def(struct block_list *bl, enum sc_type type, int rate, int tick, int flag); +//Short version, receives rate in 1->100 range, and does not uses a flag setting. +#define sc_start(bl, type, rate, val1, tick) status_change_start(bl,type,100*(rate),val1,0,0,0,tick,0) +#define sc_start2(bl, type, rate, val1, val2, tick) status_change_start(bl,type,100*(rate),val1,val2,0,0,tick,0) +#define sc_start4(bl, type, rate, val1, val2, val3, val4, tick) status_change_start(bl,type,100*(rate),val1,val2,val3,val4,tick,0) + +int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,int tick,int flag); +int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const char* file, int line); +#define status_change_end(bl,type,tid) status_change_end_(bl,type,tid,__FILE__,__LINE__) +int kaahi_heal_timer(int tid, unsigned int tick, int id, intptr_t data); +int status_change_timer(int tid, unsigned int tick, int id, intptr_t data); +int status_change_timer_sub(struct block_list* bl, va_list ap); +int status_change_clear(struct block_list* bl, int type); +int status_change_clear_buffs(struct block_list* bl, int type); + +#define status_calc_bl(bl, flag) status_calc_bl_(bl, (enum scb_flag)(flag), false) +#define status_calc_mob(md, first) status_calc_bl_(&(md)->bl, SCB_ALL, first) +#define status_calc_pet(pd, first) status_calc_bl_(&(pd)->bl, SCB_ALL, first) +#define status_calc_pc(sd, first) status_calc_bl_(&(sd)->bl, SCB_ALL, first) +#define status_calc_homunculus(hd, first) status_calc_bl_(&(hd)->bl, SCB_ALL, first) +#define status_calc_mercenary(md, first) status_calc_bl_(&(md)->bl, SCB_ALL, first) +#define status_calc_elemental(ed, first) status_calc_bl_(&(ed)->bl, SCB_ALL, first) +#define status_calc_npc(nd, first) status_calc_bl_(&(nd)->bl, SCB_ALL, first) + +void status_calc_bl_(struct block_list *bl, enum scb_flag flag, bool first); +int status_calc_mob_(struct mob_data* md, bool first); +int status_calc_pet_(struct pet_data* pd, bool first); +int status_calc_pc_(struct map_session_data* sd, bool first); +int status_calc_homunculus_(struct homun_data *hd, bool first); +int status_calc_mercenary_(struct mercenary_data *md, bool first); +int status_calc_elemental_(struct elemental_data *ed, bool first); + +void status_calc_misc(struct block_list *bl, struct status_data *status, int level); +void status_calc_regen(struct block_list *bl, struct status_data *status, struct regen_data *regen); +void status_calc_regen_rate(struct block_list *bl, struct regen_data *regen, struct status_change *sc); + +int status_check_skilluse(struct block_list *src, struct block_list *target, uint16 skill_id, int flag); // [Skotlex] +int status_check_visibility(struct block_list *src, struct block_list *target); //[Skotlex] + +int status_change_spread( struct block_list *src, struct block_list *bl ); + +#ifdef RENEWAL +unsigned short status_base_matk(const struct status_data* status, int level); +#endif + +int status_readdb(void); +int do_init_status(void); +void do_final_status(void); + +#endif /* _STATUS_H_ */ diff --git a/src/map/storage.c b/src/map/storage.c new file mode 100644 index 000000000..eb7760a0f --- /dev/null +++ b/src/map/storage.c @@ -0,0 +1,735 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +#include "map.h" // struct map_session_data +#include "storage.h" +#include "chrif.h" +#include "itemdb.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "guild.h" +#include "battle.h" +#include "atcommand.h" +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +static DBMap* guild_storage_db; // int guild_id -> struct guild_storage* + +/*========================================== + * Sort items in the warehouse + *------------------------------------------*/ +static int storage_comp_item(const void *_i1, const void *_i2) +{ + struct item *i1 = (struct item *)_i1; + struct item *i2 = (struct item *)_i2; + + if (i1->nameid == i2->nameid) + return 0; + else if (!(i1->nameid) || !(i1->amount)) + return 1; + else if (!(i2->nameid) || !(i2->amount)) + return -1; + return i1->nameid - i2->nameid; +} + +//Sort item by storage_comp_item (nameid) +static void storage_sortitem(struct item* items, unsigned int size) +{ + nullpo_retv(items); + + if( battle_config.client_sort_storage ) + { + qsort(items, size, sizeof(struct item), storage_comp_item); + } +} + +/*========================================== + * Init/Terminate + *------------------------------------------*/ +int do_init_storage(void) // Called from map.c::do_init() +{ + guild_storage_db=idb_alloc(DB_OPT_RELEASE_DATA); + return 1; +} +void do_final_storage(void) // by [MC Cameri] +{ + guild_storage_db->destroy(guild_storage_db,NULL); +} + +/** + * Parses storage and saves 'dirty' ones upon reconnect. [Skotlex] + * @see DBApply + */ +static int storage_reconnect_sub(DBKey key, DBData *data, va_list ap) +{ + struct guild_storage *stor = db_data2ptr(data); + if (stor->dirty && stor->storage_status == 0) //Save closed storages. + storage_guild_storagesave(0, stor->guild_id,0); + + return 0; +} + +//Function to be invoked upon server reconnection to char. To save all 'dirty' storages [Skotlex] +void do_reconnect_storage(void) +{ + guild_storage_db->foreach(guild_storage_db, storage_reconnect_sub); +} + +/*========================================== + * Opens a storage. Returns: + * 0 - success + * 1 - fail + *------------------------------------------*/ +int storage_storageopen(struct map_session_data *sd) +{ + nullpo_ret(sd); + + if(sd->state.storage_flag) + return 1; //Already open? + + if( !pc_can_give_items(sd) ) + { //check is this GM level is allowed to put items to storage + clif_displaymessage(sd->fd, msg_txt(246)); + return 1; + } + + sd->state.storage_flag = 1; + storage_sortitem(sd->status.storage.items, ARRAYLENGTH(sd->status.storage.items)); + clif_storagelist(sd, sd->status.storage.items, ARRAYLENGTH(sd->status.storage.items)); + clif_updatestorageamount(sd, sd->status.storage.storage_amount, MAX_STORAGE); + return 0; +} + +/* helper function + * checking if 2 item structure are identique + */ +int compare_item(struct item *a, struct item *b) +{ + if( a->nameid == b->nameid && + a->identify == b->identify && + a->refine == b->refine && + a->attribute == b->attribute && + a->expire_time == b->expire_time ) + { + int i; + for (i = 0; i < MAX_SLOTS && (a->card[i] == b->card[i]); i++); + return (i == MAX_SLOTS); + } + return 0; +} + +/*========================================== + * Internal add-item function. + *------------------------------------------*/ +static int storage_additem(struct map_session_data* sd, struct item* item_data, int amount) +{ + struct storage_data* stor = &sd->status.storage; + struct item_data *data; + int i; + + if( item_data->nameid <= 0 || amount <= 0 ) + return 1; + + data = itemdb_search(item_data->nameid); + + if( data->stack.storage && amount > data->stack.amount ) + {// item stack limitation + return 1; + } + + if( !itemdb_canstore(item_data, pc_get_group_level(sd)) ) + { //Check if item is storable. [Skotlex] + clif_displaymessage (sd->fd, msg_txt(264)); + return 1; + } + + if( itemdb_isstackable2(data) ) + {//Stackable + for( i = 0; i < MAX_STORAGE; i++ ) + { + if( compare_item(&stor->items[i], item_data) ) + {// existing items found, stack them + if( amount > MAX_AMOUNT - stor->items[i].amount || ( data->stack.storage && amount > data->stack.amount - stor->items[i].amount ) ) + return 1; + stor->items[i].amount += amount; + clif_storageitemadded(sd,&stor->items[i],i,amount); + return 0; + } + } + } + + // find free slot + ARR_FIND( 0, MAX_STORAGE, i, stor->items[i].nameid == 0 ); + if( i >= MAX_STORAGE ) + return 1; + + // add item to slot + memcpy(&stor->items[i],item_data,sizeof(stor->items[0])); + stor->storage_amount++; + stor->items[i].amount = amount; + clif_storageitemadded(sd,&stor->items[i],i,amount); + clif_updatestorageamount(sd, stor->storage_amount, MAX_STORAGE); + + return 0; +} + +/*========================================== + * Internal del-item function + *------------------------------------------*/ +int storage_delitem(struct map_session_data* sd, int n, int amount) +{ + if( sd->status.storage.items[n].nameid == 0 || sd->status.storage.items[n].amount < amount ) + return 1; + + sd->status.storage.items[n].amount -= amount; + if( sd->status.storage.items[n].amount == 0 ) + { + memset(&sd->status.storage.items[n],0,sizeof(sd->status.storage.items[0])); + sd->status.storage.storage_amount--; + if( sd->state.storage_flag == 1 ) clif_updatestorageamount(sd, sd->status.storage.storage_amount, MAX_STORAGE); + } + if( sd->state.storage_flag == 1 ) clif_storageitemremoved(sd,n,amount); + return 0; +} + +/*========================================== + * Add an item to the storage from the inventory. + * @index : inventory idx + * return + * 0 : fail + * 1 : success + *------------------------------------------*/ +int storage_storageadd(struct map_session_data* sd, int index, int amount) +{ + nullpo_ret(sd); + + if( sd->status.storage.storage_amount > MAX_STORAGE ) + return 0; // storage full + + if( index < 0 || index >= MAX_INVENTORY ) + return 0; + + if( sd->status.inventory[index].nameid <= 0 ) + return 0; // No item on that spot + + if( amount < 1 || amount > sd->status.inventory[index].amount ) + return 0; + + if( storage_additem(sd,&sd->status.inventory[index],amount) == 0 ) + pc_delitem(sd,index,amount,0,4,LOG_TYPE_STORAGE); + + return 1; +} + +/*========================================== + * Retrieve an item from the storage into inventory + * @index : storage idx + * return + * 0 : fail + * 1 : success + *------------------------------------------*/ +int storage_storageget(struct map_session_data* sd, int index, int amount) +{ + int flag; + + if( index < 0 || index >= MAX_STORAGE ) + return 0; + + if( sd->status.storage.items[index].nameid <= 0 ) + return 0; //Nothing there + + if( amount < 1 || amount > sd->status.storage.items[index].amount ) + return 0; + + if( (flag = pc_additem(sd,&sd->status.storage.items[index],amount,LOG_TYPE_STORAGE)) == 0 ) + storage_delitem(sd,index,amount); + else + clif_additem(sd,0,0,flag); + + return 1; +} + +/*========================================== + * Move an item from cart to storage. + * @index : cart inventory index + * return + * 0 : fail + * 1 : success + *------------------------------------------*/ +int storage_storageaddfromcart(struct map_session_data* sd, int index, int amount) +{ + nullpo_ret(sd); + + if( sd->status.storage.storage_amount > MAX_STORAGE ) + return 0; // storage full / storage closed + + if( index < 0 || index >= MAX_CART ) + return 0; + + if( sd->status.cart[index].nameid <= 0 ) + return 0; //No item there. + + if( amount < 1 || amount > sd->status.cart[index].amount ) + return 0; + + if( storage_additem(sd,&sd->status.cart[index],amount) == 0 ) + pc_cart_delitem(sd,index,amount,0,LOG_TYPE_STORAGE); + + return 1; +} + +/*========================================== + * Get from Storage to the Cart inventory + * @index : storage index + * return + * 0 : fail + * 1 : success + *------------------------------------------*/ +int storage_storagegettocart(struct map_session_data* sd, int index, int amount) +{ + nullpo_ret(sd); + + if( index < 0 || index >= MAX_STORAGE ) + return 0; + + if( sd->status.storage.items[index].nameid <= 0 ) + return 0; //Nothing there. + + if( amount < 1 || amount > sd->status.storage.items[index].amount ) + return 0; + + if( pc_cart_additem(sd,&sd->status.storage.items[index],amount,LOG_TYPE_STORAGE) == 0 ) + storage_delitem(sd,index,amount); + + return 1; +} + + +/*========================================== + * Modified By Valaris to save upon closing [massdriller] + *------------------------------------------*/ +void storage_storageclose(struct map_session_data* sd) +{ + nullpo_retv(sd); + + clif_storageclose(sd); + + if( save_settings&4 ) + chrif_save(sd,0); //Invokes the storage saving as well. + + sd->state.storage_flag = 0; +} + +/*========================================== + * When quitting the game. + *------------------------------------------*/ +void storage_storage_quit(struct map_session_data* sd, int flag) +{ + nullpo_retv(sd); + + if (save_settings&4) + chrif_save(sd, flag); //Invokes the storage saving as well. + + sd->state.storage_flag = 0; +} + +/** + * @see DBCreateData + */ +static DBData 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 db_ptr2data(gs); +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(guild_search(guild_id) != NULL) + gs = idb_ensure(guild_storage_db,guild_id,create_guildstorage); + return gs; +} + +//For just locating a storage without creating one. [Skotlex] +struct guild_storage *guild2storage2(int guild_id) +{ + return (struct guild_storage*)idb_get(guild_storage_db,guild_id); +} + +int guild_storage_delete(int guild_id) +{ + idb_remove(guild_storage_db,guild_id); + return 0; +} + +/*========================================== +* Attempt to open guild storage for sd +* return +* 0 : success (open or req to create a new one) +* 1 : fail +* 2 : no guild for sd + *------------------------------------------*/ +int storage_guild_storageopen(struct map_session_data* sd) +{ + struct guild_storage *gstor; + + nullpo_ret(sd); + + if(sd->status.guild_id <= 0) + return 2; + + if(sd->state.storage_flag) + return 1; //Can't open both storages at a time. + + if( !pc_can_give_items(sd) ) { //check is this GM level can open guild storage and store items [Lupus] + clif_displaymessage(sd->fd, msg_txt(246)); + return 1; + } + + if((gstor = guild2storage2(sd->status.guild_id)) == NULL) { + intif_request_guild_storage(sd->status.account_id,sd->status.guild_id); + return 0; + } + if(gstor->storage_status) + return 1; + + if( gstor->lock ) + return 1; + + gstor->storage_status = 1; + sd->state.storage_flag = 2; + storage_sortitem(gstor->items, ARRAYLENGTH(gstor->items)); + clif_storagelist(sd, gstor->items, ARRAYLENGTH(gstor->items)); + clif_updatestorageamount(sd, gstor->storage_amount, MAX_GUILD_STORAGE); + return 0; +} + +/*========================================== +* Attempt to add an item in guild storage, then refresh it +* return +* 0 : success +* 1 : fail + *------------------------------------------*/ +int guild_storage_additem(struct map_session_data* sd, struct guild_storage* stor, struct item* item_data, int amount) +{ + struct item_data *data; + int i; + + nullpo_retr(1, sd); + nullpo_retr(1, stor); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + + data = itemdb_search(item_data->nameid); + + if( data->stack.guildstorage && amount > data->stack.amount ) + {// item stack limitation + return 1; + } + + if( !itemdb_canguildstore(item_data, pc_get_group_level(sd)) || item_data->expire_time ) + { //Check if item is storable. [Skotlex] + clif_displaymessage (sd->fd, msg_txt(264)); + return 1; + } + + if(itemdb_isstackable2(data)){ //Stackable + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(compare_item(&stor->items[i], item_data)) { + if( amount > MAX_AMOUNT - stor->items[i].amount || ( data->stack.guildstorage && amount > data->stack.amount - stor->items[i].amount ) ) + return 1; + stor->items[i].amount+=amount; + clif_storageitemadded(sd,&stor->items[i],i,amount); + stor->dirty = 1; + return 0; + } + } + } + //Add item + for(i=0;i<MAX_GUILD_STORAGE && stor->items[i].nameid;i++); + + if(i>=MAX_GUILD_STORAGE) + return 1; + + memcpy(&stor->items[i],item_data,sizeof(stor->items[0])); + stor->items[i].amount=amount; + stor->storage_amount++; + clif_storageitemadded(sd,&stor->items[i],i,amount); + clif_updatestorageamount(sd, stor->storage_amount, MAX_GUILD_STORAGE); + stor->dirty = 1; + return 0; +} + +/*========================================== +* Attempt to delete an item in guild storage, then refresh it +* return +* 0 : success +* 1 : fail + *------------------------------------------*/ +int guild_storage_delitem(struct map_session_data* sd, struct guild_storage* stor, int n, int amount) +{ + nullpo_retr(1, sd); + nullpo_retr(1, stor); + + if(stor->items[n].nameid==0 || stor->items[n].amount<amount) + return 1; + + stor->items[n].amount-=amount; + if(stor->items[n].amount==0){ + memset(&stor->items[n],0,sizeof(stor->items[0])); + stor->storage_amount--; + clif_updatestorageamount(sd, stor->storage_amount, MAX_GUILD_STORAGE); + } + clif_storageitemremoved(sd,n,amount); + stor->dirty = 1; + return 0; +} + +/*========================================== +* Attempt to add an item in guild storage from inventory, then refresh it +* @index : inventory idx +* return +* 0 : fail +* 1 : succes + *------------------------------------------*/ +int storage_guild_storageadd(struct map_session_data* sd, int index, int amount) +{ + struct guild_storage *stor; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + if( !stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE ) + return 0; + + if( index<0 || index>=MAX_INVENTORY ) + return 0; + + if( sd->status.inventory[index].nameid <= 0 ) + return 0; + + if( amount < 1 || amount > sd->status.inventory[index].amount ) + return 0; + + if( stor->lock ) { + storage_guild_storageclose(sd); + return 0; + } + + if(guild_storage_additem(sd,stor,&sd->status.inventory[index],amount)==0) + pc_delitem(sd,index,amount,0,4,LOG_TYPE_GSTORAGE); + + return 1; +} + +/*========================================== +* Attempt to retrieve an item from guild storage to inventory, then refresh it +* @index : storage idx +* return +* 0 : fail +* 1 : succes + *------------------------------------------*/ +int storage_guild_storageget(struct map_session_data* sd, int index, int amount) +{ + struct guild_storage *stor; + int flag; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + if(!stor->storage_status) + return 0; + + if(index<0 || index>=MAX_GUILD_STORAGE) + return 0; + + if(stor->items[index].nameid <= 0) + return 0; + + if(amount < 1 || amount > stor->items[index].amount) + return 0; + + if( stor->lock ) { + storage_guild_storageclose(sd); + return 0; + } + + if((flag = pc_additem(sd,&stor->items[index],amount,LOG_TYPE_GSTORAGE)) == 0) + guild_storage_delitem(sd,stor,index,amount); + else //inform fail + clif_additem(sd,0,0,flag); +// log_fromstorage(sd, index, 1); + + return 0; +} + +/*========================================== +* Attempt to add an item in guild storage from cart, then refresh it +* @index : cart inventory idx +* return +* 0 : fail +* 1 : succes + *------------------------------------------*/ +int storage_guild_storageaddfromcart(struct map_session_data* sd, int index, int amount) +{ + struct guild_storage *stor; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + if( !stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE ) + return 0; + + if( index < 0 || index >= MAX_CART ) + return 0; + + if( sd->status.cart[index].nameid <= 0 ) + return 0; + + if( amount < 1 || amount > sd->status.cart[index].amount ) + return 0; + + if(guild_storage_additem(sd,stor,&sd->status.cart[index],amount)==0) + pc_cart_delitem(sd,index,amount,0,LOG_TYPE_GSTORAGE); + + return 1; +} + +/*========================================== +* Attempt to retrieve an item from guild storage to cart, then refresh it +* @index : storage idx +* return +* 0 : fail +* 1 : succes + *------------------------------------------*/ +int storage_guild_storagegettocart(struct map_session_data* sd, int index, int amount) +{ + struct guild_storage *stor; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + if(!stor->storage_status) + return 0; + + if(index<0 || index>=MAX_GUILD_STORAGE) + return 0; + + if(stor->items[index].nameid<=0) + return 0; + + if(amount < 1 || amount > stor->items[index].amount) + return 0; + + if(pc_cart_additem(sd,&stor->items[index],amount,LOG_TYPE_GSTORAGE)==0) + guild_storage_delitem(sd,stor,index,amount); + + return 1; +} + +/*========================================== +* Request to save guild storage +* return +* 0 : fail (no storage) +* 1 : succes + *------------------------------------------*/ +int storage_guild_storagesave(int account_id, int guild_id, int flag) +{ + struct guild_storage *stor = guild2storage2(guild_id); + + if(stor) + { + if (flag) //Char quitting, close it. + stor->storage_status = 0; + if (stor->dirty) + intif_send_guild_storage(account_id,stor); + return 1; + } + return 0; +} + +/*========================================== +* ACK save of guild storage +* return +* 0 : fail (no storage) +* 1 : succes + *------------------------------------------*/ +int storage_guild_storagesaved(int guild_id) +{ + struct guild_storage *stor; + + if((stor=guild2storage2(guild_id)) != NULL) { + if (stor->dirty && stor->storage_status == 0) + { //Storage has been correctly saved. + stor->dirty = 0; + } + return 1; + } + return 0; +} + +//Close storage for sd and save it +int storage_guild_storageclose(struct map_session_data* sd) +{ + struct guild_storage *stor; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + clif_storageclose(sd); + if (stor->storage_status) + { + if (save_settings&4) + chrif_save(sd, 0); //This one also saves the storage. [Skotlex] + else + storage_guild_storagesave(sd->status.account_id, sd->status.guild_id,0); + stor->storage_status=0; + } + sd->state.storage_flag = 0; + + return 0; +} + +int storage_guild_storage_quit(struct map_session_data* sd, int flag) +{ + struct guild_storage *stor; + + nullpo_ret(sd); + nullpo_ret(stor=guild2storage2(sd->status.guild_id)); + + if(flag) + { //Only during a guild break flag is 1 (don't save storage) + sd->state.storage_flag = 0; + stor->storage_status = 0; + clif_storageclose(sd); + if (save_settings&4) + chrif_save(sd,0); + return 0; + } + + if(stor->storage_status) { + if (save_settings&4) + chrif_save(sd,0); + else + storage_guild_storagesave(sd->status.account_id,sd->status.guild_id,1); + } + sd->state.storage_flag = 0; + stor->storage_status = 0; + + return 0; +} diff --git a/src/map/storage.h b/src/map/storage.h new file mode 100644 index 000000000..c08ec81cb --- /dev/null +++ b/src/map/storage.h @@ -0,0 +1,41 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _STORAGE_H_ +#define _STORAGE_H_ + +//#include "../common/mmo.h" +struct storage_data; +struct guild_storage; +struct item; +//#include "map.h" +struct map_session_data; + +int storage_delitem(struct map_session_data* sd, int n, int amount); +int storage_storageopen(struct map_session_data *sd); +int storage_storageadd(struct map_session_data *sd,int index,int amount); +int storage_storageget(struct map_session_data *sd,int index,int amount); +int storage_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_storagegettocart(struct map_session_data *sd,int index,int amount); +void storage_storageclose(struct map_session_data *sd); +int do_init_storage(void); +void do_final_storage(void); +void do_reconnect_storage(void); +void storage_storage_quit(struct map_session_data *sd, int flag); + +struct guild_storage* guild2storage(int guild_id); +struct guild_storage *guild2storage2(int guild_id); +int guild_storage_delete(int guild_id); +int storage_guild_storageopen(struct map_session_data *sd); +int guild_storage_additem(struct map_session_data *sd,struct guild_storage *stor,struct item *item_data,int amount); +int guild_storage_delitem(struct map_session_data *sd,struct guild_storage *stor,int n,int amount); +int storage_guild_storageadd(struct map_session_data *sd,int index,int amount); +int storage_guild_storageget(struct map_session_data *sd,int index,int amount); +int storage_guild_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_guild_storagegettocart(struct map_session_data *sd,int index,int amount); +int storage_guild_storageclose(struct map_session_data *sd); +int storage_guild_storage_quit(struct map_session_data *sd,int flag); +int storage_guild_storagesave(int account_id, int guild_id, int flag); +int storage_guild_storagesaved(int guild_id); //Ack from char server that guild store was saved. + +#endif /* _STORAGE_H_ */ diff --git a/src/map/trade.c b/src/map/trade.c new file mode 100644 index 000000000..0d01b54a6 --- /dev/null +++ b/src/map/trade.c @@ -0,0 +1,610 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/nullpo.h" +#include "../common/socket.h" +#include "clif.h" +#include "itemdb.h" +#include "map.h" +#include "path.h" +#include "trade.h" +#include "pc.h" +#include "npc.h" +#include "battle.h" +#include "chrif.h" +#include "storage.h" +#include "intif.h" +#include "atcommand.h" +#include "log.h" + +#include <stdio.h> +#include <string.h> + + +//Max distance from traders to enable a trade to take place. +#define TRADE_DISTANCE 2 + +/*========================================== + * Initiates a trade request. + *------------------------------------------*/ +void trade_traderequest(struct map_session_data *sd, struct map_session_data *target_sd) +{ + nullpo_retv(sd); + + if (map[sd->bl.m].flag.notrade) { + clif_displaymessage (sd->fd, msg_txt(272)); + return; //Can't trade in notrade mapflag maps. + } + + if (target_sd == NULL || sd == target_sd) { + clif_tradestart(sd, 1); // character does not exist + return; + } + + if (target_sd->npc_id) + { //Trade fails if you are using an NPC. + clif_tradestart(sd, 2); + return; + } + + if (!battle_config.invite_request_check) { + if (target_sd->guild_invite > 0 || target_sd->party_invite > 0 || target_sd->adopt_invite) { + clif_tradestart(sd, 2); + return; + } + } + + if ( sd->trade_partner != 0 ) { // If a character tries to trade to another one then cancel the previous one + struct map_session_data *previous_sd = map_id2sd(sd->trade_partner); + if( previous_sd ){ + previous_sd->trade_partner = 0; + clif_tradecancelled(previous_sd); + } // Once cancelled then continue to the new one. + sd->trade_partner = 0; + clif_tradecancelled(sd); + } + + if (target_sd->trade_partner != 0) { + clif_tradestart(sd, 2); // person is in another trade + return; + } + + if (!pc_can_give_items(sd) || !pc_can_give_items(target_sd)) //check if both GMs are allowed to trade + { + clif_displaymessage(sd->fd, msg_txt(246)); + clif_tradestart(sd, 2); // GM is not allowed to trade + return; + } + + // Players can not request trade from far away, unless they are allowed to use @trade. + if (!pc_can_use_command(sd, "trade", COMMAND_ATCOMMAND) && + (sd->bl.m != target_sd->bl.m || !check_distance_bl(&sd->bl, &target_sd->bl, TRADE_DISTANCE))) { + clif_tradestart(sd, 0); // too far + return ; + } + + target_sd->trade_partner = sd->status.account_id; + sd->trade_partner = target_sd->status.account_id; + clif_traderequest(target_sd, sd->status.name); +} + +/*========================================== + * Reply to a trade-request. + * Type values: + * 0: Char is too far + * 1: Character does not exist + * 2: Trade failed + * 3: Accept + * 4: Cancel + * Weird enough, the client should only send 3/4 + * and the server is the one that can reply 0~2 + *------------------------------------------*/ +void trade_tradeack(struct map_session_data *sd, int type) +{ + struct map_session_data *tsd; + nullpo_retv(sd); + + if (sd->state.trading || !sd->trade_partner) + return; //Already trading or no partner set. + + if ((tsd = map_id2sd(sd->trade_partner)) == NULL) { + clif_tradestart(sd, 1); // character does not exist + sd->trade_partner=0; + return; + } + + if (tsd->state.trading || tsd->trade_partner != sd->bl.id) + { + clif_tradestart(sd, 2); + sd->trade_partner=0; + return; //Already trading or wrong partner. + } + + if (type == 4) { // Cancel + clif_tradestart(tsd, type); + clif_tradestart(sd, type); + sd->state.deal_locked = 0; + sd->trade_partner = 0; + tsd->state.deal_locked = 0; + tsd->trade_partner = 0; + return; + } + + if (type != 3) + return; //If client didn't send accept, it's a broken packet? + + // Players can not request trade from far away, unless they are allowed to use @trade. + // Check here as well since the original character could had warped. + if (!pc_can_use_command(sd, "trade", COMMAND_ATCOMMAND) && + (sd->bl.m != tsd->bl.m || !check_distance_bl(&sd->bl, &tsd->bl, TRADE_DISTANCE))) { + clif_tradestart(sd, 0); // too far + sd->trade_partner=0; + tsd->trade_partner = 0; + return; + } + + //Check if you can start trade. + if (sd->npc_id || sd->state.vending || sd->state.buyingstore || sd->state.storage_flag || + tsd->npc_id || tsd->state.vending || tsd->state.buyingstore || tsd->state.storage_flag) + { //Fail + clif_tradestart(sd, 2); + clif_tradestart(tsd, 2); + sd->state.deal_locked = 0; + sd->trade_partner = 0; + tsd->state.deal_locked = 0; + tsd->trade_partner = 0; + return; + } + + //Initiate trade + sd->state.trading = 1; + tsd->state.trading = 1; + memset(&sd->deal, 0, sizeof(sd->deal)); + memset(&tsd->deal, 0, sizeof(tsd->deal)); + clif_tradestart(tsd, type); + clif_tradestart(sd, type); +} + +/*========================================== + * Check here hacker for duplicate item in trade + * normal client refuse to have 2 same types of item (except equipment) in same trade window + * normal client authorise only no equiped item and only from inventory + *------------------------------------------*/ +int impossible_trade_check(struct map_session_data *sd) +{ + struct item inventory[MAX_INVENTORY]; + char message_to_gm[200]; + int i, index; + + nullpo_retr(1, sd); + + if(sd->deal.zeny > sd->status.zeny) + { + pc_setglobalreg(sd,"ZENY_HACKER",1); + return -1; + } + + // get inventory of player + memcpy(&inventory, &sd->status.inventory, sizeof(struct item) * MAX_INVENTORY); + + // remove this part: arrows can be trade and equiped + // re-added! [celest] + // remove equiped items (they can not be trade) + for (i = 0; i < MAX_INVENTORY; i++) + if (inventory[i].nameid > 0 && inventory[i].equip && !(inventory[i].equip & EQP_AMMO)) + memset(&inventory[i], 0, sizeof(struct item)); + + // check items in player inventory + for(i = 0; i < 10; i++) { + if (!sd->deal.item[i].amount) + continue; + index = sd->deal.item[i].index; + if (inventory[index].amount < sd->deal.item[i].amount) + { // if more than the player have -> hack + sprintf(message_to_gm, msg_txt(538), sd->status.name, sd->status.account_id); // Hack on trade: character '%s' (account: %d) try to trade more items that he has. + intif_wis_message_to_gm(wisp_server_name, PC_PERM_RECEIVE_HACK_INFO, message_to_gm); + sprintf(message_to_gm, msg_txt(539), inventory[index].amount, inventory[index].nameid, sd->deal.item[i].amount); // This player has %d of a kind of item (id: %d), and try to trade %d of them. + intif_wis_message_to_gm(wisp_server_name, PC_PERM_RECEIVE_HACK_INFO, message_to_gm); + // if we block people + if (battle_config.ban_hack_trade < 0) { + chrif_char_ask_name(-1, sd->status.name, 1, 0, 0, 0, 0, 0, 0); // type: 1 - block + set_eof(sd->fd); // forced to disconnect because of the hack + // message about the ban + strcpy(message_to_gm, msg_txt(540)); // This player has been definitively blocked. + // if we ban people + } else if (battle_config.ban_hack_trade > 0) { + chrif_char_ask_name(-1, sd->status.name, 2, 0, 0, 0, 0, battle_config.ban_hack_trade, 0); // type: 2 - ban (year, month, day, hour, minute, second) + set_eof(sd->fd); // forced to disconnect because of the hack + // message about the ban + sprintf(message_to_gm, msg_txt(507), battle_config.ban_hack_trade); // This player has been banned for %d minute(s). + } else + // message about the ban + strcpy(message_to_gm, msg_txt(508)); // This player hasn't been banned (Ban option is disabled). + + intif_wis_message_to_gm(wisp_server_name, PC_PERM_RECEIVE_HACK_INFO, message_to_gm); + return 1; + } + inventory[index].amount -= sd->deal.item[i].amount; // remove item from inventory + } + return 0; +} + +/*========================================== + * Checks if trade is possible (against zeny limits, inventory limits, etc) + *------------------------------------------*/ +int trade_check(struct map_session_data *sd, struct map_session_data *tsd) +{ + struct item inventory[MAX_INVENTORY]; + struct item inventory2[MAX_INVENTORY]; + struct item_data *data; + int trade_i, i, n; + short amount; + + // check zenys value against hackers (Zeny was already checked on time of adding, but you never know when you lost some zeny since then. + if(sd->deal.zeny > sd->status.zeny || (tsd->status.zeny > MAX_ZENY - sd->deal.zeny)) + return 0; + if(tsd->deal.zeny > tsd->status.zeny || (sd->status.zeny > MAX_ZENY - tsd->deal.zeny)) + return 0; + + // get inventory of player + memcpy(&inventory, &sd->status.inventory, sizeof(struct item) * MAX_INVENTORY); + memcpy(&inventory2, &tsd->status.inventory, sizeof(struct item) * MAX_INVENTORY); + + // check free slot in both inventory + for(trade_i = 0; trade_i < 10; trade_i++) { + amount = sd->deal.item[trade_i].amount; + if (amount) { + n = sd->deal.item[trade_i].index; + if (amount > inventory[n].amount) + return 0; //qty Exploit? + + data = itemdb_search(inventory[n].nameid); + i = MAX_INVENTORY; + if (itemdb_isstackable2(data)) { //Stackable item. + for(i = 0; i < MAX_INVENTORY; i++) + if (inventory2[i].nameid == inventory[n].nameid && + inventory2[i].card[0] == inventory[n].card[0] && inventory2[i].card[1] == inventory[n].card[1] && + inventory2[i].card[2] == inventory[n].card[2] && inventory2[i].card[3] == inventory[n].card[3]) { + if (inventory2[i].amount + amount > MAX_AMOUNT) + return 0; + inventory2[i].amount += amount; + inventory[n].amount -= amount; + break; + } + } + + if (i == MAX_INVENTORY) {// look for an empty slot. + for(i = 0; i < MAX_INVENTORY && inventory2[i].nameid; i++); + if (i == MAX_INVENTORY) + return 0; + memcpy(&inventory2[i], &inventory[n], sizeof(struct item)); + inventory2[i].amount = amount; + inventory[n].amount -= amount; + } + } + amount = tsd->deal.item[trade_i].amount; + if (!amount) + continue; + n = tsd->deal.item[trade_i].index; + if (amount > inventory2[n].amount) + return 0; + // search if it's possible to add item (for full inventory) + data = itemdb_search(inventory2[n].nameid); + i = MAX_INVENTORY; + if (itemdb_isstackable2(data)) { + for(i = 0; i < MAX_INVENTORY; i++) + if (inventory[i].nameid == inventory2[n].nameid && + inventory[i].card[0] == inventory2[n].card[0] && inventory[i].card[1] == inventory2[n].card[1] && + inventory[i].card[2] == inventory2[n].card[2] && inventory[i].card[3] == inventory2[n].card[3]) { + if (inventory[i].amount + amount > MAX_AMOUNT) + return 0; + inventory[i].amount += amount; + inventory2[n].amount -= amount; + break; + } + } + if (i == MAX_INVENTORY) { + for(i = 0; i < MAX_INVENTORY && inventory[i].nameid; i++); + if (i == MAX_INVENTORY) + return 0; + memcpy(&inventory[i], &inventory2[n], sizeof(struct item)); + inventory[i].amount = amount; + inventory2[n].amount -= amount; + } + } + + return 1; +} + +/*========================================== + * Adds an item/qty to the trade window + *------------------------------------------*/ +void trade_tradeadditem(struct map_session_data *sd, short index, short amount) +{ + struct map_session_data *target_sd; + struct item *item; + int trade_i, trade_weight; + int src_lv, dst_lv; + + nullpo_retv(sd); + if( !sd->state.trading || sd->state.deal_locked > 0 ) + return; //Can't add stuff. + + if( (target_sd = map_id2sd(sd->trade_partner)) == NULL ) + { + trade_tradecancel(sd); + return; + } + + if( amount == 0 ) + { //Why do this.. ~.~ just send an ack, the item won't display on the trade window. + clif_tradeitemok(sd, index, 0); + return; + } + + index -= 2; // 0 is for zeny, 1 is unknown. Gravity, go figure... + + //Item checks... + if( index < 0 || index >= MAX_INVENTORY ) + return; + if( amount < 0 || amount > sd->status.inventory[index].amount ) + return; + + item = &sd->status.inventory[index]; + src_lv = pc_get_group_level(sd); + dst_lv = pc_get_group_level(target_sd); + if( !itemdb_cantrade(item, src_lv, dst_lv) && //Can't trade + (pc_get_partner(sd) != target_sd || !itemdb_canpartnertrade(item, src_lv, dst_lv)) ) //Can't partner-trade + { + clif_displaymessage (sd->fd, msg_txt(260)); + clif_tradeitemok(sd, index+2, 1); + return; + } + + if( item->expire_time ) + { // Rental System + clif_displaymessage (sd->fd, msg_txt(260)); + clif_tradeitemok(sd, index+2, 1); + return; + } + + //Locate a trade position + ARR_FIND( 0, 10, trade_i, sd->deal.item[trade_i].index == index || sd->deal.item[trade_i].amount == 0 ); + if( trade_i == 10 ) //No space left + { + clif_tradeitemok(sd, index+2, 1); + return; + } + + trade_weight = sd->inventory_data[index]->weight * amount; + if( target_sd->weight + sd->deal.weight + trade_weight > target_sd->max_weight ) + { //fail to add item -- the player was over weighted. + clif_tradeitemok(sd, index+2, 1); + return; + } + + if( sd->deal.item[trade_i].index == index ) + { //The same item as before is being readjusted. + if( sd->deal.item[trade_i].amount + amount > sd->status.inventory[index].amount ) + { //packet deal exploit check + amount = sd->status.inventory[index].amount - sd->deal.item[trade_i].amount; + trade_weight = sd->inventory_data[index]->weight * amount; + } + sd->deal.item[trade_i].amount += amount; + } + else + { //New deal item + sd->deal.item[trade_i].index = index; + sd->deal.item[trade_i].amount = amount; + } + sd->deal.weight += trade_weight; + + clif_tradeitemok(sd, index+2, 0); // Return the index as it was received + clif_tradeadditem(sd, target_sd, index+2, amount); +} + +/*========================================== + * Adds the specified amount of zeny to the trade window + *------------------------------------------*/ +void trade_tradeaddzeny(struct map_session_data* sd, int amount) +{ + struct map_session_data* target_sd; + nullpo_retv(sd); + + if( !sd->state.trading || sd->state.deal_locked > 0 ) + return; //Can't add stuff. + + if( (target_sd = map_id2sd(sd->trade_partner)) == NULL ) + { + trade_tradecancel(sd); + return; + } + + if( amount < 0 || amount > sd->status.zeny || amount > MAX_ZENY - target_sd->status.zeny ) + { // invalid values, no appropriate packet for it => abort + trade_tradecancel(sd); + return; + } + + sd->deal.zeny = amount; + clif_tradeadditem(sd, target_sd, 0, amount); +} + +/*========================================== + * 'Ok' button on the trade window is pressed. + *------------------------------------------*/ +void trade_tradeok(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + + if(sd->state.deal_locked || !sd->state.trading) + return; + + if ((target_sd = map_id2sd(sd->trade_partner)) == NULL) { + trade_tradecancel(sd); + return; + } + sd->state.deal_locked = 1; + clif_tradeitemok(sd, 0, 0); + clif_tradedeal_lock(sd, 0); + clif_tradedeal_lock(target_sd, 1); +} + +/*========================================== + * 'Cancel' is pressed. (or trade was force-cancelled by the code) + *------------------------------------------*/ +void trade_tradecancel(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + target_sd = map_id2sd(sd->trade_partner); + + if(!sd->state.trading) + { // Not trade acepted + if( target_sd ) { + target_sd->trade_partner = 0; + clif_tradecancelled(target_sd); + } + sd->trade_partner = 0; + clif_tradecancelled(sd); + return; + } + + for(trade_i = 0; trade_i < 10; trade_i++) { // give items back (only virtual) + if (!sd->deal.item[trade_i].amount) + continue; + clif_additem(sd, sd->deal.item[trade_i].index, sd->deal.item[trade_i].amount, 0); + sd->deal.item[trade_i].index = 0; + sd->deal.item[trade_i].amount = 0; + } + if (sd->deal.zeny) { + clif_updatestatus(sd, SP_ZENY); + sd->deal.zeny = 0; + } + + sd->state.deal_locked = 0; + sd->state.trading = 0; + sd->trade_partner = 0; + clif_tradecancelled(sd); + + if (!target_sd) + return; + + for(trade_i = 0; trade_i < 10; trade_i++) { // give items back (only virtual) + if (!target_sd->deal.item[trade_i].amount) + continue; + clif_additem(target_sd, target_sd->deal.item[trade_i].index, target_sd->deal.item[trade_i].amount, 0); + target_sd->deal.item[trade_i].index = 0; + target_sd->deal.item[trade_i].amount = 0; + } + + if (target_sd->deal.zeny) { + clif_updatestatus(target_sd, SP_ZENY); + target_sd->deal.zeny = 0; + } + target_sd->state.deal_locked = 0; + target_sd->trade_partner = 0; + target_sd->state.trading = 0; + clif_tradecancelled(target_sd); +} + +/*========================================== + * lock sd and tsd trade data, execute the trade, clear, then save players + *------------------------------------------*/ +void trade_tradecommit(struct map_session_data *sd) +{ + struct map_session_data *tsd; + int trade_i; + int flag; + + if (!sd->state.trading || !sd->state.deal_locked) //Locked should be 1 (pressed ok) before you can press trade. + return; + + if ((tsd = map_id2sd(sd->trade_partner)) == NULL) { + trade_tradecancel(sd); + return; + } + + sd->state.deal_locked = 2; + + if (tsd->state.deal_locked < 2) + return; //Not yet time for trading. + + //Now is a good time (to save on resources) to check that the trade can indeed be made and it's not exploitable. + // check exploit (trade more items that you have) + if (impossible_trade_check(sd)) { + trade_tradecancel(sd); + return; + } + // check exploit (trade more items that you have) + if (impossible_trade_check(tsd)) { + trade_tradecancel(tsd); + return; + } + // check for full inventory (can not add traded items) + if (!trade_check(sd,tsd)) { // check the both players + trade_tradecancel(sd); + return; + } + + // trade is accepted and correct. + for( trade_i = 0; trade_i < 10; trade_i++ ) + { + int n; + if (sd->deal.item[trade_i].amount) + { + n = sd->deal.item[trade_i].index; + + flag = pc_additem(tsd, &sd->status.inventory[n], sd->deal.item[trade_i].amount,LOG_TYPE_TRADE); + if (flag == 0) + pc_delitem(sd, n, sd->deal.item[trade_i].amount, 1, 6, LOG_TYPE_TRADE); + else + clif_additem(sd, n, sd->deal.item[trade_i].amount, 0); + sd->deal.item[trade_i].index = 0; + sd->deal.item[trade_i].amount = 0; + } + if (tsd->deal.item[trade_i].amount) + { + n = tsd->deal.item[trade_i].index; + + flag = pc_additem(sd, &tsd->status.inventory[n], tsd->deal.item[trade_i].amount,LOG_TYPE_TRADE); + if (flag == 0) + pc_delitem(tsd, n, tsd->deal.item[trade_i].amount, 1, 6, LOG_TYPE_TRADE); + else + clif_additem(tsd, n, tsd->deal.item[trade_i].amount, 0); + tsd->deal.item[trade_i].index = 0; + tsd->deal.item[trade_i].amount = 0; + } + } + + if( sd->deal.zeny ) { + pc_payzeny(sd ,sd->deal.zeny, LOG_TYPE_TRADE, tsd); + pc_getzeny(tsd,sd->deal.zeny,LOG_TYPE_TRADE, sd); + sd->deal.zeny = 0; + + } + if ( tsd->deal.zeny) { + pc_payzeny(tsd,tsd->deal.zeny,LOG_TYPE_TRADE, sd); + pc_getzeny(sd ,tsd->deal.zeny,LOG_TYPE_TRADE, tsd); + tsd->deal.zeny = 0; + } + + sd->state.deal_locked = 0; + sd->trade_partner = 0; + sd->state.trading = 0; + + tsd->state.deal_locked = 0; + tsd->trade_partner = 0; + tsd->state.trading = 0; + + clif_tradecompleted(sd, 0); + clif_tradecompleted(tsd, 0); + + // save both player to avoid crash: they always have no advantage/disadvantage between the 2 players + if (save_settings&1) + { + chrif_save(sd,0); + chrif_save(tsd,0); + } +} diff --git a/src/map/trade.h b/src/map/trade.h new file mode 100644 index 000000000..6bb39936e --- /dev/null +++ b/src/map/trade.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _TRADE_H_ +#define _TRADE_H_ + +//#include "map.h" +struct map_session_data; + +void trade_traderequest(struct map_session_data *sd, struct map_session_data *target_sd); +void trade_tradeack(struct map_session_data *sd,int type); +void trade_tradeadditem(struct map_session_data *sd,short index,short amount); +void trade_tradeaddzeny(struct map_session_data *sd,int amount); +void trade_tradeok(struct map_session_data *sd); +void trade_tradecancel(struct map_session_data *sd); +void trade_tradecommit(struct map_session_data *sd); + +#endif /* _TRADE_H_ */ diff --git a/src/map/unit.c b/src/map/unit.c new file mode 100644 index 000000000..0104e9a42 --- /dev/null +++ b/src/map/unit.c @@ -0,0 +1,2524 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/random.h" + +#include "map.h" +#include "path.h" +#include "pc.h" +#include "mob.h" +#include "pet.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "skill.h" +#include "clif.h" +#include "duel.h" +#include "npc.h" +#include "guild.h" +#include "status.h" +#include "unit.h" +#include "battle.h" +#include "battleground.h" +#include "chat.h" +#include "trade.h" +#include "vending.h" +#include "party.h" +#include "intif.h" +#include "chrif.h" +#include "script.h" +#include "storage.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +const short dirx[8]={0,-1,-1,-1,0,1,1,1}; +const short diry[8]={1,1,0,-1,-1,-1,0,1}; + +struct unit_data* unit_bl2ud(struct block_list *bl) +{ + if( bl == NULL) return NULL; + if( bl->type == BL_PC) return &((struct map_session_data*)bl)->ud; + if( bl->type == BL_MOB) return &((struct mob_data*)bl)->ud; + if( bl->type == BL_PET) return &((struct pet_data*)bl)->ud; + if( bl->type == BL_NPC) return &((struct npc_data*)bl)->ud; + if( bl->type == BL_HOM) return &((struct homun_data*)bl)->ud; + if( bl->type == BL_MER) return &((struct mercenary_data*)bl)->ud; + if( bl->type == BL_ELEM) return &((struct elemental_data*)bl)->ud; + return NULL; +} + +static int unit_attack_timer(int tid, unsigned int tick, int id, intptr_t data); +static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data); + +int unit_walktoxy_sub(struct block_list *bl) +{ + int i; + struct walkpath_data wpd; + struct unit_data *ud = NULL; + + nullpo_retr(1, bl); + ud = unit_bl2ud(bl); + if(ud == NULL) return 0; + + if( !path_search(&wpd,bl->m,bl->x,bl->y,ud->to_x,ud->to_y,ud->state.walk_easy,CELL_CHKNOPASS) ) + return 0; + + memcpy(&ud->walkpath,&wpd,sizeof(wpd)); + + if (ud->target_to && ud->chaserange>1) { + //Generally speaking, the walk path is already to an adjacent tile + //so we only need to shorten the path if the range is greater than 1. + uint8 dir; + //Trim the last part of the path to account for range, + //but always move at least one cell when requested to move. + for (i = ud->chaserange*10; i > 0 && ud->walkpath.path_len>1;) { + ud->walkpath.path_len--; + dir = ud->walkpath.path[ud->walkpath.path_len]; + if(dir&1) + i-=14; + else + i-=10; + ud->to_x -= dirx[dir]; + ud->to_y -= diry[dir]; + } + } + + ud->state.change_walk_target=0; + + if (bl->type == BL_PC) { + ((TBL_PC *)bl)->head_dir = 0; + clif_walkok((TBL_PC*)bl); + } + clif_move(ud); + + if(ud->walkpath.path_pos>=ud->walkpath.path_len) + i = -1; + else if(ud->walkpath.path[ud->walkpath.path_pos]&1) + i = status_get_speed(bl)*14/10; + else + i = status_get_speed(bl); + if( i > 0) + ud->walktimer = add_timer(gettick()+i,unit_walktoxy_timer,bl->id,i); + return 1; +} + +static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + int i; + int x,y,dx,dy; + uint8 dir; + struct block_list *bl; + struct map_session_data *sd; + struct mob_data *md; + struct unit_data *ud; + struct mercenary_data *mrd; + + bl = map_id2bl(id); + if(bl == NULL) + return 0; + sd = BL_CAST(BL_PC, bl); + md = BL_CAST(BL_MOB, bl); + mrd = BL_CAST(BL_MER, bl); + ud = unit_bl2ud(bl); + + if(ud == NULL) return 0; + + if(ud->walktimer != tid){ + ShowError("unit_walk_timer mismatch %d != %d\n",ud->walktimer,tid); + return 0; + } + ud->walktimer = INVALID_TIMER; + if (bl->prev == NULL) return 0; // Stop moved because it is missing from the block_list + + if(ud->walkpath.path_pos>=ud->walkpath.path_len) + return 0; + + if(ud->walkpath.path[ud->walkpath.path_pos]>=8) + return 1; + x = bl->x; + y = bl->y; + + dir = ud->walkpath.path[ud->walkpath.path_pos]; + ud->dir = dir; + + dx = dirx[(int)dir]; + dy = diry[(int)dir]; + + if(map_getcell(bl->m,x+dx,y+dy,CELL_CHKNOPASS)) + return unit_walktoxy_sub(bl); + + //Refresh view for all those we lose sight + map_foreachinmovearea(clif_outsight, bl, AREA_SIZE, dx, dy, sd?BL_ALL:BL_PC, bl); + + x += dx; + y += dy; + map_moveblock(bl, x, y, tick); + ud->walk_count++; //walked cell counter, to be used for walk-triggered skills. [Skotlex] + status_change_end(bl, SC_ROLLINGCUTTER, INVALID_TIMER); //If you move, you lose your counters. [malufett] + + if (bl->x != x || bl->y != y || ud->walktimer != INVALID_TIMER) + return 0; //map_moveblock has altered the object beyond what we expected (moved/warped it) + + ud->walktimer = -2; // arbitrary non-INVALID_TIMER value to make the clif code send walking packets + map_foreachinmovearea(clif_insight, bl, AREA_SIZE, -dx, -dy, sd?BL_ALL:BL_PC, bl); + ud->walktimer = INVALID_TIMER; + + if(sd) { + if( sd->touching_id ) + npc_touchnext_areanpc(sd,false); + if(map_getcell(bl->m,x,y,CELL_CHKNPC)) { + npc_touch_areanpc(sd,bl->m,x,y); + if (bl->prev == NULL) //Script could have warped char, abort remaining of the function. + return 0; + } else + sd->areanpc_id=0; + + if( sd->md && !check_distance_bl(&sd->bl, &sd->md->bl, MAX_MER_DISTANCE) ) + { + // mercenary should be warped after being 3 seconds too far from the master [greenbox] + if (sd->md->masterteleport_timer == 0) + { + sd->md->masterteleport_timer = gettick(); + } + else if (DIFF_TICK(gettick(), sd->md->masterteleport_timer) > 3000) + { + sd->md->masterteleport_timer = 0; + unit_warp( &sd->md->bl, sd->bl.m, sd->bl.x, sd->bl.y, CLR_TELEPORT ); + } + } + else if( sd->md ) + { + // reset the tick, he is not far anymore + sd->md->masterteleport_timer = 0; + } + } else if (md) { + if( map_getcell(bl->m,x,y,CELL_CHKNPC) ) { + if( npc_touch_areanpc2(md) ) return 0; // Warped + } else + md->areanpc_id = 0; + if (md->min_chase > md->db->range3) md->min_chase--; + //Walk skills are triggered regardless of target due to the idle-walk mob state. + //But avoid triggering on stop-walk calls. + if(tid != INVALID_TIMER && + !(ud->walk_count%WALK_SKILL_INTERVAL) && + mobskill_use(md, tick, -1)) + { + if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER)) + { //Skill used, abort walking + clif_fixpos(bl); //Fix position as walk has been cancelled. + return 0; + } + //Resend walk packet for proper Self Destruction display. + clif_move(ud); + } + } + else if( mrd && mrd->master ) + { + if (!check_distance_bl(&mrd->master->bl, bl, MAX_MER_DISTANCE)) + { + // mercenary should be warped after being 3 seconds too far from the master [greenbox] + if (mrd->masterteleport_timer == 0) + { + mrd->masterteleport_timer = gettick(); + } + else if (DIFF_TICK(gettick(), mrd->masterteleport_timer) > 3000) + { + mrd->masterteleport_timer = 0; + unit_warp( bl, mrd->master->bl.id, mrd->master->bl.x, mrd->master->bl.y, CLR_TELEPORT ); + } + } + else + { + mrd->masterteleport_timer = 0; + } + } + + if(tid == INVALID_TIMER) //A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant. + return 0; + + if(ud->state.change_walk_target) + return unit_walktoxy_sub(bl); + + ud->walkpath.path_pos++; + if(ud->walkpath.path_pos>=ud->walkpath.path_len) + i = -1; + else if(ud->walkpath.path[ud->walkpath.path_pos]&1) + i = status_get_speed(bl)*14/10; + else + i = status_get_speed(bl); + + if(i > 0) { + ud->walktimer = add_timer(tick+i,unit_walktoxy_timer,id,i); + if( md && DIFF_TICK(tick,md->dmgtick) < 3000 )//not required not damaged recently + clif_move(ud); + } else if(ud->state.running) { + //Keep trying to run. + if ( !(unit_run(bl) || unit_wugdash(bl,sd)) ) + ud->state.running = 0; + } + else if (ud->target_to) { + //Update target trajectory. + struct block_list *tbl = map_id2bl(ud->target_to); + if (!tbl || !status_check_visibility(bl, tbl)) { //Cancel chase. + ud->to_x = bl->x; + ud->to_y = bl->y; + if (tbl && bl->type == BL_MOB && mob_warpchase((TBL_MOB*)bl, tbl) ) + return 0; + ud->target_to = 0; + return 0; + } + if (tbl->m == bl->m && check_distance_bl(bl, tbl, ud->chaserange)) + { //Reached destination. + if (ud->state.attack_continue) + { //Aegis uses one before every attack, we should + //only need this one for syncing purposes. [Skotlex] + ud->target_to = 0; + clif_fixpos(bl); + unit_attack(bl, tbl->id, ud->state.attack_continue); + } + } else { //Update chase-path + unit_walktobl(bl, tbl, ud->chaserange, ud->state.walk_easy|(ud->state.attack_continue?2:0)); + return 0; + } + } + else { //Stopped walking. Update to_x and to_y to current location [Skotlex] + ud->to_x = bl->x; + ud->to_y = bl->y; + } + return 0; +} + +static int unit_delay_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl = map_id2bl(id); + + if (!bl || bl->prev == NULL) + return 0; + unit_walktoxy(bl, (short)((data>>16)&0xffff), (short)(data&0xffff), 0); + return 1; +} + +//flag parameter: +//&1 -> 1/0 = easy/hard +//&2 -> force walking +//&4 -> Delay walking if the reason you can't walk is the canwalk delay +int unit_walktoxy( struct block_list *bl, short x, short y, int flag) +{ + struct unit_data* ud = NULL; + struct status_change* sc = NULL; +#ifdef OFFICIAL_WALKPATH + struct walkpath_data wpd; +#endif + nullpo_ret(bl); + + ud = unit_bl2ud(bl); + + if( ud == NULL) return 0; + +#ifdef OFFICIAL_WALKPATH + if( path_search(&wpd, bl->m, bl->x, bl->y, x, y, flag&1, CELL_CHKNOPASS) // Check if there is an obstacle between + && wpd.path_len > 14 ) // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett] + return 0; +#endif + + if (flag&4 && DIFF_TICK(ud->canmove_tick, gettick()) > 0 && + DIFF_TICK(ud->canmove_tick, gettick()) < 2000) + { // Delay walking command. [Skotlex] + add_timer(ud->canmove_tick+1, unit_delay_walktoxy_timer, bl->id, (x<<16)|(y&0xFFFF)); + return 1; + } + + if(!(flag&2) && (!(status_get_mode(bl)&MD_CANMOVE) || !unit_can_move(bl))) + return 0; + + ud->state.walk_easy = flag&1; + ud->to_x = x; + ud->to_y = y; + unit_set_target(ud, 0); + + sc = status_get_sc(bl); + if (sc && sc->data[SC_CONFUSION]) //Randomize the target position + map_random_dir(bl, &ud->to_x, &ud->to_y); + + if(ud->walktimer != INVALID_TIMER) { + // When you come to the center of the grid because the change of destination while you're walking right now + // Call a function from a timer unit_walktoxy_sub + ud->state.change_walk_target = 1; + return 1; + } + + if(ud->attacktimer != INVALID_TIMER) { + delete_timer( ud->attacktimer, unit_attack_timer ); + ud->attacktimer = INVALID_TIMER; + } + + return unit_walktoxy_sub(bl); +} + +//To set Mob's CHASE/FOLLOW states (shouldn't be done if there's no path to reach) +static inline void set_mobstate(struct block_list* bl, int flag) +{ + struct mob_data* md = BL_CAST(BL_MOB,bl); + + if( md && flag ) + md->state.skillstate = md->state.aggressive ? MSS_FOLLOW : MSS_RUSH; +} + +static int unit_walktobl_sub(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl = map_id2bl(id); + struct unit_data *ud = bl?unit_bl2ud(bl):NULL; + + if (ud && ud->walktimer == INVALID_TIMER && ud->target == data) + { + if (DIFF_TICK(ud->canmove_tick, tick) > 0) //Keep waiting? + add_timer(ud->canmove_tick+1, unit_walktobl_sub, id, data); + else if (unit_can_move(bl)) + { + if (unit_walktoxy_sub(bl)) + set_mobstate(bl, ud->state.attack_continue); + } + } + return 0; +} + +// Chases a tbl. If the flag&1, use hard-path seek, +// if flag&2, start attacking upon arrival within range, otherwise just walk to that character. +int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int flag) +{ + struct unit_data *ud = NULL; + struct status_change *sc = NULL; + + nullpo_ret(bl); + nullpo_ret(tbl); + + ud = unit_bl2ud(bl); + if( ud == NULL) return 0; + + if (!(status_get_mode(bl)&MD_CANMOVE)) + return 0; + + if (!unit_can_reach_bl(bl, tbl, distance_bl(bl, tbl)+1, flag&1, &ud->to_x, &ud->to_y)) { + ud->to_x = bl->x; + ud->to_y = bl->y; + ud->target_to = 0; + return 0; + } + + ud->state.walk_easy = flag&1; + ud->target_to = tbl->id; + ud->chaserange = range; //Note that if flag&2, this SHOULD be attack-range + ud->state.attack_continue = flag&2?1:0; //Chase to attack. + unit_set_target(ud, 0); + + sc = status_get_sc(bl); + if (sc && sc->data[SC_CONFUSION]) //Randomize the target position + map_random_dir(bl, &ud->to_x, &ud->to_y); + + if(ud->walktimer != INVALID_TIMER) { + ud->state.change_walk_target = 1; + set_mobstate(bl, flag&2); + return 1; + } + + if(DIFF_TICK(ud->canmove_tick, gettick()) > 0) + { //Can't move, wait a bit before invoking the movement. + add_timer(ud->canmove_tick+1, unit_walktobl_sub, bl->id, ud->target); + return 1; + } + + if(!unit_can_move(bl)) + return 0; + + if(ud->attacktimer != INVALID_TIMER) { + delete_timer( ud->attacktimer, unit_attack_timer ); + ud->attacktimer = INVALID_TIMER; + } + + if (unit_walktoxy_sub(bl)) { + set_mobstate(bl, flag&2); + return 1; + } + return 0; +} + +int unit_run(struct block_list *bl) +{ + struct status_change *sc = status_get_sc(bl); + short to_x,to_y,dir_x,dir_y; + int lv; + int i; + + if (!(sc && sc->data[SC_RUN])) + return 0; + + if (!unit_can_move(bl)) { + status_change_end(bl, SC_RUN, INVALID_TIMER); + return 0; + } + + lv = sc->data[SC_RUN]->val1; + dir_x = dirx[sc->data[SC_RUN]->val2]; + dir_y = diry[sc->data[SC_RUN]->val2]; + + // determine destination cell + to_x = bl->x; + to_y = bl->y; + for(i=0;i<AREA_SIZE;i++) + { + if(!map_getcell(bl->m,to_x+dir_x,to_y+dir_y,CELL_CHKPASS)) + break; + + //if sprinting and there's a PC/Mob/NPC, block the path [Kevin] + if(sc->data[SC_RUN] && map_count_oncell(bl->m, to_x+dir_x, to_y+dir_y, BL_PC|BL_MOB|BL_NPC)) + break; + + to_x += dir_x; + to_y += dir_y; + } + + if( (to_x == bl->x && to_y == bl->y ) || (to_x == (bl->x+1) || to_y == (bl->y+1)) || (to_x == (bl->x-1) || to_y == (bl->y-1))) { + //If you can't run forward, you must be next to a wall, so bounce back. [Skotlex] + clif_status_change(bl, SI_BUMP, 1, 0, 0, 0, 0); + + //Set running to 0 beforehand so status_change_end knows not to enable spurt [Kevin] + unit_bl2ud(bl)->state.running = 0; + status_change_end(bl, SC_RUN, INVALID_TIMER); + + skill_blown(bl,bl,skill_get_blewcount(TK_RUN,lv),unit_getdir(bl),0); + clif_fixpos(bl); //Why is a clif_slide (skill_blown) AND a fixpos needed? Ask Aegis. + clif_status_change(bl, SI_BUMP, 0, 0, 0, 0, 0); + return 0; + } + if (unit_walktoxy(bl, to_x, to_y, 1)) + return 1; + //There must be an obstacle nearby. Attempt walking one cell at a time. + do { + to_x -= dir_x; + to_y -= dir_y; + } while (--i > 0 && !unit_walktoxy(bl, to_x, to_y, 1)); + if (i==0) { + // copy-paste from above + clif_status_change(bl, SI_BUMP, 1, 0, 0, 0, 0); + + //Set running to 0 beforehand so status_change_end knows not to enable spurt [Kevin] + unit_bl2ud(bl)->state.running = 0; + status_change_end(bl, SC_RUN, INVALID_TIMER); + + skill_blown(bl,bl,skill_get_blewcount(TK_RUN,lv),unit_getdir(bl),0); + clif_fixpos(bl); + clif_status_change(bl, SI_BUMP, 0, 0, 0, 0, 0); + return 0; + } + return 1; +} + +//Exclusive function to Wug Dash state. [Jobbie/3CeAM] +int unit_wugdash(struct block_list *bl, struct map_session_data *sd) { + struct status_change *sc = status_get_sc(bl); + short to_x,to_y,dir_x,dir_y; + int lv; + int i; + if (!(sc && sc->data[SC_WUGDASH])) + return 0; + + nullpo_ret(sd); + nullpo_ret(bl); + + if (!unit_can_move(bl)) { + status_change_end(bl,SC_WUGDASH,INVALID_TIMER); + return 0; + } + + lv = sc->data[SC_WUGDASH]->val1; + dir_x = dirx[sc->data[SC_WUGDASH]->val2]; + dir_y = diry[sc->data[SC_WUGDASH]->val2]; + + to_x = bl->x; + to_y = bl->y; + for(i=0;i<AREA_SIZE;i++) + { + if(!map_getcell(bl->m,to_x+dir_x,to_y+dir_y,CELL_CHKPASS)) + break; + + if(sc->data[SC_WUGDASH] && map_count_oncell(bl->m, to_x+dir_x, to_y+dir_y, BL_PC|BL_MOB|BL_NPC)) + break; + + to_x += dir_x; + to_y += dir_y; + } + + if(to_x == bl->x && to_y == bl->y) { + + unit_bl2ud(bl)->state.running = 0; + status_change_end(bl,SC_WUGDASH,INVALID_TIMER); + + if( sd ){ + clif_fixpos(bl); + skill_castend_damage_id(bl, &sd->bl, RA_WUGDASH, lv, gettick(), SD_LEVEL); + } + return 0; + } + if (unit_walktoxy(bl, to_x, to_y, 1)) + return 1; + do { + to_x -= dir_x; + to_y -= dir_y; + } while (--i > 0 && !unit_walktoxy(bl, to_x, to_y, 1)); + if (i==0) { + + unit_bl2ud(bl)->state.running = 0; + status_change_end(bl,SC_WUGDASH,INVALID_TIMER); + + if( sd ){ + clif_fixpos(bl); + skill_castend_damage_id(bl, &sd->bl, RA_WUGDASH, lv, gettick(), SD_LEVEL); + } + return 0; + } + return 1; +} + +//Makes bl attempt to run dist cells away from target. Uses hard-paths. +int unit_escape(struct block_list *bl, struct block_list *target, short dist) +{ + uint8 dir = map_calc_dir(target, bl->x, bl->y); + while( dist > 0 && map_getcell(bl->m, bl->x + dist*dirx[dir], bl->y + dist*diry[dir], CELL_CHKNOREACH) ) + dist--; + return ( dist > 0 && unit_walktoxy(bl, bl->x + dist*dirx[dir], bl->y + dist*diry[dir], 0) ); +} + +//Instant warp function. +int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath) +{ + short dx,dy; + uint8 dir; + struct unit_data *ud = NULL; + struct map_session_data *sd = NULL; + + nullpo_ret(bl); + sd = BL_CAST(BL_PC, bl); + ud = unit_bl2ud(bl); + + if( ud == NULL) return 0; + + unit_stop_walking(bl,1); + unit_stop_attack(bl); + + if( checkpath && (map_getcell(bl->m,dst_x,dst_y,CELL_CHKNOPASS) || !path_search(NULL,bl->m,bl->x,bl->y,dst_x,dst_y,easy,CELL_CHKNOREACH)) ) + return 0; // unreachable + + ud->to_x = dst_x; + ud->to_y = dst_y; + + dir = map_calc_dir(bl, dst_x, dst_y); + ud->dir = dir; + + dx = dst_x - bl->x; + dy = dst_y - bl->y; + + map_foreachinmovearea(clif_outsight, bl, AREA_SIZE, dx, dy, sd?BL_ALL:BL_PC, bl); + + map_moveblock(bl, dst_x, dst_y, gettick()); + + ud->walktimer = -2; // arbitrary non-INVALID_TIMER value to make the clif code send walking packets + map_foreachinmovearea(clif_insight, bl, AREA_SIZE, -dx, -dy, sd?BL_ALL:BL_PC, bl); + ud->walktimer = INVALID_TIMER; + + if(sd) { + if( sd->touching_id ) + npc_touchnext_areanpc(sd,false); + if(map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC)) { + npc_touch_areanpc(sd,bl->m,bl->x,bl->y); + if (bl->prev == NULL) //Script could have warped char, abort remaining of the function. + return 0; + } else + sd->areanpc_id=0; + + if( sd->status.pet_id > 0 && sd->pd && sd->pd->pet.intimate > 0 ) + { // Check if pet needs to be teleported. [Skotlex] + int flag = 0; + struct block_list* bl = &sd->pd->bl; + if( !checkpath && !path_search(NULL,bl->m,bl->x,bl->y,dst_x,dst_y,0,CELL_CHKNOPASS) ) + flag = 1; + else if (!check_distance_bl(&sd->bl, bl, AREA_SIZE)) //Too far, teleport. + flag = 2; + if( flag ) + { + unit_movepos(bl,sd->bl.x,sd->bl.y, 0, 0); + clif_slide(bl,bl->x,bl->y); + } + } + } + return 1; +} + +int unit_setdir(struct block_list *bl,unsigned char dir) +{ + struct unit_data *ud; + nullpo_ret(bl ); + ud = unit_bl2ud(bl); + if (!ud) return 0; + ud->dir = dir; + if (bl->type == BL_PC) + ((TBL_PC *)bl)->head_dir = 0; + clif_changed_dir(bl, AREA); + return 0; +} + +uint8 unit_getdir(struct block_list *bl) +{ + struct unit_data *ud; + nullpo_ret(bl ); + ud = unit_bl2ud(bl); + if (!ud) return 0; + return ud->dir; +} + +// Pushes a unit by given amount of cells into given direction. Only +// map cell restrictions are respected. +// flag: +// &1 Do not send position update packets. +int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag) +{ + if(count) { + struct map_session_data* sd; + struct skill_unit* su = NULL; + int nx, ny, result; + + sd = BL_CAST(BL_PC, bl); + su = BL_CAST(BL_SKILL, bl); + + result = path_blownpos(bl->m, bl->x, bl->y, dx, dy, count); + + nx = result>>16; + ny = result&0xffff; + + if(!su) { + unit_stop_walking(bl, 0); + } + + if( sd ) { + sd->ud.to_x = nx; + sd->ud.to_y = ny; + } + + dx = nx-bl->x; + dy = ny-bl->y; + + if(dx || dy) { + map_foreachinmovearea(clif_outsight, bl, AREA_SIZE, dx, dy, bl->type == BL_PC ? BL_ALL : BL_PC, bl); + + if(su) { + skill_unit_move_unit_group(su->group, bl->m, dx, dy); + } else { + map_moveblock(bl, nx, ny, gettick()); + } + + map_foreachinmovearea(clif_insight, bl, AREA_SIZE, -dx, -dy, bl->type == BL_PC ? BL_ALL : BL_PC, bl); + + if(!(flag&1)) { + clif_blown(bl); + } + + if(sd) { + if(sd->touching_id) { + npc_touchnext_areanpc(sd, false); + } + if(map_getcell(bl->m, bl->x, bl->y, CELL_CHKNPC)) { + npc_touch_areanpc(sd, bl->m, bl->x, bl->y); + } else { + sd->areanpc_id = 0; + } + } + } + + count = distance(dx, dy); + } + + return count; // return amount of knocked back cells +} + +//Warps a unit/ud to a given map/position. +//In the case of players, pc_setpos is used. +//it respects the no warp flags, so it is safe to call this without doing nowarpto/nowarp checks. +int unit_warp(struct block_list *bl,short m,short x,short y,clr_type type) +{ + struct unit_data *ud; + nullpo_ret(bl); + ud = unit_bl2ud(bl); + + if(bl->prev==NULL || !ud) + return 1; + + if (type == CLR_DEAD) + //Type 1 is invalid, since you shouldn't warp a bl with the "death" + //animation, it messes up with unit_remove_map! [Skotlex] + return 1; + + if( m<0 ) m=bl->m; + + switch (bl->type) { + case BL_MOB: + if (map[bl->m].flag.monster_noteleport && ((TBL_MOB*)bl)->master_id == 0) + return 1; + if (m != bl->m && map[m].flag.nobranch && battle_config.mob_warp&4 && !(((TBL_MOB *)bl)->master_id)) + return 1; + break; + case BL_PC: + if (map[bl->m].flag.noteleport) + return 1; + break; + } + + if (x<0 || y<0) + { //Random map position. + if (!map_search_freecell(NULL, m, &x, &y, -1, -1, 1)) { + ShowWarning("unit_warp failed. Unit Id:%d/Type:%d, target position map %d (%s) at [%d,%d]\n", bl->id, bl->type, m, map[m].name, x, y); + return 2; + + } + } else if (map_getcell(m,x,y,CELL_CHKNOREACH)) + { //Invalid target cell + ShowWarning("unit_warp: Specified non-walkable target cell: %d (%s) at [%d,%d]\n", m, map[m].name, x,y); + + if (!map_search_freecell(NULL, m, &x, &y, 4, 4, 1)) + { //Can't find a nearby cell + ShowWarning("unit_warp failed. Unit Id:%d/Type:%d, target position map %d (%s) at [%d,%d]\n", bl->id, bl->type, m, map[m].name, x, y); + return 2; + } + } + + if (bl->type == BL_PC) //Use pc_setpos + return pc_setpos((TBL_PC*)bl, map_id2index(m), x, y, type); + + if (!unit_remove_map(bl, type)) + return 3; + + if (bl->m != m && battle_config.clear_unit_onwarp && + battle_config.clear_unit_onwarp&bl->type) + skill_clear_unitgroup(bl); + + bl->x=ud->to_x=x; + bl->y=ud->to_y=y; + bl->m=m; + + map_addblock(bl); + clif_spawn(bl); + skill_unit_move(bl,gettick(),1); + + return 0; +} + +/*========================================== + * Caused the target object to stop moving. + * Flag values: + * &0x1: Issue a fixpos packet afterwards + * &0x2: Force the unit to move one cell if it hasn't yet + * &0x4: Enable moving to the next cell when unit was already half-way there + * (may cause on-touch/place side-effects, such as a scripted map change) + *------------------------------------------*/ +int unit_stop_walking(struct block_list *bl,int type) +{ + struct unit_data *ud; + const struct TimerData* td; + unsigned int tick; + nullpo_ret(bl); + + ud = unit_bl2ud(bl); + if(!ud || ud->walktimer == INVALID_TIMER) + return 0; + //NOTE: We are using timer data after deleting it because we know the + //delete_timer function does not messes with it. If the function's + //behaviour changes in the future, this code could break! + td = get_timer(ud->walktimer); + delete_timer(ud->walktimer, unit_walktoxy_timer); + ud->walktimer = INVALID_TIMER; + ud->state.change_walk_target = 0; + tick = gettick(); + if( (type&0x02 && !ud->walkpath.path_pos) //Force moving at least one cell. + || (type&0x04 && td && DIFF_TICK(td->tick, tick) <= td->data/2) //Enough time has passed to cover half-cell + ) { + ud->walkpath.path_len = ud->walkpath.path_pos+1; + unit_walktoxy_timer(INVALID_TIMER, tick, bl->id, ud->walkpath.path_pos); + } + + if(type&0x01) + clif_fixpos(bl); + + ud->walkpath.path_len = 0; + ud->walkpath.path_pos = 0; + ud->to_x = bl->x; + ud->to_y = bl->y; + if(bl->type == BL_PET && type&~0xff) + ud->canmove_tick = gettick() + (type>>8); + + //Readded, the check in unit_set_walkdelay means dmg during running won't fall through to this place in code [Kevin] + if (ud->state.running) { + status_change_end(bl, SC_RUN, INVALID_TIMER); + status_change_end(bl, SC_WUGDASH, INVALID_TIMER); + } + return 1; +} + +int unit_skilluse_id(struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv) +{ + return unit_skilluse_id2( + src, target_id, skill_id, skill_lv, + skill_castfix(src, skill_id, skill_lv), + skill_get_castcancel(skill_id) + ); +} + +int unit_is_walking(struct block_list *bl) +{ + struct unit_data *ud = unit_bl2ud(bl); + nullpo_ret(bl); + if(!ud) return 0; + return (ud->walktimer != INVALID_TIMER); +} + +/*========================================== + * Determines if the bl can move based on status changes. [Skotlex] + *------------------------------------------*/ +int unit_can_move(struct block_list *bl) +{ + struct map_session_data *sd; + struct unit_data *ud; + struct status_change *sc; + + nullpo_ret(bl); + ud = unit_bl2ud(bl); + sc = status_get_sc(bl); + sd = BL_CAST(BL_PC, bl); + + if (!ud) + return 0; + + if (ud->skilltimer != INVALID_TIMER && ud->skill_id != LG_EXEEDBREAK && (!sd || !pc_checkskill(sd, SA_FREECAST) || skill_get_inf2(ud->skill_id)&INF2_GUILD_SKILL)) + return 0; // prevent moving while casting + + if (DIFF_TICK(ud->canmove_tick, gettick()) > 0) + return 0; + + if (sd && ( + pc_issit(sd) || + sd->state.vending || + sd->state.buyingstore || + sd->state.blockedmove + )) + return 0; //Can't move + + if (sc) { + if( sc->cant.move /* status placed here are ones that cannot be cached by sc->cant.move for they depend on other conditions other than their availability */ + || (sc->data[SC_FEAR] && sc->data[SC_FEAR]->val2 > 0) + || (sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1) + || (sc->data[SC_DANCING] && sc->data[SC_DANCING]->val4 && ( + !sc->data[SC_LONGING] || + (sc->data[SC_DANCING]->val1&0xFFFF) == CG_MOONLIT || + (sc->data[SC_DANCING]->val1&0xFFFF) == CG_HERMODE + ) ) + || (sc->data[SC_CLOAKING] && //Need wall at level 1-2 + sc->data[SC_CLOAKING]->val1 < 3 && !(sc->data[SC_CLOAKING]->val4&1)) + ) + return 0; + + if (sc->opt1 > 0 && sc->opt1 != OPT1_STONEWAIT && sc->opt1 != OPT1_BURNING && (sc->opt1 != OPT1_CRYSTALIZE && bl->type != BL_MOB)) + return 0; + + if ((sc->option & OPTION_HIDE) && (!sd || pc_checkskill(sd, RG_TUNNELDRIVE) <= 0)) + return 0; + + } + return 1; +} + +/*========================================== + * Resume running after a walk delay + *------------------------------------------*/ + +int unit_resume_running(int tid, unsigned int tick, int id, intptr_t data) +{ + + struct unit_data *ud = (struct unit_data *)data; + TBL_PC * sd = map_id2sd(id); + + if(sd && pc_isridingwug(sd)) + clif_skill_nodamage(ud->bl,ud->bl,RA_WUGDASH,ud->skill_lv, + sc_start4(ud->bl,status_skill2sc(RA_WUGDASH),100,ud->skill_lv,unit_getdir(ud->bl),0,0,1)); + else + clif_skill_nodamage(ud->bl,ud->bl,TK_RUN,ud->skill_lv, + sc_start4(ud->bl,status_skill2sc(TK_RUN),100,ud->skill_lv,unit_getdir(ud->bl),0,0,0)); + + if (sd) clif_walkok(sd); + + return 0; + +} + + +/*========================================== + * Applies walk delay to character, considering that + * if type is 0, this is a damage induced delay: if previous delay is active, do not change it. + * if type is 1, this is a skill induced delay: walk-delay may only be increased, not decreased. + *------------------------------------------*/ +int unit_set_walkdelay(struct block_list *bl, unsigned int tick, int delay, int type) +{ + struct unit_data *ud = unit_bl2ud(bl); + if (delay <= 0 || !ud) return 0; + + /** + * MvP mobs have no walk delay + **/ + if( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) + return 0; + + if (type) { + if (DIFF_TICK(ud->canmove_tick, tick+delay) > 0) + return 0; + } else { + //Don't set walk delays when already trapped. + if (!unit_can_move(bl)) + return 0; + } + ud->canmove_tick = tick + delay; + if (ud->walktimer != INVALID_TIMER) + { //Stop walking, if chasing, readjust timers. + if (delay == 1) + { //Minimal delay (walk-delay) disabled. Just stop walking. + unit_stop_walking(bl,4); + } else { + //Resume running after can move again [Kevin] + if(ud->state.running) + { + add_timer(ud->canmove_tick, unit_resume_running, bl->id, (intptr_t)ud); + } + else + { + unit_stop_walking(bl,2|4); + if(ud->target) + add_timer(ud->canmove_tick+1, unit_walktobl_sub, bl->id, ud->target); + } + } + } + return 1; +} + +int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel) +{ + struct unit_data *ud; + struct status_data *tstatus; + struct status_change *sc; + struct map_session_data *sd = NULL; + struct block_list * target = NULL; + unsigned int tick = gettick(); + int temp = 0, range; + + nullpo_ret(src); + if(status_isdead(src)) + return 0; //Do not continue source is dead + + sd = BL_CAST(BL_PC, src); + ud = unit_bl2ud(src); + + if(ud == NULL) return 0; + sc = status_get_sc(src); + if (sc && !sc->count) + sc = NULL; //Unneeded + + //temp: used to signal combo-skills right now. + if (sc && sc->data[SC_COMBO] && (sc->data[SC_COMBO]->val1 == skill_id || + (sd?skill_check_condition_castbegin(sd,skill_id,skill_lv):0) )) { + if (sc->data[SC_COMBO]->val2) + target_id = sc->data[SC_COMBO]->val2; + else + target_id = ud->target; + + if( skill_get_inf(skill_id)&INF_SELF_SKILL && skill_get_nk(skill_id)&NK_NO_DAMAGE )// exploit fix + target_id = src->id; + temp = 1; + } else + if ( target_id == src->id && + skill_get_inf(skill_id)&INF_SELF_SKILL && + skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF ) + { + target_id = ud->target; //Auto-select target. [Skotlex] + temp = 1; + } + + if (sd) { + //Target_id checking. + if(skillnotok(skill_id, sd)) // [MouseJstr] + return 0; + + switch(skill_id) + { //Check for skills that auto-select target + case MO_CHAINCOMBO: + if (sc && sc->data[SC_BLADESTOP]){ + if ((target=map_id2bl(sc->data[SC_BLADESTOP]->val4)) == NULL) + return 0; + } + break; + case WE_MALE: + case WE_FEMALE: + if (!sd->status.partner_id) + return 0; + target = (struct block_list*)map_charid2sd(sd->status.partner_id); + if (!target) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + } + if (target) + target_id = target->id; + } + if (src->type==BL_HOM) + switch(skill_id) + { //Homun-auto-target skills. + case HLIF_HEAL: + case HLIF_AVOID: + case HAMI_DEFENCE: + case HAMI_CASTLE: + target = battle_get_master(src); + if (!target) return 0; + target_id = target->id; + } + + if( !target ) // choose default target + target = map_id2bl(target_id); + + if( !target || src->m != target->m || !src->prev || !target->prev ) + return 0; + + if( battle_config.ksprotection && sd && mob_ksprotected(src, target) ) + return 0; + + //Normally not needed because clif.c checks for it, but the at/char/script commands don't! [Skotlex] + if(ud->skilltimer != INVALID_TIMER && skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST) + return 0; + + if(skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF && src->id == target_id) + return 0; + + if(!status_check_skilluse(src, target, skill_id, 0)) + return 0; + + tstatus = status_get_status_data(target); + // Record the status of the previous skill) + if(sd) { + switch(skill_id){ + case SA_CASTCANCEL: + if(ud->skill_id != skill_id){ + sd->skill_id_old = ud->skill_id; + sd->skill_lv_old = ud->skill_lv; + } + break; + case BD_ENCORE: + //Prevent using the dance skill if you no longer have the skill in your tree. + if(!sd->skill_id_dance || pc_checkskill(sd,sd->skill_id_dance)<=0){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + sd->skill_id_old = skill_id; + break; + case BD_LULLABY: + case BD_RICHMANKIM: + case BD_ETERNALCHAOS: + case BD_DRUMBATTLEFIELD: + case BD_RINGNIBELUNGEN: + case BD_ROKISWEIL: + case BD_INTOABYSS: + case BD_SIEGFRIED: + case CG_MOONLIT: + if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 1) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case WL_WHITEIMPRISON: + if( battle_check_target(src,target,BCT_SELF|BCT_ENEMY) < 0 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); + return 0; + } + break; + case MG_FIREBOLT: + case MG_LIGHTNINGBOLT: + case MG_COLDBOLT: + sd->skill_id_old = skill_id; + sd->skill_lv_old = skill_lv; + break; + } + if (!skill_check_condition_castbegin(sd, skill_id, skill_lv)) + return 0; + } + + if( src->type == BL_MOB ) + switch( skill_id ) + { + case NPC_SUMMONSLAVE: + case NPC_SUMMONMONSTER: + case AL_TELEPORT: + if( ((TBL_MOB*)src)->master_id && ((TBL_MOB*)src)->special_state.ai ) + return 0; + } + + if (src->type == BL_NPC) // NPC-objects can override cast distance + range = AREA_SIZE; // Maximum visible distance before NPC goes out of sight + else + range = skill_get_range2(src, skill_id, skill_lv); // Skill cast distance from database + + //Check range when not using skill on yourself or is a combo-skill during attack + //(these are supposed to always have the same range as your attack) + if( src->id != target_id && (!temp || ud->attacktimer == INVALID_TIMER) ) { + if( skill_get_state(ud->skill_id) == ST_MOVE_ENABLE ) { + if( !unit_can_reach_bl(src, target, range + 1, 1, NULL, NULL) ) + return 0; // Walk-path check failed. + } else if( src->type == BL_MER && skill_id == MA_REMOVETRAP ) { + if( !battle_check_range(battle_get_master(src), target, range + 1) ) + return 0; // Aegis calc remove trap based on Master position, ignoring mercenary O.O + } else if( !battle_check_range(src, target, range + (skill_id == RG_CLOSECONFINE?0:2)) ) { + return 0; // Arrow-path check failed. + } + } + + if (!temp) //Stop attack on non-combo skills [Skotlex] + unit_stop_attack(src); + else if(ud->attacktimer != INVALID_TIMER) //Elsewise, delay current attack sequence + ud->attackabletime = tick + status_get_adelay(src); + + ud->state.skillcastcancel = castcancel; + + //temp: Used to signal force cast now. + temp = 0; + + switch(skill_id){ + case ALL_RESURRECTION: + if(battle_check_undead(tstatus->race,tstatus->def_ele)) { + temp = 1; + } else if (!status_isdead(target)) + return 0; //Can't cast on non-dead characters. + break; + case MO_FINGEROFFENSIVE: + if(sd) + casttime += casttime * min(skill_lv, sd->spiritball); + break; + case MO_EXTREMITYFIST: + if (sc && sc->data[SC_COMBO] && + (sc->data[SC_COMBO]->val1 == MO_COMBOFINISH || + sc->data[SC_COMBO]->val1 == CH_TIGERFIST || + sc->data[SC_COMBO]->val1 == CH_CHAINCRUSH)) + casttime = -1; + temp = 1; + break; + case SR_GATEOFHELL: + case SR_TIGERCANNON: + if (sc && sc->data[SC_COMBO] && + sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE) + casttime = -1; + temp = 1; + break; + case SA_SPELLBREAKER: + temp = 1; + break; + case ST_CHASEWALK: + if (sc && sc->data[SC_CHASEWALK]) + casttime = -1; + break; + case TK_RUN: + if (sc && sc->data[SC_RUN]) + casttime = -1; + break; + case HP_BASILICA: + if( sc && sc->data[SC_BASILICA] ) + casttime = -1; // No Casting time on basilica cancel + break; + case KN_CHARGEATK: + { + unsigned int k = (distance_bl(src,target)-1)/3; //+100% every 3 cells of distance + if( k > 2 ) k = 2; // ...but hard-limited to 300%. + casttime += casttime * k; + } + break; + case GD_EMERGENCYCALL: //Emergency Call double cast when the user has learned Leap [Daegaladh] + if( sd && pc_checkskill(sd,TK_HIGHJUMP) ) + casttime *= 2; + break; + case RA_WUGDASH: + if (sc && sc->data[SC_WUGDASH]) + casttime = -1; + break; + case EL_WIND_SLASH: + case EL_HURRICANE: + case EL_TYPOON_MIS: + case EL_STONE_HAMMER: + case EL_ROCK_CRUSHER: + case EL_STONE_RAIN: + case EL_ICE_NEEDLE: + case EL_WATER_SCREW: + case EL_TIDAL_WEAPON: + if( src->type == BL_ELEM ){ + sd = BL_CAST(BL_PC, battle_get_master(src)); + if( sd && sd->skill_id_old == SO_EL_ACTION ){ + casttime = -1; + sd->skill_id_old = 0; + } + } + break; + } + + // moved here to prevent Suffragium from ending if skill fails +#ifndef RENEWAL_CAST + if (!(skill_get_castnodex(skill_id, skill_lv)&2)) + casttime = skill_castfix_sc(src, casttime); +#else + casttime = skill_vfcastfix(src, casttime, skill_id, skill_lv); +#endif + + if (src->type == BL_NPC) { // NPC-objects do not have cast time + casttime = 0; + } + + if(!ud->state.running) //need TK_RUN or WUGDASH handler to be done before that, see bugreport:6026 + unit_stop_walking(src,1);// eventhough this is not how official works but this will do the trick. bugreport:6829 + // in official this is triggered even if no cast time. + clif_skillcasting(src, src->id, target_id, 0,0, skill_id, skill_get_ele(skill_id, skill_lv), casttime); + if( casttime > 0 || temp ) + { + if (sd && target->type == BL_MOB) + { + TBL_MOB *md = (TBL_MOB*)target; + mobskill_event(md, src, tick, -1); //Cast targetted skill event. + if (tstatus->mode&(MD_CASTSENSOR_IDLE|MD_CASTSENSOR_CHASE) && + battle_check_target(target, src, BCT_ENEMY) > 0) + { + switch (md->state.skillstate) { + case MSS_RUSH: + case MSS_FOLLOW: + if (!(tstatus->mode&MD_CASTSENSOR_CHASE)) + break; + md->target_id = src->id; + md->state.aggressive = (tstatus->mode&MD_ANGRY)?1:0; + md->min_chase = md->db->range3; + break; + case MSS_IDLE: + case MSS_WALK: + if (!(tstatus->mode&MD_CASTSENSOR_IDLE)) + break; + md->target_id = src->id; + md->state.aggressive = (tstatus->mode&MD_ANGRY)?1:0; + md->min_chase = md->db->range3; + break; + } + } + } + } + + if( casttime <= 0 ) + ud->state.skillcastcancel = 0; + + if( !sd || sd->skillitem != skill_id || skill_get_cast(skill_id,skill_lv) ) + ud->canact_tick = tick + casttime + 100; + if( sd ) + { + switch( skill_id ) + { + case CG_ARROWVULCAN: + sd->canequip_tick = tick + casttime; + break; + } + } + ud->skilltarget = target_id; + ud->skillx = 0; + ud->skilly = 0; + ud->skill_id = skill_id; + ud->skill_lv = skill_lv; + + if( sc ) { + /** + * why the if else chain: these 3 status do not stack, so its efficient that way. + **/ + if( sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4&4) && skill_id != AS_CLOAKING ) { + status_change_end(src, SC_CLOAKING, INVALID_TIMER); + if (!src->prev) return 0; //Warped away! + } else if( sc->data[SC_CLOAKINGEXCEED] && !(sc->data[SC_CLOAKINGEXCEED]->val4&4) && skill_id != GC_CLOAKINGEXCEED ) { + status_change_end(src,SC_CLOAKINGEXCEED, INVALID_TIMER); + if (!src->prev) return 0; + } + } + + + if( casttime > 0 ) + { + ud->skilltimer = add_timer( tick+casttime, skill_castend_id, src->id, 0 ); + if( sd && (pc_checkskill(sd,SA_FREECAST) > 0 || skill_id == LG_EXEEDBREAK) ) + status_calc_bl(&sd->bl, SCB_SPEED); + } + else + skill_castend_id(ud->skilltimer,tick,src->id,0); + + return 1; +} + +int unit_skilluse_pos(struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv) +{ + return unit_skilluse_pos2( + src, skill_x, skill_y, skill_id, skill_lv, + skill_castfix(src, skill_id, skill_lv), + skill_get_castcancel(skill_id) + ); +} + +int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel) +{ + struct map_session_data *sd = NULL; + struct unit_data *ud = NULL; + struct status_change *sc; + struct block_list bl; + unsigned int tick = gettick(); + int range; + + nullpo_ret(src); + + if (!src->prev) return 0; // not on the map + if(status_isdead(src)) return 0; + + sd = BL_CAST(BL_PC, src); + ud = unit_bl2ud(src); + if(ud == NULL) return 0; + + if(ud->skilltimer != INVALID_TIMER) //Normally not needed since clif.c checks for it, but at/char/script commands don't! [Skotlex] + return 0; + + sc = status_get_sc(src); + if (sc && !sc->count) + sc = NULL; + + if( sd ) + { + if( skillnotok(skill_id, sd) || !skill_check_condition_castbegin(sd, skill_id, skill_lv) ) + return 0; + /** + * "WHY IS IT HEREE": pneuma cannot be cancelled past this point, the client displays the animation even, + * if we cancel it from nodamage_id, so it has to be here for it to not display the animation. + **/ + if( skill_id == AL_PNEUMA && map_getcell(src->m, skill_x, skill_y, CELL_CHKLANDPROTECTOR) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + + if (!status_check_skilluse(src, NULL, skill_id, 0)) + return 0; + + if( map_getcell(src->m, skill_x, skill_y, CELL_CHKWALL) ) + {// can't cast ground targeted spells on wall cells + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + + /* Check range and obstacle */ + bl.type = BL_NUL; + bl.m = src->m; + bl.x = skill_x; + bl.y = skill_y; + + if (src->type == BL_NPC) // NPC-objects can override cast distance + range = AREA_SIZE; // Maximum visible distance before NPC goes out of sight + else + range = skill_get_range2(src, skill_id, skill_lv); // Skill cast distance from database + + if( skill_get_state(ud->skill_id) == ST_MOVE_ENABLE ) + { + if( !unit_can_reach_bl(src, &bl, range + 1, 1, NULL, NULL) ) + return 0; //Walk-path check failed. + } + else if( !battle_check_range(src, &bl, range + 1) ) + return 0; //Arrow-path check failed. + + unit_stop_attack(src); + + // moved here to prevent Suffragium from ending if skill fails +#ifndef RENEWAL_CAST + if (!(skill_get_castnodex(skill_id, skill_lv)&2)) + casttime = skill_castfix_sc(src, casttime); +#else + casttime = skill_vfcastfix(src, casttime, skill_id, skill_lv ); +#endif + + if (src->type == BL_NPC) { // NPC-objects do not have cast time + casttime = 0; + } + + ud->state.skillcastcancel = castcancel&&casttime>0?1:0; + if( !sd || sd->skillitem != skill_id || skill_get_cast(skill_id,skill_lv) ) + ud->canact_tick = tick + casttime + 100; +// if( sd ) +// { +// switch( skill_id ) +// { +// case ????: +// sd->canequip_tick = tick + casttime; +// } +// } + ud->skill_id = skill_id; + ud->skill_lv = skill_lv; + ud->skillx = skill_x; + ud->skilly = skill_y; + ud->skilltarget = 0; + + if( sc ) { + /** + * why the if else chain: these 3 status do not stack, so its efficient that way. + **/ + if (sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4&4)) { + status_change_end(src, SC_CLOAKING, INVALID_TIMER); + if (!src->prev) return 0; //Warped away! + } else if (sc->data[SC_CLOAKINGEXCEED] && !(sc->data[SC_CLOAKINGEXCEED]->val4&4)) { + status_change_end(src, SC_CLOAKINGEXCEED, INVALID_TIMER); + if (!src->prev) return 0; + } + } + + unit_stop_walking(src,1); + // in official this is triggered even if no cast time. + clif_skillcasting(src, src->id, 0, skill_x, skill_y, skill_id, skill_get_ele(skill_id, skill_lv), casttime); + if( casttime > 0 ) + { + ud->skilltimer = add_timer( tick+casttime, skill_castend_pos, src->id, 0 ); + if( (sd && pc_checkskill(sd,SA_FREECAST) > 0) || skill_id == LG_EXEEDBREAK) + status_calc_bl(&sd->bl, SCB_SPEED); + } + else + { + ud->skilltimer = INVALID_TIMER; + skill_castend_pos(ud->skilltimer,tick,src->id,0); + } + return 1; +} + +/*======================================== + * update a block's attack target + *----------------------------------------*/ +int unit_set_target(struct unit_data* ud, int target_id) +{ + struct unit_data * ux; + struct block_list* target; + + nullpo_ret(ud); + + if( ud->target != target_id ) { + if( ud->target && (target = map_id2bl(ud->target)) && (ux = unit_bl2ud(target)) && ux->target_count > 0 ) + ux->target_count --; + if( target_id && (target = map_id2bl(target_id)) && (ux = unit_bl2ud(target)) ) + ux->target_count ++; + } + + ud->target = target_id; + return 0; +} + +int unit_stop_attack(struct block_list *bl) +{ + struct unit_data *ud = unit_bl2ud(bl); + nullpo_ret(bl); + + if(!ud || ud->attacktimer == INVALID_TIMER) + return 0; + + delete_timer( ud->attacktimer, unit_attack_timer ); + ud->attacktimer = INVALID_TIMER; + unit_set_target(ud, 0); + return 0; +} + +//Means current target is unattackable. For now only unlocks mobs. +int unit_unattackable(struct block_list *bl) +{ + struct unit_data *ud = unit_bl2ud(bl); + if (ud) { + ud->state.attack_continue = 0; + unit_set_target(ud, 0); + } + + if(bl->type == BL_MOB) + mob_unlocktarget((struct mob_data*)bl, gettick()) ; + else if(bl->type == BL_PET) + pet_unlocktarget((struct pet_data*)bl); + return 0; +} + +/*========================================== + * Attack request + * If type is an ongoing attack + *------------------------------------------*/ +int unit_attack(struct block_list *src,int target_id,int continuous) +{ + struct block_list *target; + struct unit_data *ud; + + nullpo_ret(ud = unit_bl2ud(src)); + + target = map_id2bl(target_id); + if( target==NULL || status_isdead(target) ) { + unit_unattackable(src); + return 1; + } + + if( src->type == BL_PC ) { + TBL_PC* sd = (TBL_PC*)src; + if( target->type == BL_NPC ) { // monster npcs [Valaris] + npc_click(sd,(TBL_NPC*)target); // submitted by leinsirk10 [Celest] + return 0; + } + if( pc_is90overweight(sd) || pc_isridingwug(sd) ) { // overweight or mounted on warg - stop attacking + unit_stop_attack(src); + return 0; + } + } + if( battle_check_target(src,target,BCT_ENEMY) <= 0 || !status_check_skilluse(src, target, 0, 0) ) { + unit_unattackable(src); + return 1; + } + ud->state.attack_continue = continuous; + unit_set_target(ud, target_id); + + if (continuous) //If you're to attack continously, set to auto-case character + ud->chaserange = status_get_range(src); + + //Just change target/type. [Skotlex] + if(ud->attacktimer != INVALID_TIMER) + return 0; + + //Set Mob's ANGRY/BERSERK states. + if(src->type == BL_MOB) + ((TBL_MOB*)src)->state.skillstate = ((TBL_MOB*)src)->state.aggressive?MSS_ANGRY:MSS_BERSERK; + + if(DIFF_TICK(ud->attackabletime, gettick()) > 0) + //Do attack next time it is possible. [Skotlex] + ud->attacktimer=add_timer(ud->attackabletime,unit_attack_timer,src->id,0); + else //Attack NOW. + unit_attack_timer(INVALID_TIMER, gettick(), src->id, 0); + + return 0; +} + +//Cancels an ongoing combo, resets attackable time and restarts the +//attack timer to resume attacking after amotion time. [Skotlex] +int unit_cancel_combo(struct block_list *bl) +{ + struct unit_data *ud; + + if (!status_change_end(bl, SC_COMBO, INVALID_TIMER)) + return 0; //Combo wasn't active. + + ud = unit_bl2ud(bl); + nullpo_ret(ud); + + ud->attackabletime = gettick() + status_get_amotion(bl); + + if (ud->attacktimer == INVALID_TIMER) + return 1; //Nothing more to do. + + delete_timer(ud->attacktimer, unit_attack_timer); + ud->attacktimer=add_timer(ud->attackabletime,unit_attack_timer,bl->id,0); + return 1; +} +/*========================================== + * + *------------------------------------------*/ +bool unit_can_reach_pos(struct block_list *bl,int x,int y, int easy) +{ + nullpo_retr(false, bl); + + if (bl->x == x && bl->y == y) //Same place + return true; + + return path_search(NULL,bl->m,bl->x,bl->y,x,y,easy,CELL_CHKNOREACH); +} + +/*========================================== + * + *------------------------------------------*/ +bool unit_can_reach_bl(struct block_list *bl,struct block_list *tbl, int range, int easy, short *x, short *y) +{ + int i; + short dx,dy; + nullpo_retr(false, bl); + nullpo_retr(false, tbl); + + if( bl->m != tbl->m) + return false; + + if( bl->x==tbl->x && bl->y==tbl->y ) + return true; + + if(range>0 && !check_distance_bl(bl, tbl, range)) + return false; + + // It judges whether it can adjoin or not. + dx=tbl->x - bl->x; + dy=tbl->y - bl->y; + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + + if (map_getcell(tbl->m,tbl->x-dx,tbl->y-dy,CELL_CHKNOPASS)) + { //Look for a suitable cell to place in. + for(i=0;i<9 && map_getcell(tbl->m,tbl->x-dirx[i],tbl->y-diry[i],CELL_CHKNOPASS);i++); + if (i==9) return false; //No valid cells. + dx = dirx[i]; + dy = diry[i]; + } + + if (x) *x = tbl->x-dx; + if (y) *y = tbl->y-dy; + return path_search(NULL,bl->m,bl->x,bl->y,tbl->x-dx,tbl->y-dy,easy,CELL_CHKNOREACH); +} +/*========================================== + * Calculates position of Pet/Mercenary/Homunculus/Elemental + *------------------------------------------*/ +int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir) +{ + int dx, dy, x, y, i, k; + struct unit_data *ud = unit_bl2ud(bl); + nullpo_ret(ud); + + if(dir > 7) + return 1; + + ud->to_x = tx; + ud->to_y = ty; + + // 2 cells from Master Position + dx = -dirx[dir] * 2; + dy = -diry[dir] * 2; + x = tx + dx; + y = ty + dy; + + if( !unit_can_reach_pos(bl, x, y, 0) ) + { + if( dx > 0 ) x--; else if( dx < 0 ) x++; + if( dy > 0 ) y--; else if( dy < 0 ) y++; + if( !unit_can_reach_pos(bl, x, y, 0) ) + { + for( i = 0; i < 12; i++ ) + { + k = rnd()%8; // Pick a Random Dir + dx = -dirx[k] * 2; + dy = -diry[k] * 2; + x = tx + dx; + y = ty + dy; + if( unit_can_reach_pos(bl, x, y, 0) ) + break; + else + { + if( dx > 0 ) x--; else if( dx < 0 ) x++; + if( dy > 0 ) y--; else if( dy < 0 ) y++; + if( unit_can_reach_pos(bl, x, y, 0) ) + break; + } + } + if( i == 12 ) + { + x = tx; y = tx; // Exactly Master Position + if( !unit_can_reach_pos(bl, x, y, 0) ) + return 1; + } + } + } + ud->to_x = x; + ud->to_y = y; + + return 0; +} + +/*========================================== + * Continuous Attack (function timer) + *------------------------------------------*/ +static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int tick) +{ + struct block_list *target; + struct unit_data *ud; + struct status_data *sstatus; + struct map_session_data *sd = NULL; + struct mob_data *md = NULL; + int range; + + if( (ud=unit_bl2ud(src))==NULL ) + return 0; + if( ud->attacktimer != tid ) + { + ShowError("unit_attack_timer %d != %d\n",ud->attacktimer,tid); + return 0; + } + + sd = BL_CAST(BL_PC, src); + md = BL_CAST(BL_MOB, src); + ud->attacktimer = INVALID_TIMER; + target=map_id2bl(ud->target); + + if( src == NULL || src->prev == NULL || target==NULL || target->prev == NULL ) + return 0; + + if( status_isdead(src) || status_isdead(target) || + battle_check_target(src,target,BCT_ENEMY) <= 0 || !status_check_skilluse(src, target, 0, 0) +#ifdef OFFICIAL_WALKPATH + || !path_search_long(NULL, src->m, src->x, src->y, target->x, target->y, CELL_CHKWALL) +#endif + ) + return 0; // can't attack under these conditions + + if( src->m != target->m ) + { + if( src->type == BL_MOB && mob_warpchase((TBL_MOB*)src, target) ) + return 1; // Follow up. + return 0; + } + + if( ud->skilltimer != INVALID_TIMER && !(sd && pc_checkskill(sd,SA_FREECAST) > 0) ) + return 0; // can't attack while casting + + if( !battle_config.sdelay_attack_enable && DIFF_TICK(ud->canact_tick,tick) > 0 && !(sd && pc_checkskill(sd,SA_FREECAST) > 0) ) + { // attacking when under cast delay has restrictions: + if( tid == INVALID_TIMER ) + { //requested attack. + if(sd) clif_skill_fail(sd,1,USESKILL_FAIL_SKILLINTERVAL,0); + return 0; + } + //Otherwise, we are in a combo-attack, delay this until your canact time is over. [Skotlex] + if( ud->state.attack_continue ) + { + if( DIFF_TICK(ud->canact_tick, ud->attackabletime) > 0 ) + ud->attackabletime = ud->canact_tick; + ud->attacktimer=add_timer(ud->attackabletime,unit_attack_timer,src->id,0); + } + return 1; + } + + sstatus = status_get_status_data(src); + range = sstatus->rhw.range + 1; + + if( unit_is_walking(target) ) + range++; //Extra range when chasing + if( !check_distance_bl(src,target,range) ) { //Chase if required. + if(sd) + clif_movetoattack(sd,target); + else if(ud->state.attack_continue) + unit_walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); + return 1; + } + if( !battle_check_range(src,target,range) ) { + //Within range, but no direct line of attack + if( ud->state.attack_continue ) { + if(ud->chaserange > 2) ud->chaserange-=2; + unit_walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); + } + return 1; + } + //Sync packet only for players. + //Non-players use the sync packet on the walk timer. [Skotlex] + if (tid == INVALID_TIMER && sd) clif_fixpos(src); + + if( DIFF_TICK(ud->attackabletime,tick) <= 0 ) + { + if (battle_config.attack_direction_change && (src->type&battle_config.attack_direction_change)) { + ud->dir = map_calc_dir(src, target->x,target->y ); + } + if(ud->walktimer != INVALID_TIMER) + unit_stop_walking(src,1); + if(md) { + if (mobskill_use(md,tick,-1)) + return 1; + if (sstatus->mode&MD_ASSIST && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME) + { // Link monsters nearby [Skotlex] + md->last_linktime = tick; + map_foreachinrange(mob_linksearch, src, md->db->range2, BL_MOB, md->class_, target, tick); + } + } + if(src->type == BL_PET && pet_attackskill((TBL_PET*)src, target->id)) + return 1; + + map_freeblock_lock(); + ud->attacktarget_lv = battle_weapon_attack(src,target,tick,0); + + if(sd && sd->status.pet_id > 0 && sd->pd && battle_config.pet_attack_support) + pet_target_check(sd,target,0); + map_freeblock_unlock(); + /** + * Applied when you're unable to attack (e.g. out of ammo) + * We should stop here otherwise timer keeps on and this happens endlessly + **/ + if( ud->attacktarget_lv == ATK_NONE ) + return 1; + + ud->attackabletime = tick + sstatus->adelay; +// You can't move if you can't attack neither. + if (src->type&battle_config.attack_walk_delay) + unit_set_walkdelay(src, tick, sstatus->amotion, 1); + } + + if(ud->state.attack_continue) + ud->attacktimer = add_timer(ud->attackabletime,unit_attack_timer,src->id,0); + + return 1; +} + +static int unit_attack_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl; + bl = map_id2bl(id); + if(bl && unit_attack_timer_sub(bl, tid, tick) == 0) + unit_unattackable(bl); + return 0; +} + +/*========================================== + * Cancels an ongoing skill cast. + * flag&1: Cast-Cancel invoked. + * flag&2: Cancel only if skill is cancellable. + *------------------------------------------*/ +int unit_skillcastcancel(struct block_list *bl,int type) +{ + struct map_session_data *sd = NULL; + struct unit_data *ud = unit_bl2ud( bl); + unsigned int tick=gettick(); + int ret=0, skill; + + nullpo_ret(bl); + if (!ud || ud->skilltimer == INVALID_TIMER) + return 0; //Nothing to cancel. + + sd = BL_CAST(BL_PC, bl); + + if (type&2) { + //See if it can be cancelled. + if (!ud->state.skillcastcancel) + return 0; + + if (sd && (sd->special_state.no_castcancel2 || + ((sd->sc.data[SC_UNLIMITEDHUMMINGVOICE] || sd->special_state.no_castcancel) && !map_flag_gvg(bl->m) && !map[bl->m].flag.battleground))) //fixed flags being read the wrong way around [blackhole89] + return 0; + } + + ud->canact_tick = tick; + + if(type&1 && sd) + skill = sd->skill_id_old; + else + skill = ud->skill_id; + + if (skill_get_inf(skill) & INF_GROUND_SKILL) + ret=delete_timer( ud->skilltimer, skill_castend_pos ); + else + ret=delete_timer( ud->skilltimer, skill_castend_id ); + if(ret<0) + ShowError("delete timer error : skill_id : %d\n",ret); + + ud->skilltimer = INVALID_TIMER; + + if( sd && pc_checkskill(sd,SA_FREECAST) > 0 ) + status_calc_bl(&sd->bl, SCB_SPEED); + + if( sd ) + { + switch( skill ) + { + case CG_ARROWVULCAN: + sd->canequip_tick = tick; + break; + } + } + + if(bl->type==BL_MOB) ((TBL_MOB*)bl)->skill_idx = -1; + + clif_skillcastcancel(bl); + return 1; +} + +// unit_data initialization process +void unit_dataset(struct block_list *bl) +{ + struct unit_data *ud; + nullpo_retv(ud = unit_bl2ud(bl)); + + memset( ud, 0, sizeof( struct unit_data) ); + ud->bl = bl; + ud->walktimer = INVALID_TIMER; + ud->skilltimer = INVALID_TIMER; + ud->attacktimer = INVALID_TIMER; + ud->attackabletime = + ud->canact_tick = + ud->canmove_tick = gettick(); +} + +/*========================================== + * Counts the number of units attacking 'bl' + *------------------------------------------*/ +int unit_counttargeted(struct block_list* bl) +{ + struct unit_data* ud; + if( bl && (ud = unit_bl2ud(bl)) ) + return ud->target_count; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int unit_fixdamage(struct block_list *src,struct block_list *target,unsigned int tick,int sdelay,int ddelay,int damage,int div,int type,int damage2) +{ + nullpo_ret(target); + + if(damage+damage2 <= 0) + return 0; + + return status_fix_damage(src,target,damage+damage2,clif_damage(target,target,tick,sdelay,ddelay,damage,div,type,damage2)); +} + +/*========================================== + * To change the size of the char (player or mob only) + *------------------------------------------*/ +int unit_changeviewsize(struct block_list *bl,short size) +{ + nullpo_ret(bl); + + size=(size<0)?-1:(size>0)?1:0; + + if(bl->type == BL_PC) { + ((TBL_PC*)bl)->state.size=size; + } else if(bl->type == BL_MOB) { + ((TBL_MOB*)bl)->special_state.size=size; + } else + return 0; + if(size!=0) + clif_specialeffect(bl,421+size, AREA); + return 0; +} + +/*========================================== + * Removes a bl/ud from the map. + * Returns 1 on success. 0 if it couldn't be removed or the bl was free'd + * if clrtype is 1 (death), appropiate cleanup is performed. + * Otherwise it is assumed bl is being warped. + * On-Kill specific stuff is not performed here, look at status_damage for that. + *------------------------------------------*/ +int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file, int line, const char* func) +{ + struct unit_data *ud = unit_bl2ud(bl); + struct status_change *sc = status_get_sc(bl); + nullpo_ret(ud); + + if(bl->prev == NULL) + return 0; //Already removed? + + map_freeblock_lock(); + + unit_set_target(ud, 0); + + if (ud->walktimer != INVALID_TIMER) + unit_stop_walking(bl,0); + if (ud->attacktimer != INVALID_TIMER) + unit_stop_attack(bl); + if (ud->skilltimer != INVALID_TIMER) + unit_skillcastcancel(bl,0); + +// Do not reset can-act delay. [Skotlex] + ud->attackabletime = ud->canmove_tick /*= ud->canact_tick*/ = gettick(); + if(sc && sc->count ) { //map-change/warp dispells. + status_change_end(bl, SC_BLADESTOP, INVALID_TIMER); + status_change_end(bl, SC_BASILICA, INVALID_TIMER); + status_change_end(bl, SC_ANKLE, INVALID_TIMER); + status_change_end(bl, SC_TRICKDEAD, INVALID_TIMER); + status_change_end(bl, SC_BLADESTOP_WAIT, INVALID_TIMER); + status_change_end(bl, SC_RUN, INVALID_TIMER); + status_change_end(bl, SC_DANCING, INVALID_TIMER); + status_change_end(bl, SC_WARM, INVALID_TIMER); + status_change_end(bl, SC_DEVOTION, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE2, INVALID_TIMER); + status_change_end(bl, SC_CLOSECONFINE, INVALID_TIMER); + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + // Ensure the bl is a PC; if so, we'll handle the removal of cloaking and cloaking exceed later + if ( bl->type != BL_PC ) + { + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + status_change_end(bl, SC_CHASEWALK, INVALID_TIMER); + if (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) + status_change_end(bl, SC_GOSPEL, INVALID_TIMER); + status_change_end(bl, SC_CHANGE, INVALID_TIMER); + status_change_end(bl, SC_STOP, INVALID_TIMER); + status_change_end(bl, SC_WUGDASH, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + status_change_end(bl, SC__MANHOLE, INVALID_TIMER); + status_change_end(bl, SC_VACUUM_EXTREME, INVALID_TIMER); + status_change_end(bl, SC_CURSEDCIRCLE_ATKER, INVALID_TIMER); //callme before warp + } + + if (bl->type&(BL_CHAR|BL_PET)) { + skill_unit_move(bl,gettick(),4); + skill_cleartimerskill(bl); + } + + switch( bl->type ) { + case BL_PC: { + struct map_session_data *sd = (struct map_session_data*)bl; + + //Leave/reject all invitations. + if(sd->chatID) + chat_leavechat(sd,0); + if(sd->trade_partner) + trade_tradecancel(sd); + buyingstore_close(sd); + searchstore_close(sd); + if(sd->state.storage_flag == 1) + storage_storage_quit(sd,0); + else if (sd->state.storage_flag == 2) + storage_guild_storage_quit(sd,0); + sd->state.storage_flag = 0; //Force close it when being warped. + if(sd->party_invite>0) + party_reply_invite(sd,sd->party_invite,0); + if(sd->guild_invite>0) + guild_reply_invite(sd,sd->guild_invite,0); + if(sd->guild_alliance>0) + guild_reply_reqalliance(sd,sd->guild_alliance_account,0); + if(sd->menuskill_id) + sd->menuskill_id = sd->menuskill_val = 0; + if( sd->touching_id ) + npc_touchnext_areanpc(sd,true); + + // Check if warping and not changing the map. + if ( sd->state.warping && !sd->state.changemap ) + { + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + + sd->npc_shopid = 0; + sd->adopt_invite = 0; + + if(sd->pvp_timer != INVALID_TIMER) { + delete_timer(sd->pvp_timer,pc_calc_pvprank_timer); + sd->pvp_timer = INVALID_TIMER; + sd->pvp_rank = 0; + } + if(sd->duel_group > 0) + duel_leave(sd->duel_group, sd); + + if(pc_issit(sd)) { + pc_setstand(sd); + skill_sit(sd,0); + } + party_send_dot_remove(sd);//minimap dot fix [Kevin] + guild_send_dot_remove(sd); + bg_send_dot_remove(sd); + + if( map[bl->m].users <= 0 || sd->state.debug_remove_map ) + {// this is only place where map users is decreased, if the mobs were removed too soon then this function was executed too many times [FlavioJS] + if( sd->debug_file == NULL || !(sd->state.debug_remove_map) ) + { + sd->debug_file = ""; + sd->debug_line = 0; + sd->debug_func = ""; + } + ShowDebug("unit_remove_map: unexpected state when removing player AID/CID:%d/%d" + " (active=%d connect_new=%d rewarp=%d changemap=%d debug_remove_map=%d)" + " from map=%s (users=%d)." + " Previous call from %s:%d(%s), current call from %s:%d(%s)." + " Please report this!!!\n", + sd->status.account_id, sd->status.char_id, + sd->state.active, sd->state.connect_new, sd->state.rewarp, sd->state.changemap, sd->state.debug_remove_map, + map[bl->m].name, map[bl->m].users, + sd->debug_file, sd->debug_line, sd->debug_func, file, line, func); + } + else + if (--map[bl->m].users == 0 && battle_config.dynamic_mobs) //[Skotlex] + map_removemobs(bl->m); + if( !(sd->sc.option&OPTION_INVISIBLE) ) + {// decrement the number of active pvp players on the map + --map[bl->m].users_pvp; + } + if( map[bl->m].instance_id ) + { + instance[map[bl->m].instance_id].users--; + instance_check_idle(map[bl->m].instance_id); + } + sd->state.debug_remove_map = 1; // temporary state to track double remove_map's [FlavioJS] + sd->debug_file = file; + sd->debug_line = line; + sd->debug_func = func; + + break; + } + case BL_MOB: { + struct mob_data *md = (struct mob_data*)bl; + // Drop previous target mob_slave_keep_target: no. + if (!battle_config.mob_slave_keep_target) + md->target_id=0; + + md->attacked_id=0; + md->state.skillstate= MSS_IDLE; + + break; + } + case BL_PET: { + struct pet_data *pd = (struct pet_data*)bl; + if( pd->pet.intimate <= 0 && !(pd->msd && !pd->msd->state.active) ) + { //If logging out, this is deleted on unit_free + clif_clearunit_area(bl,clrtype); + map_delblock(bl); + unit_free(bl,CLR_OUTSIGHT); + map_freeblock_unlock(); + return 0; + } + + break; + } + case BL_HOM: { + struct homun_data *hd = (struct homun_data *)bl; + ud->canact_tick = ud->canmove_tick; //It appears HOM do reset the can-act tick. + if( !hd->homunculus.intimacy && !(hd->master && !hd->master->state.active) ) + { //If logging out, this is deleted on unit_free + clif_emotion(bl, E_SOB); + clif_clearunit_area(bl,clrtype); + map_delblock(bl); + unit_free(bl,CLR_OUTSIGHT); + map_freeblock_unlock(); + return 0; + } + break; + } + case BL_MER: { + struct mercenary_data *md = (struct mercenary_data *)bl; + ud->canact_tick = ud->canmove_tick; + if( mercenary_get_lifetime(md) <= 0 && !(md->master && !md->master->state.active) ) + { + clif_clearunit_area(bl,clrtype); + map_delblock(bl); + unit_free(bl,CLR_OUTSIGHT); + map_freeblock_unlock(); + return 0; + } + break; + } + case BL_ELEM: { + struct elemental_data *ed = (struct elemental_data *)bl; + ud->canact_tick = ud->canmove_tick; + if( elemental_get_lifetime(ed) <= 0 && !(ed->master && !ed->master->state.active) ) + { + clif_clearunit_area(bl,clrtype); + map_delblock(bl); + unit_free(bl,0); + map_freeblock_unlock(); + return 0; + } + break; + } + default: break;// do nothing + } + /** + * BL_MOB is handled by mob_dead unless the monster is not dead. + **/ + if( bl->type != BL_MOB || !status_isdead(bl) ) + clif_clearunit_area(bl,clrtype); + map_delblock(bl); + map_freeblock_unlock(); + return 1; +} + +void unit_remove_map_pc(struct map_session_data *sd, clr_type clrtype) +{ + unit_remove_map(&sd->bl,clrtype); + + if (clrtype == CLR_TELEPORT) clrtype = CLR_OUTSIGHT; //CLR_TELEPORT is the warp from logging out, but pets/homunc need to just 'vanish' instead of showing the warping out animation. + + if(sd->pd) + unit_remove_map(&sd->pd->bl, clrtype); + if(merc_is_hom_active(sd->hd)) + unit_remove_map(&sd->hd->bl, clrtype); + if(sd->md) + unit_remove_map(&sd->md->bl, clrtype); + if(sd->ed) + unit_remove_map(&sd->ed->bl, clrtype); +} + +void unit_free_pc(struct map_session_data *sd) +{ + if (sd->pd) unit_free(&sd->pd->bl,CLR_OUTSIGHT); + if (sd->hd) unit_free(&sd->hd->bl,CLR_OUTSIGHT); + if (sd->md) unit_free(&sd->md->bl,CLR_OUTSIGHT); + if (sd->ed) unit_free(&sd->ed->bl,CLR_OUTSIGHT); + unit_free(&sd->bl,CLR_TELEPORT); +} + +/*========================================== + * Function to free all related resources to the bl + * if unit is on map, it is removed using the clrtype specified + *------------------------------------------*/ +int unit_free(struct block_list *bl, clr_type clrtype) +{ + struct unit_data *ud = unit_bl2ud( bl ); + nullpo_ret(ud); + + map_freeblock_lock(); + if( bl->prev ) //Players are supposed to logout with a "warp" effect. + unit_remove_map(bl, clrtype); + + switch( bl->type ) + { + case BL_PC: + { + struct map_session_data *sd = (struct map_session_data*)bl; + int i; + + if( status_isdead(bl) ) + pc_setrestartvalue(sd,2); + + pc_delinvincibletimer(sd); + pc_delautobonus(sd,sd->autobonus,ARRAYLENGTH(sd->autobonus),false); + pc_delautobonus(sd,sd->autobonus2,ARRAYLENGTH(sd->autobonus2),false); + pc_delautobonus(sd,sd->autobonus3,ARRAYLENGTH(sd->autobonus3),false); + + if( sd->followtimer != INVALID_TIMER ) + pc_stop_following(sd); + + if( sd->duel_invite > 0 ) + duel_reject(sd->duel_invite, sd); + + // Notify friends that this char logged out. [Skotlex] + map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 0); + party_send_logout(sd); + guild_send_memberinfoshort(sd,0); + pc_cleareventtimer(sd); + pc_inventory_rental_clear(sd); + pc_delspiritball(sd,sd->spiritball,1); + for(i = 1; i < 5; i++) + pc_del_talisman(sd, sd->talisman[i], i); + + if( sd->reg ) { //Double logout already freed pointer fix... [Skotlex] + aFree(sd->reg); + sd->reg = NULL; + sd->reg_num = 0; + } + if( sd->regstr ) { + int i; + for( i = 0; i < sd->regstr_num; ++i ) + if( sd->regstr[i].data ) + aFree(sd->regstr[i].data); + aFree(sd->regstr); + sd->regstr = NULL; + sd->regstr_num = 0; + } + if( sd->st && sd->st->state != RUN ) {// free attached scripts that are waiting + script_free_state(sd->st); + sd->st = NULL; + sd->npc_id = 0; + } + if( sd->combos.count ) { + aFree(sd->combos.bonus); + aFree(sd->combos.id); + sd->combos.count = 0; + } + break; + } + case BL_PET: + { + struct pet_data *pd = (struct pet_data*)bl; + struct map_session_data *sd = pd->msd; + pet_hungry_timer_delete(pd); + if( pd->a_skill ) + { + aFree(pd->a_skill); + pd->a_skill = NULL; + } + if( pd->s_skill ) + { + if (pd->s_skill->timer != INVALID_TIMER) { + if (pd->s_skill->id) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + } + aFree(pd->s_skill); + pd->s_skill = NULL; + } + if( pd->recovery ) + { + if(pd->recovery->timer != INVALID_TIMER) + delete_timer(pd->recovery->timer, pet_recovery_timer); + aFree(pd->recovery); + pd->recovery = NULL; + } + if( pd->bonus ) + { + if (pd->bonus->timer != INVALID_TIMER) + delete_timer(pd->bonus->timer, pet_skill_bonus_timer); + aFree(pd->bonus); + pd->bonus = NULL; + } + if( pd->loot ) + { + pet_lootitem_drop(pd,sd); + if (pd->loot->item) + aFree(pd->loot->item); + aFree (pd->loot); + pd->loot = NULL; + } + if( pd->pet.intimate > 0 ) + intif_save_petdata(pd->pet.account_id,&pd->pet); + else + { //Remove pet. + intif_delete_petdata(pd->pet.pet_id); + if (sd) sd->status.pet_id = 0; + } + if( sd ) + sd->pd = NULL; + break; + } + case BL_MOB: + { + struct mob_data *md = (struct mob_data*)bl; + if( md->spawn_timer != INVALID_TIMER ) + { + delete_timer(md->spawn_timer,mob_delayspawn); + md->spawn_timer = INVALID_TIMER; + } + if( md->deletetimer != INVALID_TIMER ) + { + delete_timer(md->deletetimer,mob_timer_delete); + md->deletetimer = INVALID_TIMER; + } + if( md->lootitem ) + { + aFree(md->lootitem); + md->lootitem=NULL; + } + if( md->guardian_data ) + { + struct guild_castle* gc = md->guardian_data->castle; + if( md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS ) + { + gc->guardian[md->guardian_data->number].id = 0; + } + else + { + int i; + ARR_FIND(0, gc->temp_guardians_max, i, gc->temp_guardians[i] == md->bl.id); + if( i < gc->temp_guardians_max ) + gc->temp_guardians[i] = 0; + } + aFree(md->guardian_data); + md->guardian_data = NULL; + } + if( md->spawn ) + { + md->spawn->active--; + if( !md->spawn->state.dynamic ) + {// permanently remove the mob + if( --md->spawn->num == 0 ) + {// Last freed mob is responsible for deallocating the group's spawn data. + aFree(md->spawn); + md->spawn = NULL; + } + } + } + if( md->base_status) + { + aFree(md->base_status); + md->base_status = NULL; + } + if( mob_is_clone(md->class_) ) + mob_clone_delete(md); + if( md->tomb_nid ) + mvptomb_destroy(md); + break; + } + case BL_HOM: + { + struct homun_data *hd = (TBL_HOM*)bl; + struct map_session_data *sd = hd->master; + merc_hom_hungry_timer_delete(hd); + if( hd->homunculus.intimacy > 0 ) + merc_save(hd); + else + { + intif_homunculus_requestdelete(hd->homunculus.hom_id); + if( sd ) + sd->status.hom_id = 0; + } + if( sd ) + sd->hd = NULL; + break; + } + case BL_MER: + { + struct mercenary_data *md = (TBL_MER*)bl; + struct map_session_data *sd = md->master; + if( mercenary_get_lifetime(md) > 0 ) + mercenary_save(md); + else + { + intif_mercenary_delete(md->mercenary.mercenary_id); + if( sd ) + sd->status.mer_id = 0; + } + if( sd ) + sd->md = NULL; + + merc_contract_stop(md); + break; + } + case BL_ELEM: { + struct elemental_data *ed = (TBL_ELEM*)bl; + struct map_session_data *sd = ed->master; + if( elemental_get_lifetime(ed) > 0 ) + elemental_save(ed); + else { + intif_elemental_delete(ed->elemental.elemental_id); + if( sd ) + sd->status.ele_id = 0; + } + if( sd ) + sd->ed = NULL; + + elemental_summon_stop(ed); + break; + } + } + + skill_clear_unitgroup(bl); + status_change_clear(bl,1); + map_deliddb(bl); + if( bl->type != BL_PC ) //Players are handled by map_quit + map_freeblock(bl); + map_freeblock_unlock(); + return 0; +} + +int do_init_unit(void) +{ + add_timer_func_list(unit_attack_timer, "unit_attack_timer"); + add_timer_func_list(unit_walktoxy_timer,"unit_walktoxy_timer"); + add_timer_func_list(unit_walktobl_sub, "unit_walktobl_sub"); + add_timer_func_list(unit_delay_walktoxy_timer,"unit_delay_walktoxy_timer"); + return 0; +} + +int do_final_unit(void) +{ + // nothing to do + return 0; +} diff --git a/src/map/unit.h b/src/map/unit.h new file mode 100644 index 000000000..9d1c02a31 --- /dev/null +++ b/src/map/unit.h @@ -0,0 +1,145 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _UNIT_H_ +#define _UNIT_H_ + +//#include "map.h" +struct block_list; +struct unit_data; +struct map_session_data; + +#include "clif.h" // clr_type +#include "map.h" // struct block_list +#include "path.h" // struct walkpath_data +#include "skill.h" // struct skill_timerskill, struct skill_unit_group, struct skill_unit_group_tickset + +struct unit_data { + struct block_list *bl; + struct walkpath_data walkpath; + struct skill_timerskill *skilltimerskill[MAX_SKILLTIMERSKILL]; + struct skill_unit_group *skillunit[MAX_SKILLUNITGROUP]; + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; + short attacktarget_lv; + short to_x,to_y; + short skillx,skilly; + uint16 skill_id,skill_lv; + int skilltarget; + int skilltimer; + int target; + int target_to; + int attacktimer; + int walktimer; + int chaserange; + unsigned int attackabletime; + unsigned int canact_tick; + unsigned int canmove_tick; + uint8 dir; + unsigned char walk_count; + unsigned char target_count; + struct { + unsigned change_walk_target : 1 ; + unsigned skillcastcancel : 1 ; + unsigned attack_continue : 1 ; + unsigned walk_easy : 1 ; + unsigned running : 1; + unsigned speed_changed : 1; + } state; +}; + +struct view_data { +#ifdef __64BIT__ + unsigned int class_; +#endif + unsigned short +#ifndef __64BIT__ + class_, +#endif + weapon, + shield, //Or left-hand weapon. + robe, + head_top, + head_mid, + head_bottom, + hair_style, + hair_color, + cloth_color; + char sex; + unsigned dead_sit : 2; +}; + +// PC, MOB, PET に共通する処理を1つにまとめる計画 + +// 歩行開始 +// 戻り値は、0 ( 成功 ), 1 ( 失敗 ) +int unit_walktoxy( struct block_list *bl, short x, short y, int easy); +int unit_walktobl( struct block_list *bl, struct block_list *target, int range, int easy); +int unit_run(struct block_list *bl); +int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir); + +// 歩行停止 +// typeは以下の組み合わせ : +// 1: 位置情報の送信( この関数の後に位置情報を送信する場合は不要 ) +// 2: ダメージディレイ有り +// 4: 不明(MOBのみ?) +int unit_stop_walking(struct block_list *bl,int type); +int unit_can_move(struct block_list *bl); +int unit_is_walking(struct block_list *bl); +int unit_set_walkdelay(struct block_list *bl, unsigned int tick, int delay, int type); + +int unit_escape(struct block_list *bl, struct block_list *target, short dist); +// 位置の強制移動(吹き飛ばしなど) +int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath); +int unit_warp(struct block_list *bl, short map, short x, short y, clr_type type); +int unit_setdir(struct block_list *bl,unsigned char dir); +uint8 unit_getdir(struct block_list *bl); +int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag); + +// そこまで歩行でたどり着けるかの判定 +bool unit_can_reach_pos(struct block_list *bl,int x,int y,int easy); +bool unit_can_reach_bl(struct block_list *bl,struct block_list *tbl, int range, int easy, short *x, short *y); + +// 攻撃関連 +int unit_stop_attack(struct block_list *bl); +int unit_attack(struct block_list *src,int target_id,int continuous); +int unit_cancel_combo(struct block_list *bl); + +// スキル使用 +int unit_skilluse_id(struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv); +int unit_skilluse_pos(struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv); + +// スキル使用( 補正済みキャスト時間、キャンセル不可設定付き ) +int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel); +int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel); + +// 詠唱キャンセル +int unit_skillcastcancel(struct block_list *bl,int type); + +int unit_counttargeted(struct block_list *bl); +int unit_set_target(struct unit_data* ud, int target_id); + +// unit_data の初期化処理 +void unit_dataset(struct block_list *bl); + +int unit_fixdamage(struct block_list *src,struct block_list *target,unsigned int tick,int sdelay,int ddelay,int damage,int div,int type,int damage2); +// その他 +struct unit_data* unit_bl2ud(struct block_list *bl); +void unit_remove_map_pc(struct map_session_data *sd, clr_type clrtype); +void unit_free_pc(struct map_session_data *sd); +#define unit_remove_map(bl,clrtype) unit_remove_map_(bl,clrtype,__FILE__,__LINE__,__func__) +int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file, int line, const char* func); +int unit_free(struct block_list *bl, clr_type clrtype); +int unit_changeviewsize(struct block_list *bl,short size); + +// 初期化ルーチン +int do_init_unit(void); +int do_final_unit(void); +/** + * Ranger + **/ +int unit_wugdash(struct block_list *bl, struct map_session_data *sd); + +extern const short dirx[8]; +extern const short diry[8]; + +#endif /* _UNIT_H_ */ diff --git a/src/map/vending.c b/src/map/vending.c new file mode 100644 index 000000000..0f8255788 --- /dev/null +++ b/src/map/vending.c @@ -0,0 +1,417 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/nullpo.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "clif.h" +#include "itemdb.h" +#include "atcommand.h" +#include "map.h" +#include "path.h" +#include "chrif.h" +#include "vending.h" +#include "pc.h" +#include "npc.h" +#include "skill.h" +#include "battle.h" +#include "log.h" + +#include <stdio.h> +#include <string.h> + +static int vending_nextid = 0; + +/// Returns an unique vending shop id. +static int vending_getuid(void) +{ + return vending_nextid++; +} + +/*========================================== + * Close shop + *------------------------------------------*/ +void vending_closevending(struct map_session_data* sd) +{ + nullpo_retv(sd); + + if( sd->state.vending ) + { + sd->state.vending = false; + clif_closevendingboard(&sd->bl, 0); + } +} + +/*========================================== + * Request a shop's item list + *------------------------------------------*/ +void vending_vendinglistreq(struct map_session_data* sd, int id) +{ + struct map_session_data* vsd; + nullpo_retv(sd); + + if( (vsd = map_id2sd(id)) == NULL ) + return; + if( !vsd->state.vending ) + return; // not vending + + if (!pc_can_give_items(sd) || !pc_can_give_items(vsd)) //check if both GMs are allowed to trade + { // GM is not allowed to trade + clif_displaymessage(sd->fd, msg_txt(246)); + return; + } + + sd->vended_id = vsd->vender_id; // register vending uid + + clif_vendinglist(sd, id, vsd->vending); +} + +/*========================================== + * Purchase item(s) from a shop + *------------------------------------------*/ +void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count) +{ + int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING]; + double z; + struct s_vending vending[MAX_VENDING]; // against duplicate packets + struct map_session_data* vsd = map_id2sd(aid); + + nullpo_retv(sd); + if( vsd == NULL || !vsd->state.vending || vsd->bl.id == sd->bl.id ) + return; // invalid shop + + if( vsd->vender_id != uid ) + {// shop has changed + clif_buyvending(sd, 0, 0, 6); // store information was incorrect + return; + } + + if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) ) + return; // shop too far away + + searchstore_clearremote(sd); + + if( count < 1 || count > MAX_VENDING || count > vsd->vend_num ) + return; // invalid amount of purchased items + + blank = pc_inventoryblank(sd); //number of free cells in the buyer's inventory + + // duplicate item in vending to check hacker with multiple packets + memcpy(&vending, &vsd->vending, sizeof(vsd->vending)); // copy vending list + + // some checks + z = 0.; // zeny counter + w = 0; // weight counter + for( i = 0; i < count; i++ ) + { + short amount = *(uint16*)(data + 4*i + 0); + short idx = *(uint16*)(data + 4*i + 2); + idx -= 2; + + if( amount <= 0 ) + return; + + // check of item index in the cart + if( idx < 0 || idx >= MAX_CART ) + return; + + ARR_FIND( 0, vsd->vend_num, j, vsd->vending[j].index == idx ); + if( j == vsd->vend_num ) + return; //picked non-existing item + else + vend_list[i] = j; + + z += ((double)vsd->vending[j].value * (double)amount); + if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) + { + clif_buyvending(sd, idx, amount, 1); // you don't have enough zeny + return; + } + if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) + { + clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow + return; + + } + w += itemdb_weight(vsd->status.cart[idx].nameid) * amount; + if( w + sd->weight > sd->max_weight ) + { + clif_buyvending(sd, idx, amount, 2); // you can not buy, because overweight + return; + } + + //Check to see if cart/vend info is in sync. + if( vending[j].amount > vsd->status.cart[idx].amount ) + vending[j].amount = vsd->status.cart[idx].amount; + + // if they try to add packets (example: get twice or more 2 apples if marchand has only 3 apples). + // here, we check cumulative amounts + if( vending[j].amount < amount ) + { + // send more quantity is not a hack (an other player can have buy items just before) + clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // not enough quantity + return; + } + + vending[j].amount -= amount; + + switch( pc_checkadditem(sd, vsd->status.cart[idx].nameid, amount) ) { + case ADDITEM_EXIST: + break; //We'd add this item to the existing one (in buyers inventory) + case ADDITEM_NEW: + new_++; + if (new_ > blank) + return; //Buyer has no space in his inventory + break; + case ADDITEM_OVERAMOUNT: + return; //too many items + } + } + + pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd); + if( battle_config.vending_tax ) + z -= z * (battle_config.vending_tax/10000.); + pc_getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd); + + for( i = 0; i < count; i++ ) + { + short amount = *(uint16*)(data + 4*i + 0); + short idx = *(uint16*)(data + 4*i + 2); + idx -= 2; + + // vending item + pc_additem(sd, &vsd->status.cart[idx], amount, LOG_TYPE_VENDING); + vsd->vending[vend_list[i]].amount -= amount; + pc_cart_delitem(vsd, idx, amount, 0, LOG_TYPE_VENDING); + clif_vendingreport(vsd, idx, amount); + + //print buyer's name + if( battle_config.buyer_name ) + { + char temp[256]; + sprintf(temp, msg_txt(265), sd->status.name); + clif_disp_onlyself(vsd,temp,strlen(temp)); + } + } + + // compact the vending list + for( i = 0, cursor = 0; i < vsd->vend_num; i++ ) + { + if( vsd->vending[i].amount == 0 ) + continue; + + if( cursor != i ) // speedup + { + vsd->vending[cursor].index = vsd->vending[i].index; + vsd->vending[cursor].amount = vsd->vending[i].amount; + vsd->vending[cursor].value = vsd->vending[i].value; + } + + cursor++; + } + vsd->vend_num = cursor; + + //Always save BOTH: buyer and customer + if( save_settings&2 ) + { + chrif_save(sd,0); + chrif_save(vsd,0); + } + + //check for @AUTOTRADE users [durf] + if( vsd->state.autotrade ) + { + //see if there is anything left in the shop + ARR_FIND( 0, vsd->vend_num, i, vsd->vending[i].amount > 0 ); + if( i == vsd->vend_num ) + { + //Close Vending (this was automatically done by the client, we have to do it manually for autovenders) [Skotlex] + vending_closevending(vsd); + map_quit(vsd); //They have no reason to stay around anymore, do they? + } + } +} +static int vending_checknearnpc_sub(struct block_list* bl, va_list args) { + struct npc_data *nd = (struct npc_data*)bl; + + if( nd->sc.option & (OPTION_HIDE|OPTION_INVISIBLE) ) + return 0; + + return 1; +} +bool vending_checknearnpc(struct block_list * bl) { + + if( battle_config.min_npc_vending_distance > 0 && + map_foreachinrange(vending_checknearnpc_sub,bl, battle_config.min_npc_vending_distance, BL_NPC) ) + return true; + + return false; +} +/*========================================== + * Open shop + * data := {<index>.w <amount>.w <value>.l}[count] + *------------------------------------------*/ +void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count) { + int i, j; + int vending_skill_lvl; + nullpo_retv(sd); + + if( !flag ) // cancelled + return; // nothing to do + + if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) + return; // can't open vendings lying dead || didn't use via the skill (wpe/hack) || can't have 2 shops at once + + vending_skill_lvl = pc_checkskill(sd, MC_VENDING); + // skill level and cart check + if( !vending_skill_lvl || !pc_iscarton(sd) ) + { + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); + return; + } + + // check number of items in shop + if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) + { // invalid item count + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); + return; + } + //check if nearby npc, (perhaps we should check for nearby shop too + if( vending_checknearnpc(&sd->bl) ) { + char output[150]; + sprintf(output,"You're too close to a NPC, you must be at least %d cells away from any NPC.",battle_config.min_npc_vending_distance); + clif_displaymessage(sd->fd, output); + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); + return; + } + + + // filter out invalid items + i = 0; + for( j = 0; j < count; j++ ) + { + short index = *(uint16*)(data + 8*j + 0); + short amount = *(uint16*)(data + 8*j + 2); + unsigned int value = *(uint32*)(data + 8*j + 4); + + index -= 2; // offset adjustment (client says that the first cart position is 2) + + if( index < 0 || index >= MAX_CART // invalid position + || pc_cartitem_amount(sd, index, amount) < 0 // invalid item or insufficient quantity + //NOTE: official server does not do any of the following checks! + || !sd->status.cart[index].identify // unidentified item + || sd->status.cart[index].attribute == 1 // broken item + || sd->status.cart[index].expire_time // It should not be in the cart but just in case + || !itemdb_cantrade(&sd->status.cart[index], pc_get_group_level(sd), pc_get_group_level(sd)) ) // untradeable item + continue; + + sd->vending[i].index = index; + sd->vending[i].amount = amount; + sd->vending[i].value = cap_value(value, 0, (unsigned int)battle_config.vending_max_value); + + i++; // item successfully added + } + + if( i != j ) + clif_displaymessage (sd->fd, msg_txt(266)); //"Some of your items cannot be vended and were removed from the shop." + + if( i == 0 ) + { // no valid item found + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // custom reply packet + return; + } + sd->state.prevend = 0; + sd->state.vending = true; + sd->vender_id = vending_getuid(); + sd->vend_num = i; + safestrncpy(sd->message, message, MESSAGE_SIZE); + + pc_stop_walking(sd,1); + clif_openvending(sd,sd->bl.id,sd->vending); + clif_showvendingboard(&sd->bl,message,0); +} + + +/// Checks if an item is being sold in given player's vending. +bool vending_search(struct map_session_data* sd, unsigned short nameid) +{ + int i; + + if( !sd->state.vending ) + {// not vending + return false; + } + + ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)nameid ); + if( i == sd->vend_num ) + {// not found + return false; + } + + return true; +} + + +/// Searches for all items in a vending, that match given ids, price and possible cards. +/// @return Whether or not the search should be continued. +bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s) +{ + int i, c, slot; + unsigned int idx, cidx; + struct item* it; + + if( !sd->state.vending ) + {// not vending + return true; + } + + for( idx = 0; idx < s->item_count; idx++ ) + { + ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)s->itemlist[idx] ); + if( i == sd->vend_num ) + {// not found + continue; + } + it = &sd->status.cart[sd->vending[i].index]; + + if( s->min_price && s->min_price > sd->vending[i].value ) + {// too low price + continue; + } + + if( s->max_price && s->max_price < sd->vending[i].value ) + {// too high price + continue; + } + + if( s->card_count ) + {// check cards + if( itemdb_isspecial(it->card[0]) ) + {// something, that is not a carded + continue; + } + slot = itemdb_slot(it->nameid); + + for( c = 0; c < slot && it->card[c]; c ++ ) + { + ARR_FIND( 0, s->card_count, cidx, s->cardlist[cidx] == it->card[c] ); + if( cidx != s->card_count ) + {// found + break; + } + } + + if( c == slot || !it->card[c] ) + {// no card match + continue; + } + } + + if( !searchstore_result(s->search_sd, sd->vender_id, sd->status.account_id, sd->message, it->nameid, sd->vending[i].amount, sd->vending[i].value, it->card, it->refine) ) + {// result set full + return false; + } + } + + return true; +} diff --git a/src/map/vending.h b/src/map/vending.h new file mode 100644 index 000000000..2ed52b9bd --- /dev/null +++ b/src/map/vending.h @@ -0,0 +1,26 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _VENDING_H_ +#define _VENDING_H_ + +#include "../common/cbasetypes.h" +//#include "map.h" +struct map_session_data; +struct s_search_store_search; + +struct s_vending { + short index; //cart index (return item data) + short amount; //amout of the item for vending + unsigned int value; //at wich price +}; + +void vending_closevending(struct map_session_data* sd); +void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count); +void vending_vendinglistreq(struct map_session_data* sd, int id); +void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count); +bool vending_search(struct map_session_data* sd, unsigned short nameid); +bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s); +bool vending_checknearnpc(struct block_list * bl); + +#endif /* _VENDING_H_ */ diff --git a/src/test/Makefile.in b/src/test/Makefile.in new file mode 100644 index 000000000..c601de9cb --- /dev/null +++ b/src/test/Makefile.in @@ -0,0 +1,61 @@ + +COMMON_H = $(shell ls ../common/*.h) + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +TEST_SPINLOCK_OBJ=obj/test_spinlock.o +TEST_SPINLOCK_H= +TEST_SPINLOCK_DEPENDS=obj $(TEST_SPINLOCK_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) + +@SET_MAKE@ + +##################################################################### +.PHONY :all test_spinlock + +all: test_spinlock + +clean: + @echo " CLEAN test" + @rm -rf *.o obj ../../test_spinlock@EXEEXT@ + +##################################################################### + +# object directories + +obj: + @echo " MKDIR obj" + @-mkdir obj + +#executables + +test_spinlock: $(TEST_SPINLOCK_DEPENDS) + @echo " LD $@" + @@CC@ @LDFLAGS@ -o ../../test_spinlock@EXEEXT@ $(TEST_SPINLOCK_OBJ) ../common/obj_sql/common_sql.a ../common/obj_all/common.a $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) @LIBS@ @MYSQL_LIBS@ + +# login object files + +obj/%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) -DWITH_SQL @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +# missing object files +../common/obj_all/common.a: + @$(MAKE) -C ../common sql + +../common/obj_sql/common_sql.a: + @$(MAKE) -C ../common sql + +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/test/test_spinlock.c b/src/test/test_spinlock.c new file mode 100644 index 000000000..878ee8bab --- /dev/null +++ b/src/test/test_spinlock.c @@ -0,0 +1,117 @@ + +#include "../common/core.h" +#include "../common/atomic.h" +#include "../common/thread.h" +#include "../common/spinlock.h" +#include "../common/showmsg.h" + +#include <stdio.h> +#include <stdlib.h> + +// +// Simple test for the spinlock implementation to see if it works properly.. +// + + + +#define THRC 32 //thread Count +#define PERINC 100000 +#define LOOPS 47 + + +static SPIN_LOCK lock; +static int val = 0; +static volatile int32 done_threads = 0; + +static void *worker(void *p){ + register int i; + + for(i = 0; i < PERINC; i++){ + EnterSpinLock(&lock); + EnterSpinLock(&lock); + + val++; + + LeaveSpinLock(&lock); + LeaveSpinLock(&lock); + } + + InterlockedIncrement(&done_threads); + + return NULL; +}//end: worker() + + +int do_init(int argc, char **argv){ + rAthread t[THRC]; + int j, i; + int ok; + + ShowStatus("==========\n"); + ShowStatus("TEST: %u Runs, (%u Threads)\n", LOOPS, THRC); + ShowStatus("This can take a while\n"); + ShowStatus("\n\n"); + + ok =0; + for(j = 0; j < LOOPS; j++){ + val = 0; + done_threads = 0; + + InitializeSpinLock(&lock); + + + for(i =0; i < THRC; i++){ + t[i] = rathread_createEx( worker, NULL, 1024*512, RAT_PRIO_NORMAL); + } + + + while(1){ + if(InterlockedCompareExchange(&done_threads, THRC, THRC) == THRC) + break; + + rathread_yield(); + } + + FinalizeSpinLock(&lock); + + // Everything fine? + if(val != (THRC*PERINC) ){ + printf("FAILED! (Result: %u, Expected: %u)\n", val, (THRC*PERINC) ); + }else{ + printf("OK! (Result: %u, Expected: %u)\n", val, (THRC*PERINC) ); + ok++; + } + + } + + + if(ok != LOOPS){ + ShowFatalError("Test failed.\n"); + exit(1); + }else{ + ShowStatus("Test passed.\n"); + exit(0); + } + + +return 0; +}//end: do_init() + + +void do_abort(){ +}//end: do_abort() + + +void set_server_type(){ + SERVER_TYPE = ATHENA_SERVER_NONE; +}//end: set_server_type() + + +void do_final(){ +}//end: do_final() + + +int parse_console(const char* command){ + return 0; +}//end: parse_console + diff --git a/src/tool/CMakeLists.txt b/src/tool/CMakeLists.txt new file mode 100644 index 000000000..a54ffa0e2 --- /dev/null +++ b/src/tool/CMakeLists.txt @@ -0,0 +1,45 @@ + +# +# mapcache +# +if( WITH_ZLIB ) + option( BUILD_MAPCACHE "build mapcache executable" ON ) +else() + message( STATUS "Disabled mapcache target (required ZLIB)" ) +endif() +if( BUILD_MAPCACHE ) +message( STATUS "Creating target mapcache" ) +set( COMMON_HEADERS + ${COMMON_MINI_HEADERS} + "${COMMON_SOURCE_DIR}/des.h" + "${COMMON_SOURCE_DIR}/grfio.h" + "${COMMON_SOURCE_DIR}/utils.h" + ) +set( COMMON_SOURCES + ${COMMON_MINI_SOURCES} + "${COMMON_SOURCE_DIR}/des.c" + "${COMMON_SOURCE_DIR}/grfio.c" + "${COMMON_SOURCE_DIR}/utils.c" + ) +set( MAPCACHE_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/mapcache.c" + ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ${ZLIB_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${COMMON_MINI_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_MINI_DEFINITIONS}" ) +set( SOURCE_FILES ${COMMON_HEADERS} ${COMMON_SOURCES} ${MAPCACHE_SOURCES} ) +source_group( common FILES ${COMMON_HEADERS} ${COMMON_SOURCES} ) +source_group( mapcache FILES ${MAPCACHE_SOURCES} ) +add_executable( mapcache ${SOURCE_FILES} ) +include_directories( ${INCLUDE_DIRS} ) +target_link_libraries( mapcache ${LIBRARIES} ) +set_target_properties( mapcache PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +if( INSTALL_COMPONENT_RUNTIME ) + cpack_add_component( Runtime_mapcache DESCRIPTION "mapcache generator" DISPLAY_NAME "mapcache" GROUP Runtime ) + install( TARGETS mapcache + DESTINATION "." + COMPONENT Runtime_mapcache ) +endif( INSTALL_COMPONENT_RUNTIME ) +set( TARGET_LIST ${TARGET_LIST} mapcache CACHE INTERNAL "" ) +message( STATUS "Creating target mapcache - done" ) +endif( BUILD_MAPCACHE ) diff --git a/src/tool/Makefile.in b/src/tool/Makefile.in new file mode 100644 index 000000000..d72ef5405 --- /dev/null +++ b/src/tool/Makefile.in @@ -0,0 +1,59 @@ + +COMMON_OBJ = ../common/obj_all/minicore.o ../common/obj_all/malloc.o \ + ../common/obj_all/showmsg.o ../common/obj_all/strlib.o \ + ../common/obj_all/utils.o ../common/obj_all/des.o ../common/obj_all/grfio.o +COMMON_H = ../common/core.h ../common/mmo.h \ + ../common/malloc.h ../common/showmsg.h ../common/strlib.h \ + ../common/utils.h ../common/cbasetypes.h ../common/des.h ../common/grfio.h ../config/renewal.h + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +OTHER_H = ../config/renewal.h + +MAPCACHE_OBJ = obj_all/mapcache.o + +@SET_MAKE@ + +##################################################################### +.PHONY : all mapcache clean help + +all: mapcache + +mapcache: obj_all $(MAPCACHE_OBJ) $(COMMON_OBJ) $(LIBCONFIG_OBJ) + @echo " LD $@" + @@CC@ @LDFLAGS@ $(LIBCONFIG_INCLUDE) -o ../../mapcache@EXEEXT@ $(MAPCACHE_OBJ) $(COMMON_OBJ) $(LIBCONFIG_OBJ) @LIBS@ + +clean: + @echo " CLEAN tool" + @rm -rf obj_all/*.o ../../mapcache@EXEEXT@ + +help: + @echo "possible targets are 'mapcache' 'all' 'clean' 'help'" + @echo "'mapcache' - mapcache generator" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +obj_all: + -mkdir obj_all + +obj_all/%.o: %.c $(COMMON_H) $(OTHER_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(LIBCONFIG_INCLUDE) @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +# missing common object files +../common/obj_all/%.o: + @$(MAKE) -C ../common txt + +../common/obj_all/mini%.o: + @$(MAKE) -C ../common txt + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/tool/mapcache.c b/src/tool/mapcache.c new file mode 100644 index 000000000..49f948709 --- /dev/null +++ b/src/tool/mapcache.c @@ -0,0 +1,355 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" + +#include "../config/renewal.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +#define NO_WATER 1000000 + +char grf_list_file[256] = "conf/grf-files.txt"; +char map_list_file[256] = "db/map_index.txt"; +char map_cache_file[256]; +int rebuild = 0; + +FILE *map_cache_fp; + +unsigned long file_size; + +// Used internally, this structure contains the physical map cells +struct map_data { + int16 xs; + int16 ys; + unsigned char *cells; +}; + +// This is the main header found at the very beginning of the file +struct main_header { + uint32 file_size; + uint16 map_count; +} header; + +// This is the header appended before every compressed map cells info +struct map_info { + char name[MAP_NAME_LENGTH]; + int16 xs; + int16 ys; + int32 len; +}; + + +/************************************* +* Big-endian compatibility functions * +*************************************/ + +// Converts an int16 from current machine order to little-endian +int16 MakeShortLE(int16 val) +{ + unsigned char buf[2]; + buf[0] = (unsigned char)( (val & 0x00FF) ); + buf[1] = (unsigned char)( (val & 0xFF00) >> 0x08 ); + return *((int16*)buf); +} + +// Converts an int32 from current machine order to little-endian +int32 MakeLongLE(int32 val) +{ + unsigned char buf[4]; + buf[0] = (unsigned char)( (val & 0x000000FF) ); + buf[1] = (unsigned char)( (val & 0x0000FF00) >> 0x08 ); + buf[2] = (unsigned char)( (val & 0x00FF0000) >> 0x10 ); + buf[3] = (unsigned char)( (val & 0xFF000000) >> 0x18 ); + return *((int32*)buf); +} + +// Reads an uint16 in little-endian from the buffer +uint16 GetUShort(const unsigned char* buf) +{ + return ( ((uint16)(buf[0])) ) + |( ((uint16)(buf[1])) << 0x08 ); +} + +// Reads an uint32 in little-endian from the buffer +uint32 GetULong(const unsigned char* buf) +{ + return ( ((uint32)(buf[0])) ) + |( ((uint32)(buf[1])) << 0x08 ) + |( ((uint32)(buf[2])) << 0x10 ) + |( ((uint32)(buf[3])) << 0x18 ); +} + +// Reads an int32 in little-endian from the buffer +int32 GetLong(const unsigned char* buf) +{ + return (int32)GetULong(buf); +} + +// Reads a float (32 bits) from the buffer +float GetFloat(const unsigned char* buf) +{ + uint32 val = GetULong(buf); + return *((float*)(void*)&val); +} + + +// Reads a map from GRF's GAT and RSW files +int read_map(char *name, struct map_data *m) +{ + char filename[256]; + unsigned char *gat, *rsw; + int water_height; + size_t xy, off, num_cells; + float height; + uint32 type; + + // Open map GAT + sprintf(filename,"data\\%s.gat", name); + gat = (unsigned char *)grfio_read(filename); + if (gat == NULL) + return 0; + + // Open map RSW + sprintf(filename,"data\\%s.rsw", name); + rsw = (unsigned char *)grfio_read(filename); + + // Read water height + if (rsw) { + water_height = (int)GetFloat(rsw+166); + aFree(rsw); + } else + water_height = NO_WATER; + + // Read map size and allocate needed memory + m->xs = (int16)GetULong(gat+6); + m->ys = (int16)GetULong(gat+10); + if (m->xs <= 0 || m->ys <= 0) { + aFree(gat); + return 0; + } + num_cells = (size_t)m->xs*(size_t)m->ys; + m->cells = (unsigned char *)aMalloc(num_cells); + + // Set cell properties + off = 14; + for (xy = 0; xy < num_cells; xy++) + { + // Height of the bottom-left corner + height = GetFloat( gat + off ); + // Type of cell + type = GetULong( gat + off + 16 ); + off += 20; + + if (type == 0 && water_height != NO_WATER && height > water_height) + type = 3; // Cell is 0 (walkable) but under water level, set to 3 (walkable water) + + m->cells[xy] = (unsigned char)type; + } + + aFree(gat); + + return 1; +} + +// Adds a map to the cache +void cache_map(char *name, struct map_data *m) +{ + struct map_info info; + unsigned long len; + unsigned char *write_buf; + + // Create an output buffer twice as big as the uncompressed map... this way we're sure it fits + len = (unsigned long)m->xs*(unsigned long)m->ys*2; + write_buf = (unsigned char *)aMalloc(len); + // Compress the cells and get the compressed length + encode_zip(write_buf, &len, m->cells, m->xs*m->ys); + + // Fill the map header + if (strlen(name) > MAP_NAME_LENGTH) // It does not hurt to warn that there are maps with name longer than allowed. + ShowWarning ("Map name '%s' size '%d' is too long. Truncating to '%d'.\n", name, strlen(name), MAP_NAME_LENGTH); + strncpy(info.name, name, MAP_NAME_LENGTH); + info.xs = MakeShortLE(m->xs); + info.ys = MakeShortLE(m->ys); + info.len = MakeLongLE(len); + + // Append map header then compressed cells at the end of the file + fseek(map_cache_fp, header.file_size, SEEK_SET); + fwrite(&info, sizeof(struct map_info), 1, map_cache_fp); + fwrite(write_buf, 1, len, map_cache_fp); + header.file_size += sizeof(struct map_info) + len; + header.map_count++; + + aFree(write_buf); + aFree(m->cells); + + return; +} + +// Checks whether a map is already is the cache +int find_map(char *name) +{ + int i; + struct map_info info; + + fseek(map_cache_fp, sizeof(struct main_header), SEEK_SET); + + for(i = 0; i < header.map_count; i++) { + if(fread(&info, sizeof(info), 1, map_cache_fp) != 1) printf("An error as occured in fread while reading map_cache\n"); + if(strcmp(name, info.name) == 0) // Map found + return 1; + else // Map not found, jump to the beginning of the next map info header + fseek(map_cache_fp, GetLong((unsigned char *)&(info.len)), SEEK_CUR); + } + + return 0; +} + +// Cuts the extension from a map name +char *remove_extension(char *mapname) +{ + char *ptr, *ptr2; + ptr = strchr(mapname, '.'); + if (ptr) { //Check and remove extension. + while (ptr[1] && (ptr2 = strchr(ptr+1, '.'))) + ptr = ptr2; //Skip to the last dot. + if (strcmp(ptr,".gat") == 0) + *ptr = '\0'; //Remove extension. + } + return mapname; +} + +// Processes command-line arguments +void process_args(int argc, char *argv[]) +{ + int i; + + for(i = 0; i < argc; i++) { + if(strcmp(argv[i], "-grf") == 0) { + if(++i < argc) + strcpy(grf_list_file, argv[i]); + } else if(strcmp(argv[i], "-list") == 0) { + if(++i < argc) + strcpy(map_list_file, argv[i]); + } else if(strcmp(argv[i], "-cache") == 0) { + if(++i < argc) + strcpy(map_cache_file, argv[i]); + } else if(strcmp(argv[i], "-rebuild") == 0) + rebuild = 1; + } + +} + +int do_init(int argc, char** argv) +{ + FILE *list; + char line[1024]; + struct map_data map; + char name[MAP_NAME_LENGTH_EXT]; + + /* setup pre-defined, #define-dependant */ + sprintf(map_cache_file,"db/%s/map_cache.dat", +#ifdef RENEWAL + "re" +#else + "pre-re" +#endif + ); + + // Process the command-line arguments + process_args(argc, argv); + + ShowStatus("Initializing grfio with %s\n", grf_list_file); + grfio_init(grf_list_file); + + // Attempt to open the map cache file and force rebuild if not found + ShowStatus("Opening map cache: %s\n", map_cache_file); + if(!rebuild) { + map_cache_fp = fopen(map_cache_file, "rb"); + if(map_cache_fp == NULL) { + ShowNotice("Existing map cache not found, forcing rebuild mode\n"); + rebuild = 1; + } else + fclose(map_cache_fp); + } + if(rebuild) + map_cache_fp = fopen(map_cache_file, "w+b"); + else + map_cache_fp = fopen(map_cache_file, "r+b"); + if(map_cache_fp == NULL) { + ShowError("Failure when opening map cache file %s\n", map_cache_file); + exit(EXIT_FAILURE); + } + + // Open the map list + ShowStatus("Opening map list: %s\n", map_list_file); + list = fopen(map_list_file, "r"); + if(list == NULL) { + ShowError("Failure when opening maps list file %s\n", map_list_file); + exit(EXIT_FAILURE); + } + + // Initialize the main header + if(rebuild) { + header.file_size = sizeof(struct main_header); + header.map_count = 0; + } else { + if(fread(&header, sizeof(struct main_header), 1, map_cache_fp) != 1){ printf("An error as occured while reading map_cache_fp \n"); } + header.file_size = GetULong((unsigned char *)&(header.file_size)); + header.map_count = GetUShort((unsigned char *)&(header.map_count)); + } + + // Read and process the map list + while(fgets(line, sizeof(line), list)) + { + if(line[0] == '/' && line[1] == '/') + continue; + + if(sscanf(line, "%15s", name) < 1) + continue; + + if(strcmp("map:", name) == 0 && sscanf(line, "%*s %15s", name) < 1) + continue; + + name[MAP_NAME_LENGTH_EXT-1] = '\0'; + remove_extension(name); + if(find_map(name)) + ShowInfo("Map '"CL_WHITE"%s"CL_RESET"' already in cache.\n", name); + else if(read_map(name, &map)) { + cache_map(name, &map); + ShowInfo("Map '"CL_WHITE"%s"CL_RESET"' successfully cached.\n", name); + } else + ShowError("Map '"CL_WHITE"%s"CL_RESET"' not found!\n", name); + + } + + ShowStatus("Closing map list: %s\n", map_list_file); + fclose(list); + + // Write the main header and close the map cache + ShowStatus("Closing map cache: %s\n", map_cache_file); + fseek(map_cache_fp, 0, SEEK_SET); + fwrite(&header, sizeof(struct main_header), 1, map_cache_fp); + fclose(map_cache_fp); + + ShowStatus("Finalizing grfio\n"); + grfio_final(); + + ShowInfo("%d maps now in cache\n", header.map_count); + + return 0; +} + +void do_final(void) +{ +} |