summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTrojal <trojal@gmail.com>2013-01-10 20:09:39 -0800
committershennetsind <ind@henn.et>2013-01-12 05:56:35 -0200
commitc55855fcf627478f864c0f82a1a2f201fd407a38 (patch)
treeb7f6d11b2058248d026f2d9944e8f4b6ac288d50 /src
parent51bfeb38eb139e97e0e1c096c85c15fba234f35b (diff)
parent38e583df21eccd9e4f31d38acaae32579c6f0d27 (diff)
downloadhercules-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')
-rw-r--r--src/CMakeLists.txt19
-rw-r--r--src/char/CMakeLists.txt60
-rw-r--r--src/char/Makefile.in76
-rw-r--r--src/char/char.c4800
-rw-r--r--src/char/char.h83
-rw-r--r--src/char/int_auction.c495
-rw-r--r--src/char/int_auction.h12
-rw-r--r--src/char/int_elemental.c163
-rw-r--r--src/char/int_elemental.h15
-rw-r--r--src/char/int_guild.c1872
-rw-r--r--src/char/int_guild.h37
-rw-r--r--src/char/int_homun.c314
-rw-r--r--src/char/int_homun.h18
-rw-r--r--src/char/int_mail.c483
-rw-r--r--src/char/int_mail.h16
-rw-r--r--src/char/int_mercenary.c218
-rw-r--r--src/char/int_mercenary.h20
-rw-r--r--src/char/int_party.c863
-rw-r--r--src/char/int_party.h26
-rw-r--r--src/char/int_pet.c309
-rw-r--r--src/char/int_pet.h21
-rw-r--r--src/char/int_quest.c184
-rw-r--r--src/char/int_quest.h13
-rw-r--r--src/char/int_storage.c248
-rw-r--r--src/char/int_storage.h22
-rw-r--r--src/char/inter.c1257
-rw-r--r--src/char/inter.h44
-rw-r--r--src/common/CMakeLists.txt163
-rw-r--r--src/common/Makefile.in97
-rw-r--r--src/common/atomic.h142
-rw-r--r--src/common/cbasetypes.h404
-rw-r--r--src/common/conf.c109
-rw-r--r--src/common/conf.h13
-rw-r--r--src/common/core.c355
-rw-r--r--src/common/core.h47
-rw-r--r--src/common/db.c2822
-rw-r--r--src/common/db.h1492
-rw-r--r--src/common/des.c218
-rw-r--r--src/common/des.h15
-rw-r--r--src/common/ers.c292
-rw-r--r--src/common/ers.h172
-rw-r--r--src/common/evdp.h168
-rw-r--r--src/common/evdp_epoll.c232
-rw-r--r--src/common/grfio.c818
-rw-r--r--src/common/grfio.h17
-rw-r--r--src/common/malloc.c718
-rw-r--r--src/common/malloc.h92
-rw-r--r--src/common/mapindex.c185
-rw-r--r--src/common/mapindex.h60
-rw-r--r--src/common/md5calc.c240
-rw-r--r--src/common/md5calc.h8
-rw-r--r--src/common/mempool.c568
-rw-r--r--src/common/mempool.h100
-rw-r--r--src/common/mmo.h750
-rw-r--r--src/common/mutex.c247
-rw-r--r--src/common/mutex.h92
-rw-r--r--src/common/netbuffer.c221
-rw-r--r--src/common/netbuffer.h83
-rw-r--r--src/common/network.c1061
-rw-r--r--src/common/network.h189
-rw-r--r--src/common/nullpo.c91
-rw-r--r--src/common/nullpo.h225
-rw-r--r--src/common/raconf.c584
-rw-r--r--src/common/raconf.h59
-rw-r--r--src/common/random.c83
-rw-r--r--src/common/random.h18
-rw-r--r--src/common/showmsg.c892
-rw-r--r--src/common/showmsg.h99
-rw-r--r--src/common/socket.c1456
-rw-r--r--src/common/socket.h163
-rw-r--r--src/common/spinlock.h104
-rw-r--r--src/common/sql.c948
-rw-r--r--src/common/sql.h344
-rw-r--r--src/common/strlib.c1167
-rw-r--r--src/common/strlib.h155
-rw-r--r--src/common/thread.c317
-rw-r--r--src/common/thread.h119
-rw-r--r--src/common/timer.c432
-rw-r--r--src/common/timer.h57
-rw-r--r--src/common/utils.c283
-rw-r--r--src/common/utils.h32
-rw-r--r--src/common/winapi.h36
-rw-r--r--src/config/classes/general.h23
-rw-r--r--src/config/const.h99
-rw-r--r--src/config/core.h67
-rw-r--r--src/config/renewal.h71
-rw-r--r--src/config/secure.h33
-rw-r--r--src/login/CMakeLists.txt12
-rw-r--r--src/login/Makefile.in83
-rw-r--r--src/login/account.h156
-rw-r--r--src/login/account_sql.c680
-rw-r--r--src/login/ipban.h25
-rw-r--r--src/login/ipban_sql.c258
-rw-r--r--src/login/login.c1883
-rw-r--r--src/login/login.h102
-rw-r--r--src/login/loginlog.h15
-rw-r--r--src/login/loginlog_sql.c184
-rw-r--r--src/login/sql/CMakeLists.txt39
-rw-r--r--src/map/CMakeLists.txt12
-rw-r--r--src/map/Makefile.in114
-rw-r--r--src/map/atcommand.c9514
-rw-r--r--src/map/atcommand.h51
-rw-r--r--src/map/battle.c6132
-rw-r--r--src/map/battle.h503
-rw-r--r--src/map/battleground.c258
-rw-r--r--src/map/battleground.h45
-rw-r--r--src/map/buyingstore.c473
-rw-r--r--src/map/buyingstore.h33
-rw-r--r--src/map/chat.c425
-rw-r--r--src/map/chat.h43
-rw-r--r--src/map/chrif.c1623
-rw-r--r--src/map/chrif.h69
-rw-r--r--src/map/clif.c17128
-rw-r--r--src/map/clif.h772
-rw-r--r--src/map/date.c71
-rw-r--r--src/map/date.h18
-rw-r--r--src/map/duel.c182
-rw-r--r--src/map/duel.h29
-rw-r--r--src/map/elemental.c943
-rw-r--r--src/map/elemental.h94
-rw-r--r--src/map/guild.c2147
-rw-r--r--src/map/guild.h112
-rw-r--r--src/map/homunculus.c1284
-rw-r--r--src/map/homunculus.h132
-rw-r--r--src/map/instance.c488
-rw-r--r--src/map/instance.h51
-rw-r--r--src/map/intif.c2267
-rw-r--r--src/map/intif.h112
-rw-r--r--src/map/itemdb.c1457
-rw-r--r--src/map/itemdb.h229
-rw-r--r--src/map/log.c583
-rw-r--r--src/map/log.h89
-rw-r--r--src/map/mail.c185
-rw-r--r--src/map/mail.h19
-rw-r--r--src/map/map.c3952
-rw-r--r--src/map/map.h803
-rw-r--r--src/map/mapreg.h17
-rw-r--r--src/map/mapreg_sql.c235
-rw-r--r--src/map/mercenary.c510
-rw-r--r--src/map/mercenary.h83
-rw-r--r--src/map/mob.c4701
-rw-r--r--src/map/mob.h320
-rw-r--r--src/map/npc.c3895
-rw-r--r--src/map/npc.h184
-rw-r--r--src/map/npc_chat.c450
-rw-r--r--src/map/party.c1205
-rw-r--r--src/map/party.h95
-rw-r--r--src/map/path.c464
-rw-r--r--src/map/path.h43
-rw-r--r--src/map/pc.c9720
-rw-r--r--src/map/pc.h927
-rw-r--r--src/map/pc_groups.c468
-rw-r--r--src/map/pc_groups.h75
-rw-r--r--src/map/pet.c1388
-rw-r--r--src/map/pet.h136
-rw-r--r--src/map/quest.c358
-rw-r--r--src/map/quest.h34
-rw-r--r--src/map/script.c17779
-rw-r--r--src/map/script.h197
-rw-r--r--src/map/searchstore.c405
-rw-r--r--src/map/searchstore.h57
-rw-r--r--src/map/skill.c17983
-rw-r--r--src/map/skill.h1863
-rw-r--r--src/map/sql/CMakeLists.txt112
-rw-r--r--src/map/status.c11294
-rw-r--r--src/map/status.h1816
-rw-r--r--src/map/storage.c735
-rw-r--r--src/map/storage.h41
-rw-r--r--src/map/trade.c610
-rw-r--r--src/map/trade.h18
-rw-r--r--src/map/unit.c2524
-rw-r--r--src/map/unit.h145
-rw-r--r--src/map/vending.c417
-rw-r--r--src/map/vending.h26
-rw-r--r--src/test/Makefile.in61
-rw-r--r--src/test/test_spinlock.c117
-rw-r--r--src/tool/CMakeLists.txt45
-rw-r--r--src/tool/Makefile.in59
-rw-r--r--src/tool/mapcache.c355
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->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->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(&timestamp));
+ 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(&timestamp);
+ 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", &param1, &param2, &param3) < 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(&timestamp);
+ 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(&timestamp));
+ 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, &center, 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(&regen->tick, 0, sizeof(regen->tick));
+ if (regen->sregen)
+ memset(&regen->sregen->tick, 0, sizeof(regen->sregen->tick));
+ if (regen->ssregen)
+ memset(&regen->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)
+{
+}